Ambiten Core v1.3.0 adds a practical way to keep bootstrap configuration manageable: reference schema definitions, model options, and tenant resolver functions from application modules.
It also introduces declarative runtime defaults and restores preparation of configured log files during startup.
This walkthrough shows how the configuration pieces fit together.
1. Install the release
In your application:
pnpm add @ambiten/core@1.3.0
Use a Node.js version compatible with both Core and your MongoDB driver. Core v1.3.0 declares Node.js >=20.11.0 and a MongoDB peer range of ^5 || ^7.
The examples use native .mjs modules. Bootstrap initialization requires a running MongoDB server.
We will use these files:
| File | Purpose |
|---|---|
ambiten.config.json |
Connects the configuration pieces |
src/schemas/user.schema.mjs |
Exports the schema definition |
src/models/user.model.mjs |
Exports model options |
src/tenantResolver.mjs |
Resolves tenant database configuration |
2. Export a schema definition
Create src/schemas/user.schema.mjs:
export const userSchema = {
tenantId: {
type: "string",
required: true,
default: {
$runtime: "context",
key: "tenantId"
}
},
publicId: {
type: "string",
default: {
$runtime: "uuid"
}
},
username: {
type: "string",
required: true
},
email: {
type: "string",
required: true
},
createdAt: {
type: "date",
required: true,
default: {
$runtime: "now"
}
},
isDeleted: {
type: "boolean",
default: false
},
deletedAt: {
type: "date"
}
};
The export is a schema definition object. Bootstrap constructs the schema instance from it.
The three runtime defaults have different jobs:
| Directive | Resolved value |
|---|---|
{ $runtime: "now" } |
A new Date
|
{ $runtime: "uuid" } |
A generated UUID |
{ $runtime: "context", key: "tenantId" } |
The active context’s tenant identifier |
Defaults apply to fields whose values are undefined.
Notice that deletedAt has no default. A new document starts with isDeleted: false and no deletion timestamp. A deletion workflow can assign a valid date later.
3. Export model options
Create src/models/user.model.mjs:
export const userModelOptions = {
collectionName: "users"
};
This export supplies the model options. Bootstrap combines them with the loaded schema and provider when constructing the model.
For this configuration path, export the options object rather than constructing a model inside the module.
4. Export a tenant configuration resolver
Create src/tenantResolver.mjs:
const tenantConfigs = new Map([
[
"tenant-a",
{
uri: "mongodb://localhost:27017/tenant_a",
dbName: "tenant_a"
}
],
[
"tenant-b",
{
uri: "mongodb://localhost:27017/tenant_b",
dbName: "tenant_b"
}
]
]);
export function resolveTenantConfig(tenantId) {
return tenantConfigs.get(tenantId);
}
These local connection strings make the example easy to follow.
In an application, the resolver can obtain configuration from an environment-backed mapping, control database, configuration API, or another trusted source. The function can be asynchronous.
The resolver contract accepts a tenant identifier and returns:
{
uri: string;
dbName?: string;
metadata?: Record<string, unknown>;
}
Returning undefined or null means the tenant could not be resolved.
The resolver supplies configuration. MultiTenantManager owns tenant registration and client state.
5. Connect the modules in JSON
Create ambiten.config.json in the project root:
{
"configVersion": "1.0.0",
"projectName": "ambiten-v130-demo",
"connection": {
"uri": "mongodb://localhost:27017/ambiten_demo",
"options": {
"dbName": "ambiten_demo"
}
},
"schema": {
"path": "./src/schemas/user.schema.mjs",
"exportName": "userSchema"
},
"model": {
"path": "./src/models/user.model.mjs",
"exportName": "userModelOptions"
},
"multiTenant": {
"enabled": true,
"tenants": {},
"tenantConfigResolverModule": {
"path": "./src/tenantResolver.mjs",
"exportName": "resolveTenantConfig"
},
"initOptions": {
"lazy": true
}
},
"logger": {
"enabled": true,
"level": "info",
"transportConfigs": [
{
"type": "console",
"options": {}
},
{
"type": "rotating-file",
"options": {
"filename": "./logs/ambiten.log"
}
}
]
}
}
A few details matter here:
-
configVersionidentifies the configuration format; it is independent of the Core package version. - Module paths in JSON resolve relative to the configuration file.
- Omitting
exportNameselects the module’s default export. - An empty
tenantsobject is valid when an external tenant resolver is configured. - Bootstrap still initializes the base connection. Lazy tenant registration does not remove that startup connection.
The features.models and features.schemas properties remain scaffolding metadata. Runtime loading uses the explicit model and schema references.
6. Initialize once and use the loaded model
Within a long-running application, initialize the runtime once and reuse it:
import {
AmbitenBootstrapFactory,
AmbitenContext
} from "@ambiten/core";
const runtime = await AmbitenBootstrapFactory.create({
config: "./ambiten.config.json"
});
const UserModel = runtime.getModel();
At an independent execution boundary, an operation can establish its tenant context explicitly:
const user = await AmbitenContext.run(
{
tenantId: "tenant-a",
logger: runtime.getLogger()
},
async () => {
return UserModel.create({
username: "Alice",
email: "alice@example.com"
});
}
);
For a successful creation, the schema supplies the missing tenant identifier, public UUID, creation timestamp, and isDeleted value.
The tenant resolver provides the connection configuration for tenant-a when that tenant is needed.
In an HTTP application, a framework adapter can establish the request context before the handler runs. The bootstrap configuration itself does not install HTTP middleware or start a server.
Use a tenant identifier that the application has authenticated and authorized. A tenant default fills a missing document field; it does not validate a caller’s right to access that tenant or overwrite a tenant value supplied in input.
For server applications, coordinate HTTP or worker shutdown and tenant client cleanup with the runtime’s lifecycle.
7. Understand when defaults run
Runtime defaults resolve when schema validation applies them to a missing field. They are not evaluated once and shared across all documents.
The supported context keys are:
tenantId
requestId
dbName
collectionName
If a requested context value is unavailable, applying that default fails.
Other useful behaviors:
- Unsupported runtime directives are rejected.
- Defaults apply only to
undefinedvalues. - Explicit values remain subject to field validation.
- Object and array defaults are copied per document.
- Date fields require valid
Datevalues when supplied.
Use literal JSON-compatible defaults or supported $runtime descriptors in both inline schemas and exported schema definitions.
8. Configure file logging explicitly
The example includes a rotating-file transport targeting:
./logs/ambiten.log
For declarative file transports, bootstrap creates missing parent directories and prepares the destination file during logger initialization.
Relative log paths resolve from the process working directory. Existing file contents are preserved.
When logging is enabled and no transports are supplied, bootstrap uses its default console and rotating-file transports. An explicit console-only runtime configuration remains console-only.
Applications supplying their own logger instance or executable transports manage those transports themselves.
9. Using TypeScript modules
You can reference TypeScript files instead of .mjs files, provided the application process supports loading them through native imports.
For example, with tsx installed:
node --import tsx src/main.ts
Then a module reference can use:
{
"schema": {
"path": "./src/schemas/user.schema.ts",
"exportName": "userSchema"
}
}
A CommonJS require hook alone is insufficient for this native import path.
For compiled deployments, update configuration references to the emitted JavaScript files and ensure those files are deployed. TypeScript compilation does not rewrite module paths inside your JSON configuration.
Other changes in v1.3.0
The release also includes:
- ESM reference fixes for Node.js module resolution.
- CommonJS and ESM package integration fixes across Core, adapter types, adapter runtime, and the Express adapter.
- Context propagation checks across those package boundaries.
- Browser bundle build fixes involving Node.js imports.
- More reliable Windows invocation of pnpm in build and integration scripts.
- Scaffold summaries that understand the revised model configuration.
- Generated applications requesting
@ambiten/corethrough the registry’slatesttag.
The initial installation resolves that tag. The resulting lockfile records the selected dependency version.
Scaffold compatibility note: the v1.3.0 template generates MongoDB ^6.14.2, while Core declares ^5 || ^7. Align the generated driver dependency with Core’s peer range and the selected driver’s Node.js requirements.
Explore the release
Inline configuration remains supported. External modules give larger applications a way to organize definitions while continuing to use the same bootstrap workflow.
Top comments (0)