@ooneex/service component is the service layer foundation for Ooneex applications. A service is an injectable unit of business logic — order processing, payment handling, email dispatch — that you register with a decorator and resolve from the container. Each service implements the IService interface, exposing a single execute(data?) entry point, and is wired into @ooneex/container so dependencies resolve automatically.
Why this component
- Business logic in one place. Keep domain operations out of controllers and routes; controllers stay thin and delegate to services.
- Container-managed. Register a service with
@decorator.service()and resolve it anywhere withcontainer.get()— no manual wiring. - One contract. Every service implements
IService, so they all share the sameexecute(data?)shape and compose predictably. - Scope control. Choose singleton, transient, or request scope per service to match its lifecycle.
- Composable. Services resolve and call other services, building larger operations from smaller units.
How it works
A service is a plain class decorated with@decorator.service(). The decorator calls container.add(target, scope), registering the class with @ooneex/container. From then on, container.get(MyService) returns an instance with its scope honored — the container constructs it, caches it (for singletons), and resolves any container-backed dependencies it pulls in.
The IService contract is intentionally minimal:
| Member | Purpose |
|---|---|
execute(data?) | The service’s single entry point. Accepts optional input data and runs the business logic. |
EContainerScope:
| Scope | Lifecycle |
|---|---|
EContainerScope.Singleton | One instance shared across the whole application (default). |
EContainerScope.Transient | A new instance on every container.get() resolution. |
EContainerScope.Request | A new instance per request context. |
Decorator and usage
@decorator.service(scope?)
Registers a service class with the container. It takes an optional scope (defaults to EContainerScope.Singleton) and returns a class decorator that adds the target to the container.
Best practices
- One responsibility per service. A service handles a single business operation; compose services rather than growing one into a god class.
- Keep
execute()the public entry point. Put orchestration inexecute()and break the steps into private methods. - Type the input. Replace the default
Record<string, unknown>data shape with a precise interface for the service’s input. - Handle the empty case.
datais optional — guard forundefinedbefore using it. - Resolve, don’t construct. Get services from the container with
container.get()so scopes and dependencies are honored; avoidnew. - Pick the right scope. Default to singleton for stateless logic; use transient or request scope only when per-call or per-request state matters.
- Let controllers stay thin. Keep routing and HTTP concerns in controllers and delegate the work to services.
CLI command
Scaffold a service class and its test file with the generator. It writes the class undermodules/<module>/src/services/<Name>Service.ts, adds a matching test, and installs @ooneex/service if it is missing.
| Option | Description | Default |
|---|---|---|
--name | Service class name. The Service suffix is appended automatically. | Prompted if omitted |
--module | Target module the class is generated into. | shared |
--override | Overwrite an existing class without prompting. | false |
IService stub:
Use with Claude and Codex
The generator ships a matchingservice:create skill. It runs the scaffold and then guides your AI agent through completing the service — defining a real ServiceDataType, implementing execute() with the business logic, and injecting dependencies. Initialize the skills once for your agent:
- Claude
- Codex
Prompt
service:create --name=Invoice, then implements execute() to compute the totals.