@cap-js/cds-typer 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -2
- package/lib/cli.js +1 -1
- package/lib/components/inline.js +3 -3
- package/lib/components/resolver.js +13 -2
- package/lib/file.js +41 -12
- package/lib/visitor.js +30 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,31 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
-
## Version 0.
|
|
7
|
+
## Version 0.6.2 - TBD
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
## Version 0.6.1 - 2023-08-10
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Removed a warning about circular imports
|
|
22
|
+
|
|
23
|
+
## Version 0.6.0 - 2023-08-07
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- Support for `event` syntax
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Initialise bound actions with stubs to support `"strict":true` in _tsconfig.json_
|
|
30
|
+
- Add leading underscore to appease `noUnusedParameters` in strict tsconfigs
|
|
31
|
+
- No longer inflect `type` definitions when they are referenced within entities or other type definitions
|
|
8
32
|
|
|
9
33
|
## Version 0.5.0 - 2023-07-25
|
|
10
34
|
|
|
@@ -14,7 +38,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
14
38
|
### Added
|
|
15
39
|
- Support for `array of` syntax
|
|
16
40
|
|
|
17
|
-
###
|
|
41
|
+
### Fixed
|
|
18
42
|
- Generate `string` type for date-related types in CDS definitions
|
|
19
43
|
- Generate `Buffer | string` type for the CDS type `LargeBinary`
|
|
20
44
|
|
package/lib/cli.js
CHANGED
|
@@ -44,7 +44,7 @@ const flags = {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const hint = () =>
|
|
47
|
+
const hint = () => 'Missing or invalid parameter(s). Call with --help for more details.'
|
|
48
48
|
const indent = (s, indentation) => s.split(EOL).map(line => `${indentation}${line}`).join(EOL)
|
|
49
49
|
|
|
50
50
|
const help = () => `SYNOPSIS${EOL2}` +
|
package/lib/components/inline.js
CHANGED
|
@@ -142,7 +142,7 @@ class FlatInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
|
142
142
|
flatten(prefix, type) {
|
|
143
143
|
return type.typeInfo.structuredType
|
|
144
144
|
? Object.entries(type.typeInfo.structuredType).map(([k,v]) => this.flatten(`${this.prefix(prefix)}${k}`, v))
|
|
145
|
-
: [`${prefix}
|
|
145
|
+
: [`${prefix}${this.getPropertyTypeSeparator()} ${type.typeName}`]
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
printInlineType(name, type, buffer) {
|
|
@@ -185,7 +185,7 @@ class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
|
185
185
|
this.printDepth++
|
|
186
186
|
const lineEnding = this.printDepth > 1 ? ',' : statementEnd
|
|
187
187
|
if (type.typeInfo.structuredType) {
|
|
188
|
-
const prefix = name ? `${name}
|
|
188
|
+
const prefix = name ? `${name}${this.getPropertyTypeSeparator()}`: ''
|
|
189
189
|
buffer.add(`${prefix} {`)
|
|
190
190
|
buffer.indent()
|
|
191
191
|
for (const [n, t] of Object.entries(type.typeInfo.structuredType)) {
|
|
@@ -194,7 +194,7 @@ class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
|
194
194
|
buffer.outdent()
|
|
195
195
|
buffer.add(`}${lineEnding}`)
|
|
196
196
|
} else {
|
|
197
|
-
buffer.add(`${name}
|
|
197
|
+
buffer.add(`${name}${this.getPropertyTypeSeparator()} ${type.typeName}${lineEnding}`)
|
|
198
198
|
}
|
|
199
199
|
this.printDepth--
|
|
200
200
|
return buffer
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
const util = require('../util')
|
|
4
4
|
const { Buffer, SourceFile, Path, Library, baseDefinitions } = require("../file")
|
|
5
5
|
const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne } = require('./wrappers')
|
|
6
|
-
const { Visitor } = require("../visitor")
|
|
7
6
|
const { StructuredInlineDeclarationResolver } = require("./inline")
|
|
8
7
|
|
|
9
8
|
/** @typedef {{ cardinality?: { max?: '*' | number } }} EntityCSN */
|
|
10
9
|
/** @typedef {{ definitions?: Object<string, EntityCSN> }} CSN */
|
|
10
|
+
/** @typedef {import('../visitor').Visitor} Visitor */
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* When nested inline types require additional imports. E.g.:
|
|
@@ -116,7 +116,7 @@ class Resolver {
|
|
|
116
116
|
let qualifier = parts.join('.')
|
|
117
117
|
while (
|
|
118
118
|
this.csn.definitions[qualifier] &&
|
|
119
|
-
['entity', 'type', 'aspect'].includes(this.csn.definitions[qualifier].kind)
|
|
119
|
+
['entity', 'type', 'aspect', 'event'].includes(this.csn.definitions[qualifier].kind)
|
|
120
120
|
) {
|
|
121
121
|
parts.pop()
|
|
122
122
|
qualifier = parts.join('.')
|
|
@@ -131,6 +131,7 @@ class Resolver {
|
|
|
131
131
|
* - explicit annotation by the user in the CSN
|
|
132
132
|
* - implicitly derived inflection based on simple grammar rules
|
|
133
133
|
* - collisions between singular and plural name (resolved by appending a '_' suffix)
|
|
134
|
+
* - type definitions, which are not inflected
|
|
134
135
|
* - inline type definitions, which don't really have a linguistic plural,
|
|
135
136
|
* but need to expressed as array type to be consumable by the likes of Composition.of.many<T>
|
|
136
137
|
* @param {import('./resolver').TypeResolveInfo} typeInfo information about the type gathered so far.
|
|
@@ -138,6 +139,16 @@ class Resolver {
|
|
|
138
139
|
* @returns {Inflection}
|
|
139
140
|
*/
|
|
140
141
|
inflect(typeInfo, namespace) {
|
|
142
|
+
// TODO: handle builtins here as well?
|
|
143
|
+
// guard: types don't get inflected
|
|
144
|
+
if (typeInfo.csn?.kind === 'type') {
|
|
145
|
+
return {
|
|
146
|
+
singular: typeInfo.plainName,
|
|
147
|
+
plural: typeInfo.plainName,
|
|
148
|
+
typeName: typeInfo.plainName,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
141
152
|
let typeName
|
|
142
153
|
let singular
|
|
143
154
|
let plural
|
package/lib/file.js
CHANGED
|
@@ -99,6 +99,8 @@ class SourceFile extends File {
|
|
|
99
99
|
this.imports = {}
|
|
100
100
|
/** @type {Buffer} */
|
|
101
101
|
this.preamble = new Buffer()
|
|
102
|
+
/** @type {{ buffer: Buffer, fqs: {name: string, fq: string}[]}} */
|
|
103
|
+
this.events = { buffer: new Buffer(), fqs: []}
|
|
102
104
|
/** @type {Buffer} */
|
|
103
105
|
this.types = new Buffer()
|
|
104
106
|
/** @type {{ buffer: Buffer, fqs: {name: string, fq: string}[]}} */
|
|
@@ -119,8 +121,24 @@ class SourceFile extends File {
|
|
|
119
121
|
this.inflections = []
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Stringifies a lambda expression.
|
|
126
|
+
* @param {{name: string, parameters: [string, string][], returns: string, initialiser: string}} - name, parameters, return type, and initialiser expression
|
|
127
|
+
* @returns {string} - the stringified lambda
|
|
128
|
+
* @example
|
|
129
|
+
* ```js
|
|
130
|
+
* stringifyLambda({parameters: [['p','T']]} // (p: T) => any
|
|
131
|
+
* stringifyLambda({name: 'f', parameters: [['p','T']]} // f: (p: T) => any
|
|
132
|
+
* stringifyLambda({name: 'f', parameters: [['p','T']], returns: 'number'} // f: (p: T) => number
|
|
133
|
+
* stringifyLambda({name: 'f', parameters: [['p','T']], returns: 'number', initialiser: '_ => 42'} // f: (p: T) => string = _ => 42
|
|
134
|
+
*
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
static stringifyLambda({name, parameters=[], returns='any', initialiser}) {
|
|
138
|
+
const signature = `(${parameters.map(([n, t]) => `${n}: ${t}`).join(', ')}) => ${returns}`
|
|
139
|
+
const prefix = name ? `${name}: `: ''
|
|
140
|
+
const suffix = initialiser ? ` = ${initialiser}` : ''
|
|
141
|
+
return prefix + signature + suffix
|
|
124
142
|
}
|
|
125
143
|
|
|
126
144
|
/**
|
|
@@ -139,27 +157,25 @@ class SourceFile extends File {
|
|
|
139
157
|
/**
|
|
140
158
|
* Adds a function definition in form of a arrow function to the file.
|
|
141
159
|
* @param {string} name name of the function
|
|
142
|
-
* @param {{relative: string | undefined, local: boolean, posix: boolean}}
|
|
160
|
+
* @param {{relative: string | undefined, local: boolean, posix: boolean}} parameters list of parameters, passed as [name, type] pairs
|
|
143
161
|
* @param returns the return type of the function
|
|
144
162
|
*/
|
|
145
|
-
addFunction(name,
|
|
163
|
+
addFunction(name, parameters, returns) {
|
|
146
164
|
// FIXME: use different buffers for buffers and actions, or at least rename buffer to the more general category "functions"?
|
|
147
165
|
this.actions.buffer.add("// function")
|
|
148
|
-
this.actions.buffer.add(`export declare const ${SourceFile.stringifyLambda(name,
|
|
166
|
+
this.actions.buffer.add(`export declare const ${SourceFile.stringifyLambda({name, parameters, returns})};`)
|
|
149
167
|
this.actions.names.push(name)
|
|
150
168
|
}
|
|
151
169
|
|
|
152
170
|
/**
|
|
153
171
|
* Adds an action definition in form of a arrow function to the file.
|
|
154
172
|
* @param {string} name name of the action
|
|
155
|
-
* @param {{relative: string | undefined, local: boolean, posix: boolean}}
|
|
173
|
+
* @param {{relative: string | undefined, local: boolean, posix: boolean}} parameters list of parameters, passed as [name, type] pairs
|
|
156
174
|
* @param returns the return type of the action
|
|
157
175
|
*/
|
|
158
|
-
addAction(name,
|
|
159
|
-
//const ps = params.map(([n, t]) => `${n}: ${t}`).join(', ')
|
|
176
|
+
addAction(name, parameters, returns) {
|
|
160
177
|
this.actions.buffer.add("// action")
|
|
161
|
-
|
|
162
|
-
this.actions.buffer.add(`export declare const ${SourceFile.stringifyLambda(name, params, returns)};`)
|
|
178
|
+
this.actions.buffer.add(`export declare const ${SourceFile.stringifyLambda({name, parameters, returns})};`)
|
|
163
179
|
this.actions.names.push(name)
|
|
164
180
|
}
|
|
165
181
|
|
|
@@ -196,6 +212,7 @@ class SourceFile extends File {
|
|
|
196
212
|
// and a type containing all disctinct values.
|
|
197
213
|
// We can get away with this as TS doesn't feature nominal typing, so the structure
|
|
198
214
|
// is all we care about.
|
|
215
|
+
// FIXME: this really should be in visitor, as File should not contain logic of this kind
|
|
199
216
|
this.enums.fqs.push({ name, fq })
|
|
200
217
|
const bu = this.enums.buffer
|
|
201
218
|
bu.add('// enum')
|
|
@@ -210,7 +227,6 @@ class SourceFile extends File {
|
|
|
210
227
|
bu.add('}')
|
|
211
228
|
bu.add(`export type ${name} = ${[...vals].join(' | ')}`)
|
|
212
229
|
bu.add('')
|
|
213
|
-
|
|
214
230
|
}
|
|
215
231
|
|
|
216
232
|
/**
|
|
@@ -225,6 +241,16 @@ class SourceFile extends File {
|
|
|
225
241
|
this.classNames[clean] = fq
|
|
226
242
|
}
|
|
227
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Adds an event to this file.
|
|
246
|
+
* are supposed to be present in this file.
|
|
247
|
+
* @param {string} name cleaned name of the event
|
|
248
|
+
* @param {string} fq fully qualified name, including the namespace
|
|
249
|
+
*/
|
|
250
|
+
addEvent(name, fq) {
|
|
251
|
+
this.events.fqs.push({ name, fq })
|
|
252
|
+
}
|
|
253
|
+
|
|
228
254
|
/**
|
|
229
255
|
* Adds an import if it does not exist yet.
|
|
230
256
|
* @param {Path} imp qualifier for the namespace to import.
|
|
@@ -289,6 +315,7 @@ class SourceFile extends File {
|
|
|
289
315
|
namespaces.join(),
|
|
290
316
|
this.aspects.join(), // needs to be before classes
|
|
291
317
|
this.classes.join(),
|
|
318
|
+
this.events.buffer.join(),
|
|
292
319
|
this.actions.buffer.join(),
|
|
293
320
|
].filter(Boolean).join('\n')
|
|
294
321
|
}
|
|
@@ -311,6 +338,8 @@ class SourceFile extends File {
|
|
|
311
338
|
`module.exports.${original} = csn.${original}`
|
|
312
339
|
])))
|
|
313
340
|
) // singular -> plural aliases
|
|
341
|
+
.concat(['// events'])
|
|
342
|
+
.concat(this.events.fqs.map(({fq, name}) => `module.exports.${name} = '${fq}'`))
|
|
314
343
|
.concat(['// actions'])
|
|
315
344
|
.concat(this.actions.names.map(name => `module.exports.${name} = '${name}'`))
|
|
316
345
|
.concat(['// enums'])
|
|
@@ -473,7 +502,7 @@ export namespace Composition {
|
|
|
473
502
|
}
|
|
474
503
|
|
|
475
504
|
export class Entity {
|
|
476
|
-
static data<T extends Entity> (this:T,
|
|
505
|
+
static data<T extends Entity> (this:T, _input:Object) : T {
|
|
477
506
|
return {} as T // mock
|
|
478
507
|
}
|
|
479
508
|
}
|
package/lib/visitor.js
CHANGED
|
@@ -132,17 +132,18 @@ class Visitor {
|
|
|
132
132
|
this.visitElement(ename, element, file, buffer)
|
|
133
133
|
}
|
|
134
134
|
for (const [aname, action] of Object.entries(entity.actions ?? {})) {
|
|
135
|
+
const lambdaString =
|
|
135
136
|
buffer.add(
|
|
136
|
-
SourceFile.stringifyLambda(
|
|
137
|
-
aname,
|
|
138
|
-
Object.entries(action.params ?? {}).map(([n, t]) => [
|
|
137
|
+
SourceFile.stringifyLambda({
|
|
138
|
+
name: aname,
|
|
139
|
+
parameters: Object.entries(action.params ?? {}).map(([n, t]) => [
|
|
139
140
|
n,
|
|
140
141
|
this.resolver.resolveAndRequire(t, file).typeName,
|
|
141
142
|
]),
|
|
142
|
-
action.returns ? this.resolver.resolveAndRequire(action.returns, file).typeName : 'any'
|
|
143
|
-
|
|
143
|
+
returns: action.returns ? this.resolver.resolveAndRequire(action.returns, file).typeName : 'any',
|
|
144
|
+
initialiser: `undefined as unknown as this['${aname}']`
|
|
145
|
+
})
|
|
144
146
|
)
|
|
145
|
-
//this.visitEntity(aname, action, file, buffer)
|
|
146
147
|
}
|
|
147
148
|
buffer.outdent()
|
|
148
149
|
buffer.add('};')
|
|
@@ -287,6 +288,26 @@ class Visitor {
|
|
|
287
288
|
this._aspectify(name, aspect, file.aspects, clean)
|
|
288
289
|
}
|
|
289
290
|
|
|
291
|
+
#printEvent(name, event) {
|
|
292
|
+
this.logger.debug(`Printing event ${name}`)
|
|
293
|
+
const clean = this.resolver.trimNamespace(name)
|
|
294
|
+
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
295
|
+
const file = this.getNamespaceFile(ns)
|
|
296
|
+
file.addEvent(clean, name)
|
|
297
|
+
const buffer = file.events.buffer
|
|
298
|
+
buffer.add('// event')
|
|
299
|
+
buffer.add(`export class ${clean} {`)
|
|
300
|
+
buffer.indent()
|
|
301
|
+
const propOpt = this.options.propertiesOptional
|
|
302
|
+
this.options.propertiesOptional = false
|
|
303
|
+
for (const [ename, element] of Object.entries(event.elements ?? {})) {
|
|
304
|
+
this.visitElement(ename, element, file, buffer)
|
|
305
|
+
}
|
|
306
|
+
this.options.propertiesOptional = propOpt
|
|
307
|
+
buffer.outdent()
|
|
308
|
+
buffer.add('}')
|
|
309
|
+
}
|
|
310
|
+
|
|
290
311
|
/**
|
|
291
312
|
* Visits a single entity from the CSN's definition field.
|
|
292
313
|
* Will call #printEntity or #printAction based on the entity's kind.
|
|
@@ -310,6 +331,9 @@ class Visitor {
|
|
|
310
331
|
case 'aspect':
|
|
311
332
|
this.#printAspect(name, entity)
|
|
312
333
|
break
|
|
334
|
+
case 'event':
|
|
335
|
+
this.#printEvent(name, entity)
|
|
336
|
+
break
|
|
313
337
|
default:
|
|
314
338
|
this.logger.error(`Unhandled entity kind '${entity.kind}'.`)
|
|
315
339
|
}
|