CLI Init
The Ambiten CLI scaffolds the architectural foundation for an Ambiten-powered application.
Rather than generating isolated starter files, it creates a runtime-oriented project structure where configuration, startup orchestration, framework integration, models, and optional operational services are already organized around Ambiten's runtime model.
The CLI does not execute the Ambiten runtime itself.
It prepares the application structure that AmbitenBootstrapFactory will initialize later.
CLI
→ scaffolds application architecture
AmbitenBootstrapFactory
→ prepares runtime infrastructure
Framework Adapter
→ establishes execution boundaries
AmbitenContext
→ carries execution state
MultiTenantManager
→ manages tenant infrastructure
AmbitenModel
→ performs data operationsIn short:
The CLI creates the structure.
The factory prepares the runtime.
Adapters enter the runtime.
Context carries execution state.
Models perform operations.Why the CLI Exists
Runtime-oriented applications often begin small and gradually accumulate infrastructure across unrelated files:
database initialization
logging
Redis
GraphQL
tenant configuration
framework setup
garbage collection
models
startup hooksOver time, startup behavior can become fragmented and difficult to reason about.
The Ambiten CLI establishes a consistent architecture from the beginning.
Without CLI scaffolding
→ runtime structure evolves independently
With CLI scaffolding
→ runtime boundaries are established from the first commitThe generated project is designed around the same lifecycle used by Ambiten itself:
Configuration
↓
Bootstrap
↓
Runtime Infrastructure
↓
Framework Adapter
↓
Execution Context
↓
Application LogicQuick Start
Create a new Ambiten project:
npx ambiten init my-appThen enter the generated project:
cd my-appIf dependencies were not installed during generation:
npm installStart development using the generated project scripts:
npm run devInteractive Mode
Run the CLI without a project name:
npx ambiten initThe CLI enters interactive mode and guides project generation through the available runtime capabilities.
Depending on the CLI version and selected options, prompts may include capabilities such as:
MongoDB configuration
multi-tenancy
GraphQL
Redis
logging
garbage collection
dependency installationInteractive mode is not only a convenience layer.
It allows the generated project structure to reflect the operational requirements of the application before runtime execution begins.
Command Structure
ambiten init [projectName] [options]Options
| Option | Description |
|---|---|
--with-graphql | Enable GraphQL runtime scaffolding |
--with-redis | Enable Redis integration |
--logger | Enable runtime logging |
--multi-tenant | Enable multi-tenant configuration |
--uri <mongodbUri> | Configure the MongoDB connection URI |
--rbac | Enable RBAC support when available |
--with-garbage-collector | Enable lifecycle cleanup support |
--install | Install generated dependencies automatically |
The exact generated surface depends on the capabilities selected during initialization.
Example: Multi-Tenant Application
A larger Ambiten application might be scaffolded with:
npx ambiten init my-saas \
--multi-tenant \
--with-graphql \
--with-redis \
--logger \
--with-garbage-collector \
--installThis prepares a project structure capable of supporting:
MongoDB
multi-tenancy
runtime context
GraphQL
Redis
logging
garbage collection
framework integrationwithout requiring those concerns to be assembled manually after generation.
Generated Project Structure
A generated project typically follows a structure similar to:
my-app/
├── ambiten.config.json
├── package.json
├── tsconfig.json
│
├── src/
│ ├── main.ts
│ │
│ ├── core/
│ │ └── initAmbiten.ts
│ │
│ ├── models/
│ ├── utils/
│ ├── types/
│ │
│ ├── graphql/ (optional)
│ └── gc/ (optional)
│
└── scripts/
└── runGC.ts (optional)The structure is intentionally organized around runtime boundaries rather than framework conventions alone.
A generated project separates:
configuration
startup
framework integration
execution
application code
operational infrastructureso those concerns can evolve independently.
Configuration-First Runtime
The CLI generates:
ambiten.config.jsonas the central runtime configuration surface.
For example:
{
"connection": {
"uri": "mongodb://localhost:27017/my-app"
},
"multiTenant": {
"enabled": true
},
"graphql": {
"enabled": false
}
}The exact configuration depends on the selected runtime capabilities.
The important architectural principle is:
configuration
↓
AmbitenBootstrapFactory
↓
runtime infrastructurerather than scattering infrastructure setup throughout application entry points.
Generated Runtime Initialization
The CLI creates a startup layer based on AmbitenBootstrapFactory.
For example:
import {
AmbitenBootstrapFactory
} from "@ambiten/core";
export async function initAmbiten() {
return AmbitenBootstrapFactory.create();
}Then the application can initialize the runtime once:
const runtime =
await initAmbiten();The factory reads the runtime configuration and prepares the configured infrastructure before application execution begins.
The CLI therefore creates the startup structure.
AmbitenBootstrapFactory performs the actual initialization.
What the Generated Runtime Can Initialize
MongoDB infrastructure and model registration
Prepares MongoDB connectivity, providers, schemas, and model infrastructure for the application process. Model registration remains structural at startup, while database, collection, tenant, and session resolution occur when operations execute.
Logging and runtime instrumentation
Initializes the configured logging and instrumentation infrastructure so later execution boundaries can enrich runtime operations with request, tenant, model, and operation metadata consistently.
Redis and cache infrastructure
Prepares Redis-backed and runtime cache capabilities at process startup so requests, workers, models, and other execution flows can reuse managed cache infrastructure without creating it per operation.
MultiTenantManager and tenant infrastructure
Configures tenant runtime infrastructure, including registered tenants, lazy client activation, and dynamic tenant discovery where configured. Request-bound tenant identity remains the responsibility of adapters, resolvers, or explicit execution context.
Optional GraphQL runtime capability
Assembles GraphQL-related runtime capabilities when configured while leaving operation-scoped execution to the GraphQL adapter and its context factory. Bootstrap prepares infrastructure; the adapter establishes resolver execution scope.
Runtime services and controlled shutdown
Starts and manages process-level lifecycle features such as garbage collection, TTL-oriented cleanup, connection hooks, and runtime-owned resources, then provides a coordinated shutdown boundary when the application terminates.
Depending on configuration, the runtime may prepare capabilities such as:
MongoDB
schema
model
logging
multi-tenancy
Redis
GraphQL
garbage collection
runtime servicesNot every generated project uses every capability.
The CLI selects the initial runtime surface according to the requested application architecture.
Generated Application Entry Point
The generated application entry point connects the initialized runtime to the application's framework.
A representative Express application may look like:
import express from "express";
import {
initAmbiten
} from "./core/initAmbiten";
import {
createExpressAdapter
} from "@ambiten/adapter-express";
async function main() {
const runtime =
await initAmbiten();
const app =
express();
app.use(
express.json()
);
const adapter =
createExpressAdapter();
await adapter.install(app, {
tenancy: {
header: "x-tenant-id"
}
});
app.listen(3000);
return runtime;
}
main().catch((error) => {
console.error(
"Application startup failed:",
error
);
process.exitCode = 1;
});The responsibilities are deliberately separated:
initAmbiten()
→ prepares runtime
createExpressAdapter()
→ integrates framework execution
adapter.install()
→ establishes request boundary
application routes
→ execute inside that boundaryConfiguration-Driven Multi-Tenancy
When multi-tenancy is defined through the generated Ambiten configuration, runtime initialization can prepare that tenant infrastructure during bootstrap.
This means generated applications should not automatically duplicate configuration-driven tenancy with an unnecessary startup call such as:
await runtime.registerMultiTenancy();if multi-tenancy has already been configured and initialized through the runtime configuration.
The preferred flow is:
ambiten.config.json
↓
AmbitenBootstrapFactory
↓
multi-tenancy initialized
↓
application startsProgrammatic registration remains useful when tenant infrastructure is intentionally supplied at runtime rather than through the configuration file.
For example:
await runtime.registerMultiTenancy({
tenants: {
tenant1:
"mongodb://localhost:27017/db_tenant1",
tenant2:
"mongodb://localhost:27017/db_tenant2"
},
lazy: true
});The two approaches represent different startup strategies:
Configuration-driven
→ tenancy declared in runtime configuration
Programmatic
→ tenancy supplied explicitly during startupApplications should normally choose one clear source for initial tenant configuration rather than duplicating registration.
Multi-Tenancy Architecture
The CLI can prepare an application for multi-tenancy, but multi-tenancy itself spans several runtime responsibilities.
CLI
→ scaffolds tenancy-capable architecture
BootstrapFactory
→ initializes tenant infrastructure
Framework Adapter
→ resolves request tenant identity
AmbitenContext
→ carries tenant identity
MultiTenantManager
→ manages tenant configuration and clients
AmbitenModel
→ executes against the active tenantThis distinction is important because enabling multi-tenancy during scaffolding does not mean every tenancy concern belongs in the generated startup file.
Static Tenants
Static tenants are known during runtime initialization.
Conceptually:
ambiten.config.json
↓
tenant1
tenant2
tenant3
↓
bootstrap
↓
MultiTenantManagerWhen lazy tenancy is enabled, tenants can be registered without immediately opening every MongoDB client.
For example, startup state may become:
{
registeredTenants: 3,
connectedTenants: 0,
lazyTenants: 3
}Database connections are then activated when tenant operations actually require them.
Dynamic Tenants
The CLI does not require every future tenant to be known during project generation.
Applications can later extend the runtime with a TenantConfigResolver.
For example:
import {
MultiTenantManager
} from "@ambiten/core";
MultiTenantManager.setTenantConfigResolver({
async resolve(tenantId) {
const config =
await lookupTenant(tenantId);
if (!config) {
return undefined;
}
return {
tenantId,
uri: config.uri,
dbName: config.dbName,
lazy: true,
metadata: config.metadata
};
}
});This allows the generated application to evolve from:
known startup tenantsto:
known startup tenants
+
runtime tenant discoverywithout changing the fundamental project architecture.
Dynamic tenant discovery is runtime behavior, not CLI behavior.
The CLI prepares the application structure in which that behavior can be added.
Framework Adapter Integration
The CLI prepares adapter-ready application entry points.
Framework adapters are responsible for connecting real framework execution to Ambiten.
Conceptually:
Express / Fastify / NestJS
↓
Framework Adapter
↓
adapter-runtime
↓
AmbitenContext
↓
applicationThe adapter resolves execution-specific information such as tenant identity.
For example:
x-tenant-id: tenant5can become:
AmbitenContext.get().tenantId;
// "tenant5"The generated application's downstream services and models do not need to repeatedly inspect framework request objects to determine the tenant.
CLI vs Framework Adapters
The CLI and adapters solve different problems.
CLI
→ creates application structure
Framework Adapter
→ handles runtime execution boundariesThe CLI may generate the adapter integration code, but the adapter only becomes operational when the generated application actually starts.
Conceptually:
Generation time
───────────────
CLI
↓
project files
Runtime
───────
BootstrapFactory
↓
Adapter
↓
AmbitenContext
↓
ApplicationThis distinction keeps scaffolding and runtime execution independent.
Tenant Resolution in Generated Applications
For a multi-tenant generated application, adapter configuration may resolve tenancy from a header:
await adapter.install(app, {
tenancy: {
header: "x-tenant-id"
}
});A request:
POST /users
x-tenant-id: tenant5can then become:
Request
↓
Framework Adapter
↓
TenantResolver
↓
tenant5
↓
AmbitenContext
↓
ApplicationIf tenant validation is required:
import {
MultiTenantManager
} from "@ambiten/core";
await adapter.install(app, {
tenancy: {
header: "x-tenant-id",
validate: async (tenantId) => {
const tenant =
await MultiTenantManager
.resolveTenant(tenantId);
if (!tenant) {
throw new Error(
`Tenant with ID "${tenantId}" not found.`
);
}
return true;
}
}
});This allows both statically registered and dynamically discovered tenants to participate in the same request flow.
Generated Models
Models belong to the runtime layer, not to the HTTP framework.
A generated application may obtain its configured model from the runtime:
const model =
runtime.getModel();Inside a tenant-aware request:
const user =
await model.create({
username: "Abinod Ltd",
email: "aemma@abinod.com"
});the active tenant can be resolved from AmbitenContext.
Conceptually:
route/controller
↓
model.create(...)
↓
AmbitenContext
tenantId = tenant5
↓
MultiTenantManager
↓
db_tenant5The model does not need to read:
Express headers
Fastify request
NestJS ExecutionContextto determine tenancy.
Runtime Context in Generated Projects
The generated architecture allows request-specific state to remain separate from application startup configuration.
For example:
import {
AmbitenContext
} from "@ambiten/core";
const context =
AmbitenContext.get();may provide:
{
tenantId: "tenant5",
requestId: "req_123"
}This state exists for the active execution.
It is different from process-level configuration stored in:
ambiten.config.jsonThe distinction is:
ambiten.config.json
→ application/runtime configuration
AmbitenContext
→ current execution stateGenerated Architecture
A useful mental model for a CLI-generated Ambiten application is:
GENERATION TIME
CLI
↓
Project Structure
↓
ambiten.config.json
↓
source files
RUNTIME
AmbitenBootstrapFactory
↓
AmbitenRuntime
↓
Framework Adapter
↓
AmbitenContext
↓
Application Logic
↓
AmbitenModel
↓
MultiTenantManager
↓
MongoDBThe CLI only participates in the first half.
Everything below runtime startup happens when the application executes.
Running the Generated Project
After generation:
cd my-appInstall dependencies when necessary:
npm installThen start development:
npm run devThe generated runtime initialization and framework entry point are already in place.
Application development can then focus on:
models
routes/controllers
services
tenant policy
application logicinstead of rebuilding the runtime foundation.
Installation Behavior
Dependencies are not installed automatically unless requested.
To install them during project generation:
npx ambiten init my-app --installTypical generated dependencies may include:
@ambiten/core
mongodb
selected Ambiten adapter packagesand, when selected:
GraphQL dependencies
Redis dependencies
logging support
other optional runtime packagesThe exact dependency surface depends on the requested capabilities.
Relationship with AmbitenBootstrapFactory
The CLI and AmbitenBootstrapFactory occupy consecutive stages of the application lifecycle.
CLI
→ generates startup architecture
AmbitenBootstrapFactory
→ executes startup architectureFor example:
npx ambiten init my-app
↓
src/core/initAmbiten.ts created
↓
application starts later
↓
AmbitenBootstrapFactory.create()
↓
runtime readyThe CLI generates the bootstrap integration.
The factory performs the actual runtime initialization.
Relationship with MultiTenantManager
The CLI can generate a tenancy-ready application, but it does not manage runtime tenants.
That responsibility belongs to MultiTenantManager.
CLI
→ scaffolds tenancy configuration
Bootstrap
→ initializes tenant infrastructure
MultiTenantManager
→ owns runtime tenant registryAfter startup, the manager may contain:
tenant1 → registered, lazy
tenant2 → registered, lazy
tenant3 → registered, lazyand later dynamically add:
tenant5 → registered, connectedwithout any involvement from the CLI.
Relationship with AmbitenContext
The CLI does not create runtime request context.
Framework adapters do.
CLI
→ generated adapter integration
Adapter
→ creates execution boundary
AmbitenContext
→ contains current execution stateThis separation prevents build-time/project-generation concerns from leaking into request-time behavior.
Architectural Position
The complete lifecycle is:
CLI
↓
Scaffold
↓
Configuration
↓
AmbitenBootstrapFactory
↓
AmbitenRuntime
↓
Framework Adapter
↓
Tenant Resolution
↓
AmbitenContext
↓
Application
↓
AmbitenModel
↓
MultiTenantManager
↓
MongoDBEach stage owns a different responsibility.
ESM and CommonJS Applications
Generated applications should use the module configuration produced by the CLI and supported by the selected Ambiten packages.
Ambiten's runtime and adapter packages support both ESM and CommonJS consumer boundaries.
Conceptually:
ESM application
→ ESM adapter
→ ESM adapter-runtime
→ ESM Coreand:
CommonJS application
→ CommonJS adapter
→ CommonJS adapter-runtime
→ CommonJS CoreApplications should avoid manually mixing incompatible module entry points.
Use public package imports:
import {
AmbitenBootstrapFactory
} from "@ambiten/core";and:
import {
createExpressAdapter
} from "@ambiten/adapter-express";rather than importing internal build paths.
Avoid:
import something from
"@ambiten/core/dist/...";Public package exports allow the correct module format to be selected for the consumer environment.
Graceful Shutdown
Generated long-running applications should shut down Ambiten at the application lifecycle boundary.
For example:
const runtime =
await initAmbiten();
process.on(
"SIGTERM",
async () => {
await runtime.shutdown();
process.exit(0);
}
);Shutdown should not happen after individual requests.
The runtime represents long-lived application infrastructure.
Recommended Usage
The CLI is best suited for full applications and long-lived services where startup organization and runtime consistency matter.
It is especially useful for:
- multi-tenant SaaS applications,
- API services,
- GraphQL applications,
- distributed services,
- operationally sensitive systems,
- applications using Redis or runtime logging,
- teams standardizing Ambiten application structure.
Smaller scripts or isolated infrastructure tools may not require the complete generated architecture.
For those cases, direct AmbitenClient usage may be more appropriate.
Best Practices
Keep ambiten.config.json as the primary runtime configuration surface for generated applications unless there is a deliberate reason to manage configuration programmatically.
Initialize the Ambiten runtime once during application startup.
Do not duplicate configuration-driven multi-tenancy with unnecessary manual registration.
Keep framework adapter setup in the real framework application entry point.
Use adapters for request-time tenant resolution.
Use MultiTenantManager for tenant infrastructure and dynamic discovery.
Use AmbitenContext for request- or execution-scoped identity.
Use public package exports instead of internal dist paths.
Keep shutdown logic at the application lifecycle boundary.
Allow the generated architecture to evolve intentionally rather than gradually scattering runtime initialization across unrelated files.
Troubleshooting
CLI Does Not Prompt
If interactive prompts do not appear:
- confirm that the terminal supports interactive input,
- confirm that supplied command flags are not bypassing the corresponding prompts.
Dependencies Are Missing
If dependencies were not installed during generation:
npm installOr generate the application with:
npx ambiten init my-app --installESM or CommonJS Errors
If module-resolution errors appear during startup, verify that the application uses public Ambiten package imports and that the project's TypeScript/Node module configuration matches the intended consumer format.
Avoid importing directly from:
dist/esm
dist/cjsAmbiten's package exports are responsible for selecting the correct runtime entry.
Tenant Is Not Resolved
If a request reaches the application without the expected tenant context, verify:
adapter installation
↓
tenant resolver configuration
↓
middleware/interceptor ordering
↓
request identityThen inspect:
AmbitenContext.get().tenantIdinside the active request.
For dynamic tenants, also verify that the configured TenantConfigResolver can resolve the requested tenant.
Summary
The Ambiten CLI scaffolds architecture rather than executing runtime behavior.
It creates a configuration-first project where bootstrap, framework adapters, runtime context, models, and optional infrastructure are organized around Ambiten's execution model.
The complete relationship is:
CLI
→ scaffolds
AmbitenBootstrapFactory
→ initializes
Framework Adapter
→ enters execution
AmbitenContext
→ carries request state
MultiTenantManager
→ manages tenant infrastructure
AmbitenModel
→ performs operationsThis allows generated applications to begin with a coherent runtime architecture while remaining free to evolve their framework, tenancy, infrastructure, and application logic independently.
