Skip to content

Commit

Permalink
Add data structure and repository pattern (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
daogrady authored Jun 27, 2024
1 parent 625fc4e commit 7a53f9d
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 531 deletions.
4 changes: 2 additions & 2 deletions lib/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { Visitor } = require('./visitor')
const { LOG, setLevel } = require('./logging')

/**
* @typedef {import('./visitor').CompileParameters} CompileParameters
* @typedef {import('./typedefs').visitor.CompileParameters} CompileParameters
*/

/**
Expand Down Expand Up @@ -39,7 +39,7 @@ const writeJsConfig = path => {

/**
* Compiles a CSN object to Typescript types.
* @param {{xtended: CSN, inferred: CSN}} csn
* @param {{xtended: CSN, inferred: CSN}} csn - csn tuple
* @param {CompileParameters} parameters - path to root directory for all generated files, min log level
*/
const compileFromCSN = async (csn, parameters) => {
Expand Down
10 changes: 9 additions & 1 deletion lib/components/identifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ const normalise = ident => ident && !isValidIdent.test(ident)
? `"${ident}"`
: ident

/**
* Returns the last part of a dot-separated identifier.
* @param {string} ident - the identifier to extract the last part from
* @returns {string} the last part of the identifier
*/
const last = ident => ident.split('.').at(-1)

module.exports = {
normalise
normalise,
last
}
22 changes: 12 additions & 10 deletions lib/components/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const { SourceFile, Buffer } = require('../file')
const { normalise } = require('./identifier')
const { docify } = require('./wrappers')

/** @typedef {import('../resolution/resolver').TypeResolveInfo} TypeResolveInfo */

/**
* Inline declarations of types can come in different flavours.
* The compiler can therefore be adjusted to print out one or the other
Expand All @@ -10,20 +12,20 @@ const { docify } = require('./wrappers')
*/
class InlineDeclarationResolver {
/**
* @param {string} name
* @param {import('./resolver').TypeResolveInfo} type
* @param {import('../file').Buffer} buffer
* @param {string} statementEnd
* @param {string} fq - full qualifier of the type
* @param {TypeResolveInfo} type - type info so far
* @param {import('../file').Buffer} buffer - the buffer to write into
* @param {string} statementEnd - statement ending character
* @protected
* @abstract
*/
// eslint-disable-next-line no-unused-vars
printInlineType(name, type, buffer, statementEnd) { /* abstract */ }
printInlineType(fq, type, buffer, statementEnd) { /* abstract */ }

/**
* Attempts to resolve a type that could reference another type.
* @param {any} items
* @param {import('./resolver').TypeResolveInfo} into - @see Visitor.resolveType
* @param {any} items - properties of the declaration we are resolving
* @param {TypeResolveInfo} into - @see Visitor.resolveType
* @param {SourceFile} relativeTo - file to which the resolved type should be relative to
* @public
*/
Expand Down Expand Up @@ -57,7 +59,7 @@ class InlineDeclarationResolver {
/**
* Visits a single element in an entity.
* @param {string} name - name of the element
* @param {import('./resolver').CSN} element - CSN data belonging to the the element.
* @param {import('../resolution/resolver').CSN} element - CSN data belonging to the the element.
* @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
* @param {Buffer} [buffer] - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
* @public
Expand Down Expand Up @@ -87,7 +89,7 @@ class InlineDeclarationResolver {

/**
* It returns TypeScript datatype for provided TS property
* @param {{typeName: string, typeInfo: TypeResolveInfo & { inflection: Inflection } }} type
* @param {{typeName: string, typeInfo: TypeResolveInfo & { inflection: Inflection } }} type - type of the property
* @param {string} typeName - name of the TypeScript property
* @returns {string} the datatype to be presented on TypeScript layer
* @public
Expand All @@ -96,7 +98,7 @@ class InlineDeclarationResolver {
return type.typeInfo.isNotNull ? typeName : `${typeName} | null`
}

/** @param {import('../visitor').Visitor} visitor */
/** @param {import('../visitor').Visitor} visitor - the visitor */
constructor(visitor) {
this.visitor = visitor
// type resolution might recurse. This indicator is used to determine
Expand Down
2 changes: 1 addition & 1 deletion lib/components/reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* y: E.x // <- ref
* }
* ```
* @param {{type: any}} element
* @param {{type: any}} element - the element
* @returns boolean
*/
const isReferenceType = element => element.type && Object.hasOwn(element.type, 'ref')
Expand Down
35 changes: 24 additions & 11 deletions lib/csn.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ const annotation = '@odata.draft.enabled'
* i.e. ones that have a query, but are not a cds level projection.
* Those are still not expanded and we have to retrieve their definition
* with all properties from the inferred model.
* @param {any} entity
* @param {any} entity - the entity
*/
const isView = entity => entity.query && !entity.projection

const isProjection = entity => entity.projection

const getViewTarget = entity => entity.query?.SELECT?.from?.ref?.[0]
/**
* @param {any} entity - the entity
* @see isView
* Unresolved entities have to be looked up from inferred csn.
*/
const isUnresolved = entity => entity._unresolved === true

const getProjectionTarget = entity => entity.projection?.from?.ref?.[0]
const isCsnAny = entity => entity?.constructor?.name === 'any'

const isDraftEnabled = entity => entity['@odata.draft.enabled'] === true

Expand All @@ -22,13 +27,20 @@ const isType = entity => entity?.kind === 'type'
const isEntity = entity => entity?.kind === 'entity'

/**
* @param {any} entity
* @see isView
* Unresolved entities have to be looked up from inferred csn.
* Attempts to retrieve the max cardinality of a CSN for an entity.
* @param {EntityCSN} element - csn of entity to retrieve cardinality for
* @returns {number} max cardinality of the element.
* If no cardinality is attached to the element, cardinality is 1.
* If it is set to '*', result is Infinity.
*/
const isUnresolved = entity => entity._unresolved === true
const getMaxCardinality = element => {
const cardinality = element?.cardinality?.max ?? 1
return cardinality === '*' ? Infinity : parseInt(cardinality)
}

const isCsnAny = entity => entity?.constructor?.name === 'any'
const getViewTarget = entity => entity.query?.SELECT?.from?.ref?.[0]

const getProjectionTarget = entity => entity.projection?.from?.ref?.[0]

class DraftUnroller {
/** @type {Set<string>} */
Expand Down Expand Up @@ -160,7 +172,7 @@ class DraftUnroller {
* (a) aspects via `A: B`, where `B` is draft enabled.
* Note that when an entity extends two other entities of which one has drafts enabled and
* one has not, then the one that is later in the list of mixins "wins":
* @param {any} csn
* @param {any} csn - the entity
* @example
* ```ts
* @odata.draft.enabled true
Expand Down Expand Up @@ -201,7 +213,7 @@ function unrollDraftability(csn) {
*
* This explicit propagation is required to add foreign key relations
* to referring entities.
* @param {any} csn
* @param {any} csn - the entity
* @example
* ```cds
* entity A: cuid { key name: String; }
Expand Down Expand Up @@ -251,7 +263,7 @@ function propagateForeignKeys(csn) {

/**
*
* @param {any} csn
* @param {any} csn - complete csn
*/
function amendCSN(csn) {
unrollDraftability(csn)
Expand Down Expand Up @@ -282,6 +294,7 @@ module.exports = {
isEntity,
isUnresolved,
isType,
getMaxCardinality,
getProjectionTarget,
getProjectionAliases,
getViewTarget,
Expand Down
64 changes: 64 additions & 0 deletions lib/resolution/builtin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class BuiltinResolver {
/**
* Builtin types defined by CDS.
*/
#builtins = {
UUID: 'string',
String: 'string',
Binary: 'string',
LargeString: 'string',
LargeBinary: 'Buffer | string | {value: import("stream").Readable, $mediaContentType: string, $mediaContentDispositionFilename?: string, $mediaContentDispositionType?: string}',
Vector: 'string',
Integer: 'number',
UInt8: 'number',
Int16: 'number',
Int32: 'number',
Int64: 'number',
Integer64: 'number',
Decimal: 'number',
DecimalFloat: 'number',
Float: 'number',
Double: 'number',
Boolean: 'boolean',
// note: the date-related types are strings on purpose, which reflects their runtime behaviour
Date: '__.CdsDate', // yyyy-mm-dd
DateTime: '__.CdsDateTime', // yyyy-mm-dd + time + TZ (precision: seconds)
Time: '__.CdsTime', // hh:mm:ss
Timestamp: '__.CdsTimestamp', // yyy-mm-dd + time + TZ (ms precision)
//
Composition: 'Array',
Association: 'Array'
}

/**
* @param {object} options - additional resolution options
* @param {boolean} options.IEEE754Compatible - if true, the Decimal, DecimalFloat, Float, and Double types are also allowed to be strings
*/
constructor ({ IEEE754Compatible } = {}) {
if (IEEE754Compatible) {
this.#builtins.Decimal = '(number | string)'
this.#builtins.DecimalFloat = '(number | string)'
this.#builtins.Float = '(number | string)'
this.#builtins.Double = '(number | string)'
}
this.#builtins = Object.freeze(this.#builtins)
}

/**
* @param {string | string[]} t - name or parts of the type name split on dots
* @returns {string | undefined | false} if t refers to a builtin, the name of the corresponding TS type is returned.
* If t _looks like_ a builtin (`cds.X`), undefined is returned.
* If t is obviously not a builtin, false is returned.
*/
resolveBuiltin (t) {
if (!Array.isArray(t) && typeof t !== 'string') return false
const path = Array.isArray(t) ? t : t.split('.')
return path.length === 2 && path[0] === 'cds'
? this.#builtins[path[1]]
: false
}
}

module.exports = {
BuiltinResolver
}
Loading

0 comments on commit 7a53f9d

Please sign in to comment.