@datacapy/om
v0.2.0
Published
Datacapy object model: repositories, relations and data sources
Readme
@datacapy/om
Part of the DataCapy monorepo.
Object Document Mapping (ODM) framework for Node.js, with support for SQL, document and key-value databases. MySQL, MongoDB, Redis and an in-memory mock are bundled, and you can add your own datasource. Build your model from schemas, repositories and services.
Key Features
- Model Elements - Define your application with Schemas, Repositories, and Services
- Relations - Comprehensive relation system (hasOne, hasMany, belongsTo, etc.)
- Validation - Built-in and custom validators with type-casting
- Field-Level Encryption - Transparent at-rest encryption for sensitive schema fields
- Multi-DataSource Support - Dynamic datasource routing for multi-tenant architectures
- Composite Keys - Advanced multi-field relation matching
- Query Optimisation - Automatic query optimisation for efficient database operations
Quick Start
Define a Schema
import { Schema } from '@datacapy/om'
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, validator: 'email' },
age: { type: Number, min: 0 },
})Create a Repository
import { Repo } from '@datacapy/om'
export class RepoUser extends Repo<User> {
constructor() {
super({
name: 'user',
schema: userSchema,
relations: {
posts: {
type: 'hasMany',
repo: 'post',
pkey: '_id',
key: 'authorId',
},
},
})
}
}Use in Services
const repo = new RepoUser()
// Find with relations
const users = await repo.find(
{},
{
populate: {
posts: true,
},
}
)
// Insert
await repo.insert({
name: 'Alice',
email: '[email protected]',
age: 30,
})Core Concepts
Model Elements
- Schemas - Define data structure and validation rules
- Repositories - Handle data persistence and queries
- Services - Implement business logic and coordinate repositories
Relations
Support for common relation types:
hasOne/hasMany- Parent has related childrenbelongsToOne/belongsToMany- Child belongs to parent(s)hasManyCount- Count of related documents- Embedded relations for nested data structures
See Relations Documentation for details.
Validation
Built-in validators:
required,notNull,notEmptylength(min/max for strings and arrays)regexpattern matchingemailformat validation- Custom validators
See Validation Documentation for details.
Dynamic DataSource Resolution
Multi-tenant support with runtime datasource routing:
import { DataSourceContext } from '@datacapy/om'
// Create context for routing
const context = DataSourceContext.fromDataSources({
workspace: { lookupKey: workspaceId },
})
// Use context in queries
const surveys = await repo.find(query, { context })See DataSource Context Documentation for details.
Documentation
Core Documentation
- Architecture - System design and components
- Relations - Relation system overview
- Validation - Data validation and type-casting
Advanced Features
- Composite Keys - Multi-field relation matching
- DataSource Context - Multi-datasource support
- Encryption - Field-level at-rest encryption
- Performance - Optimisation strategies
- Upsert Operations - upsertOne, upsertMany, and $setOnInsert
Development
Multi-DataSource Configuration
Register Lookup Implementation
import { DataSourceLookup } from '@datacapy/om'
const workspaceLookup: DataSourceLookup = {
async lookup(dataSourceName, lookupKey) {
// Fetch workspace configuration
const workspace = await db.workspaces.findOne({ _id: lookupKey })
return {
type: 'mongodb',
config: {
uri: workspace.databaseUri,
database: workspace.databaseName,
},
}
},
}
modelManager.setDataSourceLookup('workspace', workspaceLookup)Configure Repositories
export class RepoSurvey extends Repo<Survey> {
constructor() {
super({
name: 'survey',
dataSource: 'workspace', // Dynamic routing
})
}
}Use in Services
async getAll({ workspaceId }) {
const context = DataSourceContext.fromDataSources({
workspace: { lookupKey: workspaceId }
})
return await this.getRepo('survey').find({}, { context })
}Examples
Cross-DataSource Relations
const context = DataSourceContext.fromDataSources({
workspace: { lookupKey: workspaceId },
tenant: { lookupKey: tenantId },
})
await repoSurvey.findOne(surveyId, {
context,
populate: {
tenant: true, // Automatically uses 'tenant' context
},
})Composite Key Relations
relations: {
participant: {
type: 'belongsToOne',
repo: 'surveyParticipant',
key: 'participantId',
keys: {
surveyId: 'surveyId',
workspaceId: 'workspaceId'
}
}
}