AmbitenClient
AmbitenClient is Ambiten's MongoDB client abstraction and primary database infrastructure surface.
It can be used directly:
Application
↓
AmbitenClient
↓
MongoDBas part of explicit runtime execution:
AmbitenContext
↓
context-aware client capability
↓
MongoDBor as the provider beneath AmbitenModel:
AmbitenContext
↓
AmbitenModel.mergeCtx()
↓
Effective ModelContext
↓
DbProvider
↓
AmbitenClient
↓
MongoDBThese are complementary usage styles.
A developer can begin with direct database access and introduce context, models, adapters, transactions, and multi-tenant infrastructure only when those capabilities become useful.
Direct when simplicity is enough. Context-aware when execution needs scope. Provider-capable when models need infrastructure.
What AmbitenClient Is
AmbitenClient stays close to the MongoDB driver while adding Ambiten's infrastructure and runtime capabilities around it.
Its public responsibilities include areas such as:
MongoDB connection lifecycle
database resolution
collection access
MongoClient access
session creation
tenant-aware provider resolution
database and tenant scoping
explicit runtime context
cluster inspection
database administration
collection administration
connection diagnosticsIt is intentionally usable without AmbitenModel.
Ambiten does not require an application to adopt the complete runtime architecture before useful MongoDB work can begin.
Progressive Usage
A simple application, script, or tutorial may begin with:
AmbitenClient
↓
MongoDBThen introduce explicit execution state:
AmbitenContext
↓
AmbitenClient runtime helpers
↓
MongoDBLater, schema-bound execution can be added:
AmbitenContext
↓
AmbitenModel
↓
ModelContext
↓
AmbitenClient
↓
MongoDBLarger applications can add:
Framework Adapters
MultiTenantManager
TenantConfigResolver
Transactions
Middleware
Instrumentation
Runtime orchestrationwithout replacing the same underlying client architecture.
This makes AmbitenClient appropriate for:
educational examples
live coding
YouTube tutorials
scripts
small applications
internal tools
production services
multi-tenant platformsCreating a Client
Create a client directly:
import {
AmbitenClient
} from "@ambiten/core";
const client =
new AmbitenClient({
uri: "mongodb://127.0.0.1:27017",
options: {
dbName: "my-app"
}
});A shared client can also be obtained through the static initializer:
const client =
AmbitenClient.init({
uri: "mongodb://127.0.0.1:27017",
options: {
dbName: "my-app"
}
});init() returns Ambiten's shared initialized client instance for that runtime.
For normal base-client database operations, establish the MongoDB connection before use:
await client.connect();Connection Lifecycle
A typical direct client lifecycle is:
const client =
new AmbitenClient({
uri: process.env.MONGODB_URI,
options: {
dbName: "app"
}
});
await client.connect();
const db =
await client.db();
const users =
await client.collection(
"users"
);The MongoDB client is reusable infrastructure.
Execution context is separate:
PROCESS / INFRASTRUCTURE
AmbitenClient
MongoClient
connection pool
configured database
EXECUTION
AmbitenContext
tenantId
requestId
dbName
collectionName
session
metadataLong-lived infrastructure can therefore be reused without storing request-specific execution state on the shared client.
Direct Client Usage
AmbitenClient does not require an AmbitenModel.
For example:
await client.connect();
const users =
await client.collection(
"users"
);This is useful when an application wants direct MongoDB access without introducing:
schema binding
model middleware
model lifecycle
model-level validation
collection-bound model definitionsDirect usage is a supported Ambiten execution style rather than an escape hatch around the runtime.
ModelContext
The client-facing provider methods use ModelContext.
export type ModelContext = {
tenantId?: string;
requestId?: string;
dbName?: string;
db?: Db;
collectionName?: string;
config?: AmbitenConfig;
session?: ClientSession;
withDeleted?: boolean;
onlyDeleted?: boolean;
hardDelete?: boolean;
};ModelContext is the model-operation-facing context contract shared across the model runtime.
It should not be confused with the full AmbitenContextState.
The two overlap intentionally.
AmbitenContextState
───────────────────
tenantId
requestId
dbName
collectionName
session
loggerMeta
logger
debug
meta
observer
budget
ModelContext
────────────
tenantId
requestId
dbName
db
collectionName
config
session
withDeleted
onlyDeleted
hardDeleteThe shared fields allow runtime execution state to participate in model and provider operations.
The additional fields serve their respective execution layers.
AmbitenContext and ModelContext
AmbitenContext carries full execution-scoped runtime state.
ModelContext is the operation-facing view consumed by model, schema, and provider behavior.
For normal model execution, the relationship is:
AmbitenContextState
↓
AmbitenContext.get()
↓
AmbitenModel.mergeCtx(...)
+
explicit operation ModelContext
+
model defaults
↓
Effective ModelContextThat effective context is then used throughout the model operation.
Effective ModelContext
├── Schema
├── Middleware
├── DbProvider
└── Model operationThis distinction is important when understanding how AmbitenClient participates in context-aware model execution.
AmbitenClient as a DbProvider
AmbitenClient satisfies the database-provider capabilities used by AmbitenModel.
export interface DbProvider {
db(
ctx?: ModelContext
): Promise<Db>;
client?(
ctx?: ModelContext
): Promise<MongoClient>;
startSession?(
ctx?: ModelContext
): Promise<ClientSession>;
}The model path is therefore:
AmbitenContext
↓
AmbitenModel.mergeCtx()
↓
Effective ModelContext
↓
AmbitenClient.db(ctx)
AmbitenClient.client(ctx)
AmbitenClient.startSession(ctx)
↓
MongoDBThe client does not need to reconstruct model-context precedence itself.
AmbitenModel performs that binding before requesting provider infrastructure.
Effective Model Context
The model runtime uses the precedence:
explicit operation context
↓
active AmbitenContext
↓
model defaultsConceptually:
await AmbitenContext.run(
{
tenantId: "tenant-a",
requestId: "req-123"
},
async () => {
await UserModel.find({});
}
);UserModel.find() can merge the ambient execution state into an effective ModelContext.
The provider then receives that resolved operation context:
tenantId = tenant-a
requestId = req-123
...
↓
AmbitenClient.db(ctx)This is the primary way ambient runtime state reaches AmbitenClient during model execution.
db(ctx?)
db() resolves a MongoDB database from the supplied ModelContext and client configuration.
const db =
await client.db();or:
const db =
await client.db({
dbName: "analytics"
});The database-resolution order is:
1. ctx.db
2. ctx.tenantId
→ tenant resolver
→ tenant client
→ ctx.dbName
or tenant database name
or default database
3. ctx.dbName
4. mutable useDatabase() override
5. configured/default databaseThis ordering makes explicit operation state authoritative while still supporting configured fallback behavior.
Explicit Database Instance
A ModelContext can provide an already resolved Db:
await client.db({
db: existingDb
});When ctx.db is present, it becomes the database for that operation.
This is the most explicit database resolution path.
Tenant-Aware db()
A tenant can also be supplied through ModelContext:
const db =
await client.db({
tenantId: "tenant-a"
});In this path, the client delegates tenant client resolution through its configured tenant resolver.
Conceptually:
ModelContext.tenantId
↓
tenantResolver.getClient(...)
↓
Tenant MongoClient
↓
resolved tenant databaseIf multi-tenancy has not been configured for the client, supplying a tenantId is considered an invalid resolution request.
Tenant Database Name Resolution
For tenant-aware database access, the effective database name can come from:
ctx.dbName
↓
registered tenant database name
↓
client defaultThis allows a tenant operation to retain explicit database override capability while still supporting registered tenant configuration.
client(ctx?)
client() returns the MongoDB client associated with the requested infrastructure scope.
const mongoClient =
await client.client();For tenant-aware execution:
const tenantMongoClient =
await client.client({
tenantId: "tenant-a"
});The tenant-aware path delegates client resolution through the configured tenant resolver.
This makes the provider contract capable of returning the correct MongoDB client for transaction/session infrastructure as well as ordinary database access.
collection(name, ctx?)
Collections can be accessed directly:
const users =
await client.collection(
"users"
);An explicit ModelContext can also be supplied:
const users =
await client.collection(
"users",
{
dbName: "analytics"
}
);collection() delegates database resolution to:
db(ctx)and then selects the requested collection from that resolved database.
Conceptually:
collection("users", ctx)
↓
db(ctx)
↓
resolved Db
↓
db.collection("users")getCollection()
getCollection() provides an alternative collection accessor:
const users =
await client.getCollection<User>(
"users",
ctx
);Where a normal MongoDB client is available, it delegates to collection().
It also contains defensive behavior used by environments where a real client may not yet be available, such as particular testing or structural initialization scenarios.
For ordinary application persistence, collection() remains the straightforward direct accessor.
Direct Context Awareness
There are two different ideas that should not be combined:
1. ModelContext-aware provider methods
2. Methods/helpers that read AmbitenContext directlyFor example:
client.db(ctx);
client.collection(name, ctx);
client.client(ctx);
client.startSession(ctx);consume ModelContext supplied to them.
They should not be described as if every one of those methods independently reconstructs the current AmbitenContext.
During model execution, AmbitenModel.mergeCtx() performs that binding first.
Ambient Runtime Context
Some client facilities do read AmbitenContext directly.
These include runtime helpers such as:
resolveRuntime()
resolveClientScope()
withContext()
context-enriched internal loggingThe distinction is:
PROVIDER PATH
AmbitenContext
↓
AmbitenModel.mergeCtx()
↓
ModelContext
↓
AmbitenClient
DIRECT AMBIENT PATH
AmbitenContext
↓
context-reading client helper
↓
MongoDB capabilityBoth are context-aware, but they arrive at context through different mechanisms.
withContext()
withContext() establishes an explicit AmbitenContext boundary:
await client.withContext(
{
tenantId: "tenant-a",
requestId: "req-123",
dbName: "tenant_a_db"
},
async () => {
// execution occurs inside AmbitenContext
}
);Conceptually:
client.withContext(...)
↓
AmbitenContext.run(...)
↓
callbackThe context can contain:
tenantId
requestId
dbName
collectionName
sessionwithContext() establishes the execution scope.
It does not mean that every AmbitenClient method automatically reads every ambient value.
Whether a direct client operation consumes ambient context depends on that method's implementation.
This keeps the execution-boundary API explicit and avoids implying behavior that belongs to AmbitenModel.mergeCtx() or other context-reading helpers.
resolveRuntime()
AmbitenClient.resolveRuntime() is an explicit ambient runtime helper.
It reads the active AmbitenContext:
AmbitenContext.get()and requires a resolved tenant identity.
Conceptually:
AmbitenContext
tenantId = tenant-a
session = S1
↓
AmbitenClient.resolveRuntime()
↓
MultiTenantManager.resolveTenant(...)
↓
TenantConfig
↓
Db + active sessionThe result contains:
db
sessionThis helper is useful when runtime infrastructure needs to be resolved directly from the active Ambiten execution rather than through an AmbitenModel.
Direct Runtime Resolution
A context-driven direct path can therefore look conceptually like:
await client.withContext(
{
tenantId: "tenant-a",
requestId: "req-123"
},
async () => {
const {
db,
session
} =
await AmbitenClient.resolveRuntime();
const users =
db.collection("users");
// direct MongoDB work
}
);This path intentionally uses ambient AmbitenContext.
It is different from passing a ModelContext directly to client.db(ctx).
Client Scope Resolution
Internally, AmbitenClient also resolves certain client-level scopes from a combination of:
explicit method input
↓
active AmbitenContext
↓
client configuration
↓
client defaultsThis scope can include:
tenantId
requestId
dbName
collectionName
sessionThe scope resolver does not mutate AmbitenContext or the client configuration.
It provides a deterministic view of the active client-level execution scope for methods that use it.
useCollection()
useCollection() resolves a collection using the client-level scope and stores the resulting collection reference on the client.
const users =
await client.useCollection(
"users"
);Because this method maintains collectionRef, it is stateful compared with the ordinary:
client.collection(...)accessor.
For normal concurrent application work, explicit collection(...) access or scoped provider APIs are generally easier to reason about.
useCollection() remains useful for controlled direct-client workflows and compatibility scenarios.
Scoped Clients
Ambiten supports scoped BootstrapClient views that pre-bind infrastructure values without mutating the base client's ordinary scope.
The available helpers include:
withTenant(...)
withDatabase(...)
withScope(...)Each scoped view merges its predefined scope with an optional operation ModelContext.
The general rule is:
explicit ModelContext value
↓
scoped valueSo explicit operation state can override the bound scope where supported.
withDatabase()
Create a database-scoped provider:
const reporting =
client.withDatabase(
"reporting"
);It can then be used directly:
const reports =
await reporting.collection(
"reports"
);Conceptually:
Base AmbitenClient
↓
withDatabase("reporting")
↓
BootstrapClient view
↓
dbName = explicit ctx.dbName
or "reporting"This is useful when a database boundary should be explicit without mutating the base client.
withTenant()
Create a tenant-scoped provider:
const tenantClient =
client.withTenant(
"tenant-a"
);Then:
const users =
await tenantClient.collection(
"users"
);Conceptually:
Base AmbitenClient
↓
withTenant("tenant-a")
↓
BootstrapClient view
↓
tenantId = explicit ctx.tenantId
or "tenant-a"The resulting provider delegates tenant-aware database and client resolution back to the base AmbitenClient.
This is especially useful for controlled execution such as:
background jobs
maintenance operations
tenant-specific scripts
migrations
administrative workflows
testswithScope()
A combined tenant/database scope can be created through:
const scoped =
client.withScope({
tenantId: "tenant-a",
dbName: "reporting"
});Conceptually:
withScope({
tenantId,
dbName
})
↓
BootstrapClient view
↓
explicit ModelContext
overrides scoped values
where suppliedThis provides an explicit infrastructure view without redefining the base client.
Scoped Clients Are Views, Not Independent MongoClients
The scope helpers should be understood as lightweight provider views over the base client.
They delegate capabilities such as:
db()
client()
collection()
startSession()
connect()
close()back to the original AmbitenClient while merging their predefined scope.
Conceptually:
Base AmbitenClient
│
├── withTenant(...)
├── withDatabase(...)
└── withScope(...)
↓
scoped provider viewThey are not unrelated MongoDB connection pools created for every scope.
Mutable Database Switching
useDatabase() provides a mutable database override:
await client.useDatabase(
"reporting"
);This sets the client's mutable database override.
The resolution order then allows that value to participate after explicit ctx.dbName and before the default database.
Because the override mutates shared client state, it should be used carefully in concurrent or request-driven environments.
Prefer:
const reporting =
client.withDatabase(
"reporting"
);for request-safe scoped execution.
useDatabase() remains useful in controlled scenarios such as:
scripts
CLI utilities
migrations
interactive examples
single-purpose workflowsResetting the Mutable Database Override
A mutable database override can be cleared with:
client.resetDatabase();After reset, database resolution falls back to the remaining resolution rules such as explicit context or configured defaults.
Sessions
AmbitenClient provides direct session creation:
const session =
await client.startSession();A ModelContext can also be supplied:
const session =
await client.startSession({
tenantId: "tenant-a"
});Internally:
startSession(ctx)
↓
client(ctx)
↓
resolved MongoClient
↓
MongoClient.startSession()This means tenant-aware session creation uses the same client-resolution contract as tenant-aware database access.
Transactions
Direct session access is useful when explicit MongoDB session control is desired.
For runtime-managed transactions, Ambiten can instead establish a transaction boundary and propagate the active session through execution context.
await AmbitenContext.withTransaction(
async () => {
await UserModel.create(
user
);
await AuditModel.create(
audit
);
}
);For the model path:
Transaction Boundary
↓
AmbitenContext.session
↓
AmbitenModel.mergeCtx()
↓
ModelContext.session
↓
participating model operationsThe model/provider operation participates in the active transaction.
The enclosing transaction boundary owns commit and rollback.
Tenant Identity vs Tenant Infrastructure
AmbitenClient participates in tenant-aware infrastructure, but it should not be described as the owner of the entire tenant runtime.
The architectural responsibilities remain:
TenantResolver
→ identifies tenant identity
AmbitenContext
→ carries tenant identity
AmbitenModel
→ derives effective ModelContext
MultiTenantManager
→ owns registered tenant infrastructure
AmbitenClient
→ provides database/client/session capabilityThe exact path differs between model execution, scoped client execution, and direct runtime helpers.
Instance Tenant Resolution
For provider methods such as:
db(ctx)
client(ctx)tenant-aware resolution uses the tenantResolver configured on the AmbitenClient.
Conceptually:
ModelContext.tenantId
↓
configured tenantResolver
↓
getClient(tenantId)
↓
tenant MongoClientDatabase resolution can additionally use the tenant database registered with MultiTenantManager.
This keeps client resolution extensible while allowing Ambiten's multi-tenant runtime to supply the underlying tenant infrastructure.
MultiTenantManager Relationship
For the broader Ambiten runtime:
AmbitenContext
tenantId
↓
AmbitenModel
↓
effective ModelContext
↓
AmbitenClient / tenant resolver
↓
MultiTenantManager-managed resources
↓
MongoDBMultiTenantManager owns concerns such as:
tenant registration
tenant configuration
dynamic tenant discovery
lazy tenant activation
tenant client lifecycle
runtime tenant registryAmbitenClient remains the MongoDB-facing capability within that architecture.
Dynamic Tenants
Dynamic tenant discovery is not performed by every ordinary direct client method.
It belongs to the multi-tenant runtime.
Conceptually:
tenantId
↓
MultiTenantManager.resolveTenant()
↓
registered?
│
├── yes → use tenant
│
└── no
↓
TenantConfigResolver
↓
register tenant
↓
getClient()Once the tenant infrastructure exists, AmbitenClient capabilities can participate against the resulting MongoDB client and database.
Static API
AmbitenClient also provides static runtime facilities.
init()
const client =
AmbitenClient.init({
uri,
options: {
dbName
}
});Returns the shared Ambiten client instance for the runtime.
db(ctx?)
A static database accessor is also available:
const db =
await AmbitenClient.db(ctx);It delegates to the initialized client:
AmbitenClient.init()
↓
client.db(ctx)The same connection and context requirements of the underlying client still apply.
resolveRuntime()
const runtime =
await AmbitenClient.resolveRuntime();This is the explicit ambient tenant-runtime resolver described earlier.
It reads AmbitenContext, resolves the tenant through MultiTenantManager, and returns the resolved database and active session.
Topology Helpers
The client also exposes static topology helpers:
handleTopologyEvent(...)
handleLogBatch(...)These support MongoDB topology event handling and runtime-aware operational logging.
They are infrastructure/observability utilities rather than ordinary persistence APIs.
Operational Capabilities
AmbitenClient includes several lower-level operational methods.
These include:
getClusterInfo()
dropCollection()
dropDatabase()
close()
disconnect()
isConnected()Cluster Inspection
const info =
await client.getClusterInfo();The client can distinguish:
standalone
replica set
sharded clusterbased on MongoDB topology information.
Drop a Collection
await client.dropCollection(
"temporary-data"
);An optional ModelContext can participate in database selection.
Drop a Database
await client.dropDatabase();or with an explicit context:
await client.dropDatabase({
dbName: "temporary"
});These are administrative operations and should be treated accordingly.
Closing the Client
Close the underlying MongoDB client with:
await client.close();or:
await client.disconnect();The client resets its internal connection state when closed.
Connection status can be inspected with:
client.isConnected();Close to MongoDB by Design
Ambiten intentionally preserves MongoDB concepts.
Developers can still reason directly about:
MongoClient
Db
Collection
ClientSession
database names
collection names
MongoDB topology
MongoDB operationsAmbiten adds:
runtime context
provider contracts
scoped infrastructure
tenant-aware resolution
transaction propagation
execution boundariesaround those concepts.
It does not attempt to replace MongoDB with an unrelated persistence vocabulary.
AmbitenClient vs AmbitenModel
The two APIs serve different abstraction levels.
Direct Client
Application
↓
AmbitenClient
↓
MongoDBUseful for:
tutorials
scripts
direct database work
small services
migrations
diagnostics
administrative toolsModel Runtime
Application
↓
AmbitenModel
↓
Effective ModelContext
↓
DbProvider
↓
AmbitenClient
↓
MongoDBThe model adds:
schema behavior
validation
middleware
collection ownership
effective-context resolution
operation lifecycle
model policiesNeither usage style invalidates the other.
AmbitenClient and AmbitenContext
The relationship depends on the execution path.
Model Path
AmbitenContext
↓
AmbitenModel.mergeCtx()
↓
ModelContext
↓
AmbitenClientHere the model performs the context binding.
Explicit ModelContext Path
Application
↓
ModelContext
↓
AmbitenClientHere the caller supplies the required operation scope directly.
Ambient Helper Path
AmbitenContext
↓
resolveRuntime()
or another context-reading helper
↓
AmbitenClient runtime capabilityHere the client helper reads ambient context directly.
These distinctions prevent the term "context-aware" from becoming ambiguous.
Framework Independence
AmbitenClient has no requirement to run behind one particular framework.
It can participate in:
Script ─────────┐
Worker ─────────┤
Express ────────┤
Fastify ────────┤
NestJS ─────────┤
GraphQL ────────┤
Lambda ─────────┘
↓
AmbitenClientFramework adapters can establish execution boundaries where appropriate.
Manual workflows can use:
explicit ModelContext
scoped clients
AmbitenContext.run(...)
client.withContext(...)depending on the desired execution style.
Bootstrap Relationship
AmbitenBootstrapFactory can prepare process-level runtime infrastructure that includes an AmbitenClient.
Conceptually:
AmbitenBootstrapFactory
↓
AmbitenRuntime
↓
AmbitenClientBootstrap prepares infrastructure.
It does not establish each request or job execution context.
Execution begins later through:
framework adapteror:
AmbitenContext.run(...)or another explicit execution boundary.
Process Lifetime vs Execution Lifetime
The client architecture follows the same lifetime separation used throughout Ambiten.
PROCESS LIFETIME
────────────────
AmbitenClient
MongoClient
connection pool
MultiTenantManager
provider configuration
runtime configurationEXECUTION LIFETIME
──────────────────
AmbitenContextState
tenantId
requestId
dbName
collectionName
session
runtime metadataAnd within model execution:
OPERATION LIFETIME
──────────────────
Effective ModelContext
operation overrides
soft-delete controls
model-specific configurationKeeping those lifetimes separate prevents execution state from leaking into reusable process infrastructure.
Common Usage Patterns
Simple Direct Client
const client =
new AmbitenClient({
uri,
options: {
dbName: "app"
}
});
await client.connect();
const users =
await client.collection(
"users"
);Explicit Database Context
const events =
await client.collection(
"events",
{
dbName: "analytics"
}
);Tenant Context
const users =
await client.collection(
"users",
{
tenantId: "tenant-a"
}
);This requires tenant resolution to be configured for the client.
Database Scope
const reporting =
client.withDatabase(
"reporting"
);
const reports =
await reporting.collection(
"reports"
);Tenant Scope
const tenant =
client.withTenant(
"tenant-a"
);
const users =
await tenant.collection(
"users"
);Combined Scope
const scoped =
client.withScope({
tenantId: "tenant-a",
dbName: "reporting"
});Model Provider
const UserModel =
new AmbitenModel({
collectionName: "users",
schema: userSchema,
provider: client
});Direct Session
const session =
await client.startSession();Tenant Session
const session =
await client.startSession({
tenantId: "tenant-a"
});When to Use AmbitenClient Directly
Direct client usage is a good fit when:
you are learning Ambiten
you are teaching Ambiten
you are creating a live coding example
the application is small
the workflow is database-oriented
you need direct collection access
you are writing a script
you are building a migration
you are performing diagnostics
a model abstraction would add no value yetThere is no requirement to introduce AmbitenModel merely to make an application "properly Ambiten."
When to Introduce AmbitenModel
AmbitenModel becomes useful when the application benefits from:
schema-bound execution
typed model operations
validation
middleware
collection ownership
effective ModelContext resolution
soft-delete behavior
consistent model policies
operation lifecycleA natural progression is:
AmbitenClient
↓
AmbitenContext
↓
AmbitenSchema
↓
AmbitenModel
↓
Adapters
↓
Multi-Tenancy / TransactionsApplications can adopt whichever level matches their requirements.
What AmbitenClient Does Not Own
AmbitenClient does not own:
HTTP routing
framework request lifecycle
application authentication
tenant authorization
schema definition
model middleware orchestration
application workflows
global dynamic-tenant policyIt also does not replace AmbitenModel.mergeCtx() in the model runtime.
The model remains responsible for deriving the effective operation context before provider execution.
Design Philosophy
AmbitenClient is intentionally accessible.
Ambiten's architecture supports sophisticated multi-tenant and transaction-aware systems, but a developer should not need to understand that entire architecture before performing useful MongoDB work.
The client therefore supports both:
APPROACHABLE USAGE
direct database access
direct collection access
explicit scopes
small runtime helpersand:
ADVANCED USAGE
DbProvider integration
ModelContext
tenant-aware clients
transaction sessions
MultiTenantManager integration
runtime executionThe same underlying client can grow with the application.
Runtime Relationship
The exact path depends on how the client is used.
Direct
Application
↓
AmbitenClient
↓
MongoDBExplicit Context
Application
↓
ModelContext
↓
AmbitenClient
↓
MongoDBModel Runtime
AmbitenContext
↓
AmbitenModel.mergeCtx()
↓
Effective ModelContext
↓
AmbitenClient
↓
MongoDBAmbient Tenant Runtime
AmbitenContext
tenantId
↓
AmbitenClient.resolveRuntime()
↓
MultiTenantManager
↓
Tenant DatabaseScoped Provider
AmbitenClient
↓
withTenant()
withDatabase()
withScope()
↓
BootstrapClient view
↓
AmbitenClient
↓
MongoDBMental Model
The simplest mental model is:
AmbitenClient
= MongoDB capability with Ambiten runtime support.For direct execution:
Explicit scope
→ AmbitenClient
→ MongoDBFor model execution:
AmbitenContext
→ AmbitenModel.mergeCtx()
→ ModelContext
→ AmbitenClient
→ MongoDBFor tenant infrastructure:
tenantId
→ tenant resolver / MultiTenantManager
→ tenant MongoClient
→ AmbitenClient capability
→ MongoDBAnd the key contract distinction is:
AmbitenContextState
= full execution state
ModelContext
= model-operation-facing state
AmbitenClient
= database/client/session capability
AmbitenModel
= binds execution state to model operationsSummary
AmbitenClient is Ambiten's MongoDB client abstraction and primary database infrastructure surface.
It supports:
- direct database access,
- direct collection access,
- explicit
ModelContext, - database and tenant scopes,
- MongoDB client access,
- session creation,
- ambient runtime helpers,
- provider integration,
- tenant-aware resolution,
- operational tooling,
- connection lifecycle management.
It can be used directly:
Application
↓
AmbitenClient
↓
MongoDBwith explicit operation context:
Application
↓
ModelContext
↓
AmbitenClient
↓
MongoDBor beneath AmbitenModel:
AmbitenContext
↓
AmbitenModel.mergeCtx()
↓
Effective ModelContext
↓
DbProvider
↓
AmbitenClient
↓
MongoDBThe distinction matters:
AmbitenModelbinds runtime state into model execution.ModelContextcarries the persistence-facing operation state.AmbitenClientprovides the MongoDB infrastructure required to execute it.
For developers who do not yet need models, adapters, or multi-tenancy, the client remains useful on its own.
For larger systems, the same client becomes part of Ambiten's wider context-aware runtime.
Start directly. Add structure when the application needs it.
