Skip to content

Latest commit

 

History

History
590 lines (389 loc) · 16.1 KB

cds-reflect.md

File metadata and controls

590 lines (389 loc) · 16.1 KB
shorty synopsis status
cds.reflect
Find here information about reflecting parsed CDS models in CSN representation.
released

Reflecting CDS Models

{{$frontmatter?.synopsis}}

[[toc]]

cds. linked (csn) {#cds-linked .method}

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 {#linked-csn .class}

Models passed through cds.linked become instances of this class.

. is_linked {.property}

A tag property which is true for linked models. {.indent}

. definitions {.property}

The CSN definitions of the model, turned into an instance of LinkedDefinitions. {.indent}

. services {.property}

. entities {.property}

These are convenient shortcuts to access all service or all entitiy 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() {#each .method }

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 returning true or false

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() {#all .method }

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() {#find .method }

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() {#foreach .method }

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 function
  • defs — 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 {.class #iterable}

All objects of a linked model containing CSN definitions are instances of this class.

For example, that applies to:

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)
}

Moreover, you can use common array methods like these:

linked.definitions .forEach (d => console.log(d.name))
linked.definitions .filter (d => d.is_entity)
linked.definitions .find (d => d.name === 'Foo')
linked.definitions .some (d => d.name === 'Foo')
linked.definitions .map (d => d.name)

Each entry in an instance of LinkedDefinitions is a LinkedDefinition.

LinkedDefinition {.class #any}

All cds.linked definitions are instances of this class, or subclasses thereof. It is accessible through cds.linked.classes.any.

. is_linked {.property}

A tag property which is true for all linked definitions. {.indent}

. name {.property}

The linked definition's fully qualified name as a non-enumerable property. {.indent}

. kind {.property}

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 {.class}

All service definitions in a linked model are instances of this class.

class cds.service extends cds.context {...}

. is_service {.property}

A tag property which is true for linked entity definitions. {.indent}

. entities {.property}

. events {.property}

. actions {.property}

These properties are convenience shortcuts to access a service definition's exposed entity, type, event, action or function definitions.
Their values are LinkedDefinitions. {.indent}

cds. entity {.class }

All entity definitions in a linked model are instances of this class.

class cds.entity extends cds.struct {...}

As cds.entity is a subclass of cds.struct it also inherits all methods from that.

. is_entity {.property}

A tag property which is true for linked entity definitions. {.indent}

. keys {.property}

. associations {.property}

. compositions {.property}

. actions {.property}

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. {.indent}

. texts {.property}

If the entity has localized elements, this property is a reference to the respective .texts entity. If not, this property is undefined {.indent}

. drafts {.property}

If draft is enabled, a definition to easily refer to draft data for the current entity is returned. {.indent}

cds. struct {.class }

This is the base class of struct elements and types, aspects, and entities.

class cds.struct extends cds.type {...}

. is_struct {.property}

A tag property which is true for linked struct definitions (types and elements).
It is also true for linked entity definitions, i.e., instances of as cds.entity. {.indent}

. elements {.property}

The entity's declared elements as documented in the CSN Specification
as an instance of LinkedDefinitions. { .indent}

cds. Association {.class}

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 {.property}

A reference to the association's resolved linked target definition. {.indent}

. isAssociation {.property}

A tag property which is true for all linked Association definitions, including Compositions. {.indent}

. isComposition {.property}

A tag property which is true for all linked Composition definitions. {.indent}

. is2one / 2many {.property}

Convenient shortcuts to check whether an association definition has to-one or to-many cardinality. { .indent}

. keys {.property}

The declared or derived foreign keys. As specified in CSN spec this is a projection of the association target's elements. {.indent}

. foreignKeys {.property}

The effective foreign keys of managed association as linked definitions.
The value is an instance of LinkedDefinitions. {.indent}

cds. linked .classes {#cds-linked-classes .property}

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() {.method}

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 {#cds-builtin-types .property}

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.