Reflecting CDS Models
Find here information about reflecting parsed CDS models in CSN representation.
cds. linked (csn)
Method cds.linked
(or cds.reflect
which is an alias to the same method) turns a given parsed model into an instance of class LinkedCSN
, and all definitions within into instances of class LinkedDefinition
, recursively.
Declaration:
function* cds.linked (csn: CSN | string) => LinkedCSN
A typical usage is like that:
let csn = cds.load('some-model.cds')
let linked = cds.linked(csn) // linked === csn
Instead of a already compiled CSN, you can also pass a string containing CDL source code:
let linked = cds.linked(`
entity Books {
key ID: UUID;
title: String;
author: Association to Authors;
}
entity Authors {
key ID: UUID;
name: String;
}
`)
The passed in model gets modified, and the returned linked model is actually the modified passed-in csn.
The operation is idempotent, that is, you can repeatedly invoke it on already linked models with zero overhead.
LinkedCSN
Models passed through cds.linked
become instances of this class.
. is_linked
A tag property which is true
for linked models.
. definitions
The CSN definitions of the model, turned into an instance of LinkedDefinitions
.
. services
. entities
These are convenient shortcuts to access all service or all entity definitions in a model.
The value is an instance of LinkedDefinitions
.
For example:
let csn = CDL`
namespace my.bookshop;
entity Books {...}
entity Authors {...}
service CatalogService {
entity ListOfBooks as projection on Books {...}
}
`
let m = cds.linked (csn)
// Object nature
let { CatalogService, AdminService } = m.services
let { Books, Authors } = m.entities
// Array nature
for (let each of m.entities) console.log(each.name)
// Function nature
let { ListOfBooks } = m.entities ('my.bookshop.CatalogService')
In addition to the object and array natures of LinkedDefinitions
these properties also can be used as functions, which allows to optionally specify a namespace to fetch all definitions with prefixed with that. If no namespace is specified, the model's declared namespace is used, if any.
each()
function* lm.each (
filter : string | def => true/false,
defs? : linked_definitions
)
Fetches definitions matching the given filter, returning an iterator on them.
let m = cds.reflect (csn)
for (let d of m.each('entity')) {
console.log (d.kind, d.name)
}
The first argument filter specifies a filter to match definitions, which can be one of:
- a
string
referring to a kind of definition - a
function
returningtrue
orfalse
Derived kinds are supported, for example, m.each('struct')
matches structs as well as entities; kind 'any'
matches all.
The second optional argument defs allows to specify the definitions to fetch in, defaults to this.definitions
.
all()
function lm.all (
filter : string | def => true/false,
defs? : linked_definitions
)
Convenience shortcut to [... model.each()]
, for example, the following are equivalent:
m.all('entity') //> using shortcut
[...m.each('entity')] //> using spread operator
find()
function lm.find (
filter : string | def => true/false,
defs? : linked_definitions
)
Convenience shortcut to fetch definitions matching the given filter, returning the first match, if any. For example:
let service = m.find('service')
The implementation uses to .each()
as follows:
for (let any of m.each('service')) return any
foreach()
function lm.foreach (
filter : def => true/false | string,
visitor : def => {},
defs? : linked_definitions
)
Calls the visitor for each definition matching the given filter. foreach
iterates through the passed in defs only, forall
in addition walks through all nested element definitions hierarchically.
filter
/kind
— the filter or kind used to match definitions → see .each(x)visitor
— the callback functiondefs
— the definitions to fetch in, default:this.definitions
Examples:
// print the names of all services
let m = cds.reflect(csn)
m.foreach ('service', s => console.log(s.name))
// print the names of all Associations in Books element
let { Books } = m.entities()
m.foreach ('Association', a => console.log(a.name), Books.elements)
LinkedDefinitions
All objects of a linked model containing CSN definitions are instances of this class.
For example, that applies to:
cds.model
.definitions, .services, .entitiescds.service
.entities, .events, .actionscds.entity
.keys, .associations, .compositions, .actionscds.struct
.elements (hence alsocds.entity
.elements)cds.Association
.foreignKeys
Instances of LinkedDefinitions
allow both, object-style access, as well as array-like access. For example:
let linked = cds.linked (model)
let { Books, Authors } = linked.entities // object-like
let [ Books, Authors ] = linked.entities // array-like
Note: Orders of definitions could change, so you should always prefer object destructuring over array destructuring.
The array-like nature also allows using these shortcuts in for..of
loops, of course. Which means, you can do that:
for (let each of linked.definitions) console.log (each.name)
... instead of iterating definitions using for..in
loops like that:
for (let each in linked.definitions) {
let d = linked.definitions [each]
console.log (d.name)
}
Each entry in an instance of LinkedDefinitions
is a LinkedDefinition
.
LinkedDefinition
All cds.linked
definitions are instances of this class, or subclasses thereof. It is accessible through cds.linked.classes.any
.
. is_linked
A tag property which is true
for all linked definitions.
. name
The linked definition's fully qualified name as a non-enumerable property.
. kind
The linked definition's resolved kind as a non-enumerable property. One of:
'context'
'service'
'entity'
'type'
'aspect'
'event'
'element'
'annotation'
... as documented in the CSN specification.
instanceof
You can use JavaScript's standard instanceof
operator in combination with the built-in classes to check a linked definition's type:
let { Foo } = cds.linked(csn).entities
if (Foo instanceof cds.entity) console.log ("it's an entity")
cds. service
All service definitions in a linked model are instances of this class.
class cds.service extends cds.context {...}
. is_service
A tag property which is true
for linked entity definitions.
. entities
. events
. actions
These properties are convenience shortcuts to access a service definition's exposed entity, type, event, action or function definitions.
Their values are LinkedDefinitions
.
cds. entity
All entity definitions in a linked model are instances of this class.
class cds.entity extends cds.struct {...}
As
cds.entity
is a subclass ofcds.struct
it also inherits all methods from that.
. is_entity
A tag property which is true
for linked entity definitions.
. keys
. associations
. compositions
. actions
These properties are convenient shortcuts to access an entity definition's declared keys, Association or Composition elements, as well as bound action or function definitions.
Their values are LinkedDefinitions
.
. texts
If the entity has localized elements, this property is a reference to the respective .texts
entity. If not, this property is undefined
. drafts
If draft is enabled, a definition to easily refer to draft data for the current entity is returned.
cds. struct
This is the base class of struct elements and types, aspects, and entities.
class cds.struct extends cds.type {...}
. is_struct
A tag property which is true
for linked struct definitions (types and elements).
It is also true
for linked entity definitions, that is, instances of as cds.entity
.
. elements
The entity's declared elements as documented in the CSN Specification
as an instance of LinkedDefinitions
.
cds. Association
All linked definitions of type Association
or Composition
, including elements, are instances of this class. Besides the properties specified for Associations in CSN, linked associations provide the following reflection properties...
. _target
A reference to the association's resolved linked target definition.
. isAssociation
A tag property which is true
for all linked Association definitions, including Compositions.
. isComposition
A tag property which is true
for all linked Composition definitions.
. is2one / 2many
Convenient shortcuts to check whether an association definition has to-one or to-many cardinality.
. keys
The declared or derived foreign keys. As specified in CSN spec this is a projection of the association target's elements.
. foreignKeys
The effective foreign keys of managed association as linked definitions.
The value is an instance of LinkedDefinitions
.
cds. linked .classes
This property gives you access to the very roots of cds
's type system. When a model is passed through cds.linked
all definitions effectively become instances of one of these classes. In essence they are defined as follows:
class any {...}
class context extends any {...}
cds.service = class service extends context {...}
cds.type = class type extends any {...}
class scalar extends type {...}
class boolean extends scalar {...}
class number extends scalar {...}
class date extends scalar {...}
class string extends scalar {...}
cds.array = class array extends type {...}
cds.struct = class struct extends type {...}
cds.entity = class entity extends struct {...}
cds.event = class event extends struct {...}
cds.Association = class Association extends type {...}
cds.Composition = class Composition extends Association {...}
A few prominent ones of the above classes are available through top-level shortcuts as indicated by the
cds.<classname> =
prefixes in the above pseudo code, find more details on these in the following sections.
For example, you can use these classes as follows:
let model = CDL`
entity Books { author: Association to Authors; }
entity Authors { key ID: UUID; }
`)
let m = cds.linked(model)
let { Books, Authors } = m.entities
let isEntity = Books instanceof cds.entity
let keys = Books.keys
let { author } = Books.elements
if (author.is2many) ...
mixin()
Provided a convenient way to enhance one or more of the builtin classes with additional methods. Use it like that:
const cds = require ('@sap/cds')
// simplistic csn2cdl enablement
cds.linked.classes .mixin (
class type {
toCDL(){ return `${this.kind} ${this.name} : ${this.typeAsCDL()};\n` }
typeAsCDL(){ return `${this.type.replace(/^cds\./,'')}` }
},
class struct {
typeAsCDL() { return `{\n${ Object.values(this.elements).map (
e => ` ${e.toCDL()}`
).join('')}}`}
},
class entity extends cds.struct {
typeAsCDL() { return (
this.includes ? this.includes+' ' : ''
) + super.typeAsCDL() }
},
class Association {
typeAsCDL(){ return `Association to ${this.target}` }
},
)
// test drive
let csn = CDL`
entity Books : cuid { title:String; author: Association to Authors }
entity Authors : cuid { name:String; }
aspect cuid : { key ID:UUID; }
`
cds.linked(csn).foreach (d => console.log(d.toCDL()))
cds. builtin. types
This property gives you access to all prototypes of the builtin classes as well as to all linked definitions of the builtin pre-defined types. The resulting object is in turn like the definitions
in a LinkedCSN
.
Actually, at runtime CDS is in fact bootstrapped out of this using core CSN object structures and cds.linked
techniques. Think of it to be constructed as follows:
cds.builtin.types = cds.linked (CDL`
using from './roots';
context cds {
type UUID : String(36);
type Boolean : boolean;
type Integer : number;
type UInt8 : Integer;
type Int16 : Integer;
type Int32 : Integer;
type Int64 : Integer;
type Integer64 : Integer;
type Decimal : number;
type Double : number;
type Date : date;
type Time : date;
type DateTime : date;
type Timestamp : date;
type String : string;
type Binary : string;
type LargeString : string;
type LargeBinary : string;
}
`) .definitions
With ./roots
being this in-memory CSN:
const { any, context, service ,
type, scalar, string, number, boolean, date,
array, struct, entity, event, aspect
Association, Composition
} = cds.linked.classes
const roots = module.exports = {definitions:{
any: new any,
context: new context ({type:'any'}),
type: new type ({type:'any'}),
scalar: new scalar ({type:'type'}),
string: new string ({type:'scalar'}),
number: new number ({type:'scalar'}),
boolean: new boolean ({type:'scalar'}),
date: new date ({type:'scalar'}),
array: new array ({type:'type'}),
struct: new struct ({type:'type'}),
entity: new entity ({type:'struct'}),
event: new event ({type:'struct'}),
aspect: new aspect ({type:'struct'}),
Association: new Association ({type:'type'}),
Composition: new Composition ({type:'Association'}),
service: new service ({type:'context'}),
}}
Indentation indicates inheritance.