@x12i/xronox-core
v2.7.3
Published
Core operations for Xronox including CRUD, versioning, transactions, and entity relationships with Poiesis Foundation Rules 25, 26, 27 compliance
Maintainers
Readme
@x12i/xronox-core
Version: 2.5.0
Status: Active
Expertise: Core CRUD operations with storage externalization
🚀 Env-Ready Component (ERC 2.0)
This component supports zero-config initialization via environment variables using @x12i/env. ERC 2.0 compliant with automatic manifest generation and transitive dependency documentation.
Core database operations for the Xronox ecosystem, providing unified CRUD operations with storage externalization, versioning support, and comprehensive audit trails.
Why This Package Exists
The Xronox ecosystem needed a unified, robust database operations layer that could handle complex data management requirements with storage externalization for large binary data.
Before this package:
- Each application implemented CRUD operations separately
- No standardized storage externalization
- Inconsistent error handling and validation
- No unified approach to versioning and audit trails
Now:
- Single, expert CRUD implementation used by all Xronox applications
- Automatic storage externalization for base64/binary data
- Consistent error handling with detailed context
- Built-in versioning and audit trails
- Config-driven transaction support
- Lazy collection creation - Collections created automatically on first write
- Natural head collection UX - Work with
*-headcollections like normal MongoDB collections
Installation
npm install @x12i/xronox-corePeer Dependencies
npm install @x12i/xronox-router @x12i/xronox-storage-interfaceQuick Start (Zero-Config Mode)
xronox-core supports zero-config initialization via environment variables:
# 1. Install the package
npm install @x12i/xronox-core
# 2. Copy .env.example to .env (auto-generated in node_modules/@x12i/xronox-core/)
cp node_modules/@x12i/xronox-core/.env.example .env
# 3. Fill in required values in .env
# MONGO_URI=mongodb://localhost:27017
# MONGO_DB=xronox_demo
# SPACE_ENDPOINT=https://s3.amazonaws.com
# SPACE_BUCKET=my-bucket
# SPACE_ACCESS_KEY=your-key
# SPACE_SECRET_KEY=your-secret
# 4. Use with zero config!
import { createSingleXronox } from '@x12i/xronox-core';
const router = createSingleXronox(); // Auto-discovers from process.envConfiguration Modes
xronox-core auto-detects the configuration mode:
Simple Mode (Zero-Config):
- No
xronox.config.jsonfile exists - Uses essential environment variables only
- Perfect for simple deployments
Complex Mode (Advanced):
xronox.config.jsonfile exists in project root- Supports full configuration with routing, multi-tenant, collection maps, etc.
- Still generates ERC manifest for documentation
Both modes are ERC 2.0 compliant and generate erc-manifest.json and .env.example automatically.
Environment Variables
See .env.example for the complete list of required and optional variables with descriptions.
Essential Variables (Simple Mode):
MONGO_URI- MongoDB connection URI (required)MONGO_DB- Logical database name (default: xronox_demo)SPACE_ENDPOINT- S3-compatible storage endpoint (required)SPACE_BUCKET- Storage bucket name (required)SPACE_ACCESS_KEY- Storage access key (required)SPACE_SECRET_KEY- Storage secret key (required)
Optional Variables:
SPACE_REGION- Storage region (default: us-east-1)DB_TRANSACTIONS_ENABLED- Enable transactions (auto-detected if unset)LOG_LEVEL- Stored in global config when present (default: info); does not control the library logger — useXRONOX_CORE_LOGS_LEVELbelowLOG_FORMAT- Log format: text, json, yaml (default: text)VERSIONING_ENABLED- Enable versioning (default: true)LOGICAL_DELETE_ENABLED- Enable soft delete (default: true)MONGO_MAPPED_ONLY- Store only mapped properties (default: false)ENABLE_XRONOX_CORE_LOGXER- Master switch for this package’s logxer (default: when unset, treated as false). See Logging below.
Logging (@x12i/logxer)
ENABLE_XRONOX_CORE_LOGXER (optional, default false in .env / resolved config)
- Set to
true(e.g.ENABLE_XRONOX_CORE_LOGXER=true) to enable full logxer behavior for the main library andcreateSingleXronox: per-prefix levels and defaults below apply. - When false or omitted, log level is forced to error only (only
errorlines are emitted; notoff/ silent). - The same variable name is used for both log surfaces so behavior stays consistent.
Per-package verbosity (when the master switch is on) uses a stable prefix and is implemented by @x12i/logxer.
| Surface | Prefix | Canonical env | Legacy (if canonical unset) |
|--------|--------|---------------|-----------------------------|
| Main library logger | XRONOX_CORE | XRONOX_CORE_LOGS_LEVEL | XRONOX_CORE_LOG_LEVEL |
| createSingleXronox | XRONOX_SINGLE | XRONOX_SINGLE_LOGS_LEVEL | XRONOX_SINGLE_LOG_LEVEL |
- Default when the master switch is on and both canonical and legacy are unset:
warn(warn and error only), not info. - Silence this package’s diagnostics: set canonical to
off,none, orsilent. - More detail: e.g.
XRONOX_CORE_LOGS_LEVEL=infoordebug.
Cross-cutting options (where logs go, format, file, unified sink) stay host-level — this repo wires LOG_FORMAT, LOG_TO_FILE, LOG_FILE, LOG_TO_UNIFIED, DEBUG through @x12i/env into the logger configuration.
ERC 2.0 Compliance
- ✅ Auto-discovers configuration from environment variables
- ✅ Type-safe with automatic coercion and validation
- ✅ Transitive requirements automatically documented
- ✅ All dependencies are ERC 2.0-compliant
- ✅ Automatic manifest and
.env.examplegeneration
Dependencies:
- ✅
@x12i/xronox-router(ERC 2.0) - ✅
@x12i/xronox-storage-interface(ERC 2.0) - ✅
@x12i/xronox-storage-s3(ERC 2.0) - ✅
@x12i/logxer(ERC 2.0) - ✅
@x12i/env(Configuration engine)
Verify ERC Compliance
# Run ERC functionality tests
npm run erc:test
# Verify ERC 2.0 compliance (generates manifest and verifies)
npm run erc:verify
# Generate documentation
npm run erc:docsTest Coverage:
- ✅ ERC manifest file exists and has correct structure
- ✅
.env.examplefile is generated - ✅ Configuration initializes in both simple and complex modes
- ✅ ERC dependencies are correctly listed
- ✅ ERC validation works correctly
Advanced Mode (Complex Configuration)
For advanced scenarios (multi-tenant, multi-region, routing), create xronox.config.json in your project root. The component will automatically detect and use it (Complex Mode).
Use Complex Mode when:
- Your data needs routing (multi-tenant, multi-region, sharding)
- You have multiple MongoDB backends
- Routing decisions are required based on context (tenantId, region, etc.)
- You need per-collection feature configuration
Use Simple Mode (Zero-Config) when:
- Your data doesn't need routing (single backend is sufficient)
- You have a single MongoDB instance and S3 bucket
- No multi-tenant or multi-region requirements
- You want the simplest possible setup
Example xronox.config.json (Complex Mode):
{
"database": {
"uri": "ENV.MONGO_URI",
"dbName": "ENV.MONGO_DB||xronox_demo",
"transactions": { "enabled": "ENV.DB_TRANSACTIONS_ENABLED:boolean" }
},
"storage": {
"endpoint": "ENV.SPACE_ENDPOINT",
"region": "ENV.SPACE_REGION||us-east-1",
"bucket": "ENV.SPACE_BUCKET",
"accessKey": "ENV.SPACE_ACCESS_KEY",
"secretKey": "ENV.SPACE_SECRET_KEY"
},
"logging": {
"level": "ENV.LOG_LEVEL||info",
"format": "ENV.LOG_FORMAT||text",
"toFile": "ENV.LOG_TO_FILE:boolean||false",
"filePath": "ENV.LOG_FILE||",
"toUnified": "ENV.LOG_TO_UNIFIED:boolean||false",
"debugNamespace": "ENV.DEBUG||"
},
"enableXronoxCoreLogxer": "ENV.ENABLE_XRONOX_CORE_LOGXER:boolean||false"
}Create instances (one per database)
import { createItem, createSingleXronox } from '@x12i/xronox-core';
import type { RouteContext } from '@x12i/xronox-router';
const main = createSingleXronox();
const archive = createSingleXronox({ overrideDbName: 'xronox_demo_archive' });
const ctxUsers: RouteContext = { collection: 'users', dbName: 'xronox_demo', databaseType: 'runtime' };
const ctxOrders: RouteContext = { collection: 'orders', dbName: 'xronox_demo_archive', databaseType: 'runtime' };
await createItem(main, ctxUsers, { name: 'Alice' }, { jobId: 'job-1' });
await createItem(archive, ctxOrders, { title: 'Order-001' }, { jobId: 'job-2' });Notes
- Simple mode is for non-routable data - Single backend, no routing decisions. All operations go to the same MongoDB.
- ENV is read via
@x12i/env; no directprocess.envaccess in application code. - Logging uses
@x12i/logxerwith ENV-configured level/format whenENABLE_XRONOX_CORE_LOGXER=true; otherwise level is error-only. - Storage is required by design (JSON is written there).
- You can create multiple "single" instances safely; each handles exactly one database and one storage bucket.
- For routable data (multi-tenant, multi-region), use Router Mode with
BridgeRouterfrom@x12i/xronox-router.
Core Features (Implemented & Tested)
CRUD Operations
- Create - Insert new items with automatic versioning
- Update - Modify existing items with optimistic locking
- Delete - Logical delete (soft delete) preserving audit trail
- Restore - Restore logically deleted items to previous versions
- Fetch - Retrieve complete records from storage
Storage Externalization
- Automatic externalization of base64/binary fields to S3-compatible storage
- Full record assembly on fetch from Mongo head + storage objects
- Per-key storage mirroring in demos for verification
Configuration-Driven Behavior
- Transactions - Enable/disable via
database.transactions.enabled(auto-detected for replica sets) - Mapped-only mode - Toggle via
mongoMapping.mappedOnlyto store only mapped properties in Mongo - DB name separation - Logical database name always from config, never from connection URI
- Lazy collection creation - Backing collections (versions, counter) created automatically on first write
- Per-collection features - Configure versioning, storage, shadow collections per collection via
collectionMaps
Natural Head Collection UX
- Works like normal MongoDB -
*-headcollections behave as primary collections - No pre-creation required - Start using collections immediately, backing collections created lazily
- Graceful degradation - Read operations work even when backing collections don't exist
- Backwards compatible - Supports both
entitiesandentities_headcollection name formats
How It Works:
Lazy collection creation works seamlessly in both Simple Mode (non-routable) and Router Mode (routable):
Simple Mode (createSingleXronox) - Non-Routable Data:
import { createItem, createSingleXronox } from '@x12i/xronox-core';
// Simple mode = single backend, no routing decisions
// Use for data that doesn't need routing (single MongoDB + S3 backend)
const router = createSingleXronox(); // Fixed: one MongoDB URI, one S3 bucket
const ctx: RouteContext = {
collection: 'users', // or 'users_head' - both work!
dbName: 'my-db',
databaseType: 'runtime'
};
// First write - collections created automatically on the single backend
await createItem(router, ctx, { name: 'John' }, { actor: 'system' });
// ✅ Creates: users_head, users_ver, users_counter (if versioning enabled)
// ✅ Always writes to the same MongoDB (no routing logic)
// ✅ Works even if collections didn't exist beforeRouter Mode (xronox-router) - Routable Data:
import { createItem } from '@x12i/xronox-core';
import { BridgeRouter } from '@x12i/xronox-router';
// Router mode = multiple backends, routing decisions
// Use for data that needs routing (multi-tenant, multi-region, etc.)
const router = new BridgeRouter(/* router config with multiple backends */);
const ctx: RouteContext = {
collection: 'entities', // or 'entities_head' - both work!
dbName: 'runtime-db',
databaseType: 'runtime',
tenantId: 'tenant-a' // Router uses this to decide which backend
};
// First write - router decides backend, then creates collections there
await createItem(router, ctx, { data: 'value' }, { actor: 'user' });
// ✅ Router selects backend based on tenantId/context
// ✅ Creates collections on the routed backend
// ✅ Respects per-collection feature configurationKey Distinction:
- Simple Mode: Non-routable data → single backend, no routing decisions, always same MongoDB
- Router Mode: Routable data → multiple backends, routing decisions, backend selected by context
Key Points:
- No setup required - Just start using collections, they're created on first write
- Configuration-driven - Features (versioning, storage) enabled/disabled per collection
- Backwards compatible - Existing code with
entities_headformat continues to work - Works everywhere - Simple mode, router mode, multi-tenant, all scenarios
How Lazy Creation Works:
On First Write:
createItem(),updateItem(), ordeleteItem()is called- Code checks if backing collections exist (versions, counter)
- If missing and enabled in config → creates them automatically
- Then proceeds with the write operation
On Read:
- Head collection queries work normally (MongoDB creates collection on first write)
- Version queries return empty/null if versions collection doesn't exist (graceful)
- No errors, no collection creation on read (unless
createOnRead: truein config)
Collection Naming:
- Pass
'entities'→ createsentities_head,entities_ver,entities_counter - Pass
'entities_head'→ strips suffix, creates same collections (backwards compatible) - No double suffixes, no manual renaming needed
- Pass
Example: Simple Collection (No Versioning)
// xronox.config.json
{
"collectionMaps": {
"simple_data": {
"features": {
"versioning": { "enabled": false }
}
}
}
}
// Usage
const ctx: RouteContext = {
collection: 'simple_data',
dbName: 'my-db',
databaseType: 'runtime'
};
await createItem(router, ctx, { name: 'Test' }, { actor: 'user' });
// ✅ Creates: simple_data_head, simple_data_counter
// ❌ Does NOT create: simple_data_ver (versioning disabled)Example: Advanced Collection (With Versioning)
// xronox.config.json
{
"collectionMaps": {
"audited_data": {
"features": {
"versioning": { "enabled": true }
}
}
}
}
// Usage
const ctx: RouteContext = {
collection: 'audited_data',
dbName: 'my-db',
databaseType: 'runtime'
};
await createItem(router, ctx, { name: 'Test' }, { actor: 'user' });
// ✅ Creates: audited_data_head, audited_data_ver, audited_data_counter
// ✅ Stores version history automaticallyWork Tracking (jobId)
- Track operations across the call stack with
jobIdparameter - Automatic storage in
_system.jobIdsarray for MongoDB records - Enables log correlation, state fetching, and debugging
Quick Start
Basic CRUD Operations
import { createItem, updateItem, deleteItem, getItem } from '@x12i/xronox-core';
import type { BridgeRouter, RouteContext } from '@x12i/xronox-router';
// Setup context
const ctx: RouteContext = {
collection: 'users',
dbName: 'my-app-db',
databaseType: 'runtime',
tenantId: 'tenant-a' // Optional: for multi-tenant scenarios
};
// Create a new item
const jobId = `api-request-${Date.now()}`;
const result = await createItem(router, ctx, {
name: 'John Doe',
email: '[email protected]',
profileImage: '<base64-data>' // Auto-externalized to storage
}, {
actor: '[email protected]',
reason: 'New user registration',
jobId // Track this work
});
console.log(`Created: ${result.id}, version ${result.ov}`);
// Mongo head points to storage; profileImage externalized with _key and _url
// Fetch complete record (assembled from Mongo + Storage)
const user = await getItem(router, ctx, result.id);
// user includes all fields, with storage data fetched automatically
// Update the item
await updateItem(router, ctx, result.id, {
name: 'John Updated'
}, {
expectedOv: result.ov, // Optimistic locking
actor: 'system',
reason: 'User profile update',
jobId
});
// Logical delete (soft delete)
await deleteItem(router, ctx, result.id, {
actor: '[email protected]',
reason: 'User requested deletion',
jobId
});Pagination (Cursor-Based)
For efficient pagination over large collections, use the cursor-based pagination API:
import { Repos } from '@x12i/xronox-core';
// Get repository instance
// Note: Collection name can be with or without _head suffix (backwards compatible)
const routeInfo = router.route(ctx);
const mongoClient = await router.getMongoClient(routeInfo.mongoUri);
const mongo = mongoClient.db(ctx.dbName || 'default');
const repos = new Repos(mongo, ctx.collection || 'default'); // Works with 'entities' or 'entities_head'
// First page (newest items first)
const page1 = await repos.listHeadsPaged({}, 20);
console.log(`Items: ${page1.items.length}`);
console.log(`Has next: ${page1.pageInfo.hasNextPage}`);
// Next page (use endCursor from previous page)
if (page1.pageInfo.hasNextPage) {
const page2 = await repos.listHeadsPaged({}, 20, page1.pageInfo.endCursor);
// Continue pagination...
}
// With filter (e.g., active users only)
const activePage = await repos.listHeadsPaged(
{ 'metaIndexed.status': 'active' },
20
);
// Paginate version history for an item
const versionPage = await repos.listVersionsPaged(itemId, 10);
console.log(`Versions: ${versionPage.items.length}`);Why cursor-based?
- Scales to millions of documents (no skip/offset overhead)
- Stable results even when data changes between requests
- Works efficiently with MongoDB's indexed _id field
- Industry best practice for large datasets
Configuration
Create a config file (e.g., xronox.config.json):
{
"database": {
"uri": "ENV.MONGO_URI",
"dbName": "ENV.MONGO_TESTING_DB||xronox_demo",
"transactions": {
"enabled": "ENV.DB_TRANSACTIONS_ENABLED:boolean"
}
},
"mongoMapping": {
"mappedOnly": "ENV.MONGO_MAPPED_ONLY:boolean||false"
},
"storage": {
"endpoint": "ENV.SPACE_ENDPOINT",
"bucket": "ENV.SPACE_BUCKET",
"region": "ENV.SPACE_REGION",
"accessKey": "ENV.SPACE_ACCESS_KEY",
"secretKey": "ENV.SPACE_SECRET_KEY"
},
"collectionMaps": {
"entities": {
"indexedProps": ["name", "status"],
"features": {
"versioning": { "enabled": true },
"storage": { "enabled": true, "threshold": 1024 },
"lazyCreation": { "enabled": true, "createOnRead": false }
}
},
"simple_collection": {
"indexedProps": ["name"],
"features": {
"versioning": { "enabled": false }
}
}
}
}Per-Collection Feature Configuration:
versioning.enabled- Enable/disable versioning for this collection (default: true)storage.enabled- Enable/disable storage externalization (default: true)storage.threshold- Size threshold for externalization in bytes (default: 1024)lazyCreation.enabled- Enable lazy collection creation (default: true)lazyCreation.createOnRead- Create collections on read operations (default: false)
Environment variables:
# MongoDB (connection URI and logical DB name are SEPARATE)
MONGO_URI=mongodb://localhost:27017
MONGO_TESTING_DB=my-test-db
# Transactions (optional; auto-detected for replica sets if unset)
DB_TRANSACTIONS_ENABLED=false
# Mapped-only mode (optional; default false)
MONGO_MAPPED_ONLY=false
# Storage
SPACE_ENDPOINT=https://s3.amazonaws.com
SPACE_BUCKET=my-bucket
SPACE_REGION=us-east-1
SPACE_ACCESS_KEY=your-key
SPACE_SECRET_KEY=your-secretTesting & Demos
The real demos have moved to a peer package (outside this repository). This library focuses on core expertise.
- Demos enforce Rules 25/26/27 in the demos package (real execution, evidence, timeouts).
- This package enforces infrastructure and boundaries (Rule 28,
@x12i/env,@x12i/logxer, Poiesis).
See the demos package README for how to run demos and inspect evidence.
Architecture
This package follows the Poiesis methodology with clear component boundaries:
Primary Component: DataComponent
Expertise: Data persistence and retrieval with storage externalization
Sub-Components:
- CrudOperations (
src/crud.ts) - Create, update, delete, restore operations - RepositoryManagement (
src/repos.ts) - MongoDB collection management - TransactionLocking (
src/lock-manager.ts) - Concurrency control - StorageExternalization (inline in crud.ts) - Base64 field externalization
Helper Components:
- ConfigurationHelper (
src/configuration.helper.ts) - Config management via@x12i/env - ValidationHelper (
src/validation.helper.ts) - Data validation - TransactionHelper (
src/transaction.helper.ts) - Transaction management - SystemHeaderHelper (
src/system-header.helper.ts) - System metadata (_system field)
Planned Features
See PLANNED_FEATURES.md for features under consideration or development:
- Versioning & history browsing (external package)
- Collection restore to CV/timestamp (external package)
- Search & pagination (external package)
- Analytics/counters (external package)
- Multitenancy formalization (router addon/doc pattern)
- Concurrency demo expansion
Documentation
- Planned Features - Features under consideration
- API Specification - Complete API documentation
- Architecture - System architecture and design
- Environment Requirements - Setup and configuration
- Testing Principles - Testing strategy (Rules 25/26/27/28)
- Quality Assurance - Known issues and best practices
- Package Context - Business context and boundaries
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
License
MIT
Built with Poiesis methodology - Clear component boundaries, single expertise per component, and composable architecture for maintainable, testable, and scalable database operations.
