When working with the func framework, users generally do not need to care about the details of what each invocation does. They can follow the application architecture and define concrete command scopes and services.
Once you understand the design model, there is almost no need to debug or dive into the internals. In that sense, it is much like developing a typical web application.
After developers declare their commands, the framework collects all metadata through reflection and stores it in registries. Whenever a user invokes the script, func selects a matching command and handler from these static registries,
then passes the user-provided context data to the handler. Before that data reaches the handler, declared validation and rules filter it automatically. At runtime, everything follows the declared types.
Definitions and invocations
A command is the most basic unit. A project usually contains multiple command-derived classes, which are the entry points ultimately used to execute commands.
@FuncModule() defines the application’s root routing and registers command scopes and services. Command scopes interpret CLI input and select actions; services provide reusable business capabilities. Each invocation selects only one execution path from the registered commands rather than running multiple command classes in sequence.
Application definition
@FuncModule()
├─ registers command scopes
│ ├─ @CommandMajor() entry without a named command
│ ├─ @Command({ name }) matches a top-level command
│ └─ @CommandMissing() optional fallback for no match
└─ registers @Service() reusable business capabilities
One CLI invocation that enters execution
└─ selects one command scope
└─ selects one @Handler()
├─ reads field options from the command instance
├─ reads positional input through @Args()
└─ calls injected services to perform the workCommand types
A command scope is the decorated class that ultimately owns an invocation. It owns the field options for that invocation and selects a handler inside the class. func has three mutually exclusive command scopes. If the corresponding major or missing command has not been registered, func does not manufacture a default scope:
| Command scope | Selection rule | func API |
|---|---|---|
| Major command | There is no input token, or the first token is an option | @CommandMajor() |
| Named command | The first bare token matches a name or alias | @Command({ name }) |
| Missing command | The first bare token matches no named command | @CommandMissing() (optional) |
One command scope executes only one handler
A handler is the method that performs a concrete action inside a command class. It is declared with @Handler(). One command scope can expose three kinds of action entry points:
- A default handler has no path or flag and runs when no other entry point matches.
- A path handler uses a fixed sequence of positional tokens, such as
member add, to select an action. - A flag handler uses an option such as
--helpor--versionto switch to a mutually exclusive action.
Regardless of how many handlers a class declares, one invocation ultimately runs only one. See Runtime for the exact priority between longest path matching, flag handlers, and the default handler.
Map a command to func concepts
The following example uses one invocation to connect all of the concepts, so it matches only @Command({ name: 'project' }). It first selects the project command scope, then the member add handler. The remaining parts provide positional and named data:
ship project member add alice --role owner --force
│ │ └───┬────┘ │ └────┬─────┘ └──┬──┘
│ │ │ │ │ └─ @Flag()
│ │ │ │ └─ @Value()
│ │ │ └─ @Args().inputs
│ │ └─ @Handler({ path: ['member', 'add'] })
│ └─ @Command({ name: 'project' })
└─ package.json#binThe other methods and properties in this command are instantiated only after the project command is matched. Which method executes depends on the remaining input.
Here, the input matches the ['member', 'add'] decorator on addMember, so that method runs:
import { Args, Command, Flag, FuncModule, Handler, Service, Value } from 'func'
import type { FuncArgs } from 'func'
@Service()
class ProjectService {
addMember(username: string, role = 'member', force = false) {
return { force, role, username }
}
}
@Command({ name: 'project' })
export class ProjectCommand {
@Value()
role?: string
@Flag()
force = false
constructor(private project: ProjectService) {}
@Handler({ path: ['member', 'add'] })
addMember(@Args() args: FuncArgs) {
const [username = ''] = args.inputs
console.log(this.project.addMember(username, this.role, this.force))
}
}
@FuncModule({
commands: [ProjectCommand],
services: [ProjectService],
})
export class AppModule {}What to read next
- Commands: define names, aliases, default handlers, and handler paths.
- Field Options: receive flags, values, repeated values, and validate them.
- Parameters: read positional input, the selected handler, and other runtime context.
- Runtime: follow module resolution, matching priority, instance creation, and the complete execution order.
- Glossary: look up precise definitions of the concepts introduced here.