Metamodel Provider
The IMetamodelProvider interface enables integration of ocl.js with runtime metamodels where type hierarchies are not based on JavaScript prototype chains. This is particularly useful for modeling platforms, dynamic object systems, and environments where type information is external to the objects themselves.
When to Use a Metamodel Provider
Consider using a metamodel provider when:
- Working with plain objects: Your objects don't use JavaScript classes or prototypes
- External type systems: Type information is stored separately from the objects (e.g., in a metamodel registry)
- Dynamic metamodels: Types and their hierarchies can change at runtime
- Modeling platforms: You're integrating with EMF/Ecore-style modeling systems
- Custom type hierarchies: Your inheritance relationships aren't expressed through JavaScript's prototype chain
Basic Usage
Setting Up a Provider
import { OclEngine, IMetamodelProvider } from '@stekoe/ocl.js';
// Example: Objects with a _type property
interface TypedObject {
_type: string;
[key: string]: unknown;
}
// Define your metamodel provider
const provider: IMetamodelProvider = {
getTypeName(obj: unknown): string | undefined {
if (typeof obj === 'object' && obj !== null && '_type' in obj) {
return (obj as TypedObject)._type;
}
return undefined;
},
isKindOf(obj: unknown, typeName: string): boolean {
const objType = this.getTypeName(obj);
if (!objType) return false;
// Check if objType is typeName or a subtype of typeName
// This example uses a simple hierarchy
const hierarchy: Record<string, string[]> = {
'Entity': [],
'NamedElement': ['Entity'],
'Person': ['NamedElement', 'Entity'],
'Employee': ['Person', 'NamedElement', 'Entity']
};
return objType === typeName || (hierarchy[objType]?.includes(typeName) ?? false);
},
isTypeOf(obj: unknown, typeName: string): boolean {
return this.getTypeName(obj) === typeName;
}
};
// Use the provider with OclEngine
const engine = OclEngine.create()
.setMetamodelProvider(provider)
.addOclExpression('context Person inv: self.oclIsKindOf(NamedElement)');
// Evaluate an object
const person = {
_type: 'Person',
name: 'John Doe',
age: 30
};
const result = engine.evaluate(person);
console.log(result.getResult()); // trueAdvanced Example: External Metamodel Registry
For more complex scenarios, you might have an external metamodel registry that manages type definitions and relationships:
// Metamodel registry that manages type definitions
class MetamodelRegistry {
private types = new Map<string, TypeDefinition>();
registerType(name: string, superTypes: string[] = []) {
this.types.set(name, { name, superTypes });
}
isSubtypeOf(typeName: string, superTypeName: string): boolean {
if (typeName === superTypeName) return true;
const typeDef = this.types.get(typeName);
if (!typeDef) return false;
// Check direct supertypes
if (typeDef.superTypes.includes(superTypeName)) return true;
// Check transitive supertypes
return typeDef.superTypes.some(st => this.isSubtypeOf(st, superTypeName));
}
getType(typeName: string): TypeDefinition | undefined {
return this.types.get(typeName);
}
}
interface TypeDefinition {
name: string;
superTypes: string[];
}
// Initialize the registry
const registry = new MetamodelRegistry();
registry.registerType('Entity', []);
registry.registerType('NamedElement', ['Entity']);
registry.registerType('Person', ['NamedElement']);
registry.registerType('Employee', ['Person']);
// Create a provider that uses the registry
const registryProvider: IMetamodelProvider = {
getTypeName(obj: unknown): string | undefined {
if (typeof obj === 'object' && obj !== null && '__typename' in obj) {
return (obj as Record<string, unknown>).__typename as string;
}
return undefined;
},
isKindOf(obj: unknown, typeName: string): boolean {
const objType = this.getTypeName(obj);
if (!objType) return false;
return registry.isSubtypeOf(objType, typeName);
},
isTypeOf(obj: unknown, typeName: string): boolean {
return this.getTypeName(obj) === typeName;
}
};
// Use with OclEngine
const engine = OclEngine.create()
.setMetamodelProvider(registryProvider);EMF/Ecore Integration Example
For modeling systems like Eclipse Modeling Framework (EMF), you can create a provider that integrates with the metamodel:
// Simplified EMF-style example
interface EObject {
eClass(): EClass;
eGet(feature: string): unknown;
}
interface EClass {
getName(): string;
getESuperTypes(): EClass[];
}
const emfProvider: IMetamodelProvider = {
getTypeName(obj: unknown): string | undefined {
if (this.isEObject(obj)) {
return obj.eClass().getName();
}
return undefined;
},
isKindOf(obj: unknown, typeName: string): boolean {
if (!this.isEObject(obj)) return false;
const eClass = obj.eClass();
return this.eClassMatches(eClass, typeName);
},
isTypeOf(obj: unknown, typeName: string): boolean {
if (!this.isEObject(obj)) return false;
return obj.eClass().getName() === typeName;
},
// Helper methods
isEObject(obj: unknown): obj is EObject {
return typeof obj === 'object' && obj !== null && 'eClass' in obj;
},
eClassMatches(eClass: EClass, typeName: string): boolean {
if (eClass.getName() === typeName) return true;
return eClass.getESuperTypes().some(superType =>
this.eClassMatches(superType, typeName)
);
}
};Comparison with setTypeDeterminer
OCL.js provides two ways to customize type resolution:
setTypeDeterminer()
Simple callback for type name resolution only:
engine.setTypeDeterminer((obj) => {
return obj._type || obj.constructor.name;
});Use when:
- You only need to determine type names
- No custom type hierarchy support needed
- Simple, single-purpose type resolution
setMetamodelProvider()
Full metamodel integration with type hierarchy support:
engine.setMetamodelProvider({
getTypeName(obj) { /* ... */ },
isKindOf(obj, typeName) { /* ... */ },
isTypeOf(obj, typeName) { /* ... */ }
});Use when:
- You need full type hierarchy support
- Working with external metamodels
- Need
oclIsKindOfandoclIsTypeOfoperations - Type relationships are not based on prototypes
OCL Operations Enabled
Setting a metamodel provider enables proper support for:
oclIsKindOf(type): Checks if object is of the specified type or any subtypeoclIsTypeOf(type): Checks if object is exactly of the specified type- Context matching: Automatically matches objects to OCL context declarations
API Reference
See the detailed API documentation: