@sap/cds-compiler 4.5.0 → 4.6.2

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +50 -7
  2. package/bin/cdsc.js +13 -11
  3. package/doc/CHANGELOG_BETA.md +6 -0
  4. package/lib/api/main.js +256 -115
  5. package/lib/api/options.js +8 -0
  6. package/lib/base/message-registry.js +17 -4
  7. package/lib/base/messages.js +15 -3
  8. package/lib/base/model.js +1 -0
  9. package/lib/base/optionProcessorHelper.js +45 -176
  10. package/lib/checks/elements.js +32 -34
  11. package/lib/checks/enricher.js +39 -3
  12. package/lib/checks/validator.js +2 -3
  13. package/lib/compiler/assert-consistency.js +2 -1
  14. package/lib/compiler/builtins.js +20 -4
  15. package/lib/compiler/checks.js +30 -6
  16. package/lib/compiler/define.js +31 -9
  17. package/lib/compiler/populate.js +5 -1
  18. package/lib/compiler/resolve.js +26 -21
  19. package/lib/compiler/shared.js +19 -9
  20. package/lib/compiler/tweak-assocs.js +82 -107
  21. package/lib/compiler/utils.js +2 -1
  22. package/lib/edm/annotations/edmJson.js +23 -22
  23. package/lib/edm/annotations/genericTranslation.js +14 -4
  24. package/lib/edm/csn2edm.js +24 -10
  25. package/lib/edm/edmInboundChecks.js +1 -2
  26. package/lib/edm/edmPreprocessor.js +11 -9
  27. package/lib/edm/edmUtils.js +5 -2
  28. package/lib/gen/Dictionary.json +3 -1
  29. package/lib/gen/language.checksum +1 -1
  30. package/lib/gen/language.interp +4 -1
  31. package/lib/gen/language.tokens +1 -0
  32. package/lib/gen/languageParser.js +5253 -5214
  33. package/lib/json/to-csn.js +7 -1
  34. package/lib/language/antlrParser.js +19 -1
  35. package/lib/language/errorStrategy.js +21 -4
  36. package/lib/language/genericAntlrParser.js +9 -11
  37. package/lib/main.d.ts +28 -3
  38. package/lib/main.js +3 -0
  39. package/lib/model/csnRefs.js +4 -1
  40. package/lib/model/csnUtils.js +12 -7
  41. package/lib/optionProcessor.js +21 -19
  42. package/lib/render/manageConstraints.js +13 -29
  43. package/lib/render/toCdl.js +18 -15
  44. package/lib/render/toHdbcds.js +59 -28
  45. package/lib/render/toRename.js +6 -10
  46. package/lib/render/toSql.js +57 -82
  47. package/lib/render/utils/common.js +17 -0
  48. package/lib/transform/.eslintrc.json +9 -1
  49. package/lib/transform/addTenantFields.js +228 -0
  50. package/lib/transform/db/applyTransformations.js +27 -31
  51. package/lib/transform/db/assertUnique.js +4 -4
  52. package/lib/transform/db/cdsPersistence.js +1 -1
  53. package/lib/transform/db/flattening.js +68 -69
  54. package/lib/transform/db/temporal.js +1 -1
  55. package/lib/transform/draft/db.js +2 -16
  56. package/lib/transform/draft/odata.js +3 -3
  57. package/lib/transform/effective/associations.js +3 -5
  58. package/lib/transform/effective/main.js +6 -9
  59. package/lib/transform/forOdata.js +13 -9
  60. package/lib/transform/forRelationalDB.js +36 -17
  61. package/lib/transform/odata/toFinalBaseType.js +3 -3
  62. package/lib/transform/odata/typesExposure.js +14 -5
  63. package/lib/transform/transformUtils.js +47 -34
  64. package/lib/transform/translateAssocsToJoins.js +33 -8
  65. package/package.json +2 -2
@@ -6,6 +6,8 @@
6
6
 
7
7
  const { csnRefs } = require('../model/csnRefs');
8
8
  const { setProp } = require('../base/model');
9
+ const { xprInAnnoProperties } = require('../compiler/builtins');
10
+
9
11
  /**
10
12
  * The following properties are attached as non-enumerable where appropriate:
11
13
  *
@@ -16,9 +18,10 @@ const { setProp } = require('../base/model');
16
18
  *- `$path` has the csnPath to reach that property.
17
19
  *
18
20
  * @param {CSN.Model} csn CSN to enrich in-place
21
+ * @param {enrichCsnOptions} [options={}]
19
22
  * @returns {{ csn: CSN.Model, cleanup: () => void }} CSN with all ref's pre-resolved
20
23
  */
21
- function enrichCsn( csn ) {
24
+ function enrichCsn( csn, options ) {
22
25
  const transformers = {
23
26
  elements: dictionary,
24
27
  definitions: dictionary,
@@ -31,8 +34,7 @@ function enrichCsn( csn ) {
31
34
  target,
32
35
  includes: simpleRef,
33
36
  columns,
34
- // Annotations are ignored.
35
- '@': () => { /* ignore annotations */ },
37
+ '@': annotation,
36
38
  };
37
39
  let cleanupCallbacks = [];
38
40
 
@@ -83,6 +85,35 @@ function enrichCsn( csn ) {
83
85
  csnPath.pop();
84
86
  }
85
87
 
88
+ /**
89
+ * Transformer for things that are annotations. When we have a "=" plus an expression of some sorts,
90
+ * we treat it like a "standard" thing.
91
+ *
92
+ * @param {object | Array} _parent the thing that has _prop
93
+ * @param {string|number} _prop the name of the current property or index
94
+ * @param {object} node The value of node[_prop]
95
+ */
96
+ function annotation( _parent, _prop, node ) {
97
+ if (options.processAnnotations) {
98
+ if (node?.['='] !== undefined && xprInAnnoProperties.some(xProp => node[xProp] !== undefined)) {
99
+ standard(_parent, _prop, node);
100
+ }
101
+ else if (node && typeof node === 'object') {
102
+ csnPath.push(_prop);
103
+
104
+ if (Array.isArray(node)) {
105
+ node.forEach( (n, i) => annotation( node, i, n ) );
106
+ }
107
+ else {
108
+ for (const name of Object.getOwnPropertyNames( node ))
109
+ annotation( node, name, node[name] );
110
+ }
111
+
112
+ csnPath.pop();
113
+ }
114
+ }
115
+ }
116
+
86
117
  // eslint-disable-next-line jsdoc/require-jsdoc
87
118
  function columns( parent, prop, node ) {
88
119
  // Establish the link relationships
@@ -178,3 +209,8 @@ function enrichCsn( csn ) {
178
209
  }
179
210
 
180
211
  module.exports = enrichCsn;
212
+
213
+ /**
214
+ * @typedef {object} enrichCsnOptions
215
+ * @property {boolean} [processAnnotations=false] Wether to process annotations and call custom transformers on them
216
+ */
@@ -121,12 +121,11 @@ const forOdataQueryValidators = [];
121
121
  const commonMemberValidators
122
122
  = [ validateOnCondition, validateForeignKeys,
123
123
  validateAssociationsInItems, checkForInvalidTarget,
124
- checkVirtualElement ];
124
+ checkVirtualElement, checkManagedAssoc ];
125
125
 
126
126
  // TODO: checkManagedAssoc is a forEachMemberRecursively!
127
127
  const commonArtifactValidators = [
128
128
  checkTypeDefinitionHasType,
129
- checkManagedAssoc,
130
129
  checkRecursiveTypeUsage,
131
130
  ];
132
131
  // TODO: Does it make sense to run the on-condition check as part of a CSN validator?
@@ -150,7 +149,7 @@ function _validate( csn, that,
150
149
  artifactValidators = [],
151
150
  queryValidators = [],
152
151
  iterateOptions = {} ) {
153
- const { cleanup } = enrich(csn);
152
+ const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });
154
153
 
155
154
  applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });
156
155
 
@@ -480,7 +480,8 @@ function assertConsistency( model, stage ) {
480
480
  optional: [
481
481
  'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
482
482
  // expressions as annotation values
483
- '$tokenTexts', 'op', 'args', 'func', '_artifact', 'type',
483
+ '$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
484
+ 'scale', 'srid', 'length', 'precision',
484
485
  ],
485
486
  // TODO: restrict path to #simplePath
486
487
  },
@@ -32,6 +32,7 @@ const core = {
32
32
  Timestamp: { category: 'dateTime' },
33
33
  Boolean: { category: 'boolean' },
34
34
  UUID: { category: 'string' },
35
+ Vector: { parameters: [ 'length' /* , 'type' */ ], category: 'vector' },
35
36
  Association: { internal: true, category: 'relation' },
36
37
  Composition: { internal: true, category: 'relation' },
37
38
  };
@@ -320,9 +321,7 @@ function checkDate( year, month, day ) {
320
321
  * Return whether JSON object `val` is a representation for an annotation expression
321
322
  */
322
323
  function isAnnotationExpression( val ) {
323
- // TODO: we might allow `'=': true`, not just a string, for expressions, to be
324
- // decided → just check truthy at the moment
325
- return val['='] && xprInAnnoProperties.some( prop => val[prop] !== undefined );
324
+ return val?.['='] !== undefined && xprInAnnoProperties.some( prop => val[prop] !== undefined );
326
325
  }
327
326
 
328
327
  /**
@@ -354,6 +353,7 @@ const typeCategories = {
354
353
  boolean: [],
355
354
  relation: [],
356
355
  geo: [],
356
+ vector: [],
357
357
  };
358
358
  // Fill type categories with `cds.*` types
359
359
  Object.keys( core ).forEach( (type) => {
@@ -399,6 +399,16 @@ function isBuiltinType( type ) {
399
399
  return typeof type === 'string' && isInReservedNamespace( type );
400
400
  }
401
401
 
402
+ /**
403
+ * Tell if a name is a magic variable
404
+ *
405
+ * @param {string} name
406
+ * @returns {boolean}
407
+ */
408
+ function isMagicVariable( name ) {
409
+ return typeof name === 'string' && Object.prototype.hasOwnProperty.call(magicVariables, name);
410
+ }
411
+
402
412
  /**
403
413
  * Add CDS builtins like the `cds` namespace with types like `cds.Integer` to
404
414
  * `definitions` of the XSN model as well as to `$builtins`.
@@ -411,9 +421,14 @@ function initBuiltins( model ) {
411
421
  // namespace:"cds" stores the builtins ---
412
422
  const cds = createNamespace( 'cds', 'reserved' );
413
423
  model.definitions.cds = cds;
424
+
414
425
  // Also add the core artifacts to model.definitions`
415
- model.$builtins = env( core, 'cds.', cds );
426
+ const c = { ...core };
427
+ if (!isBetaEnabled( model.options, 'vectorType' ))
428
+ delete c.Vector;
429
+ model.$builtins = env( c, 'cds.', cds );
416
430
  model.$builtins.cds = cds;
431
+
417
432
  // namespace:"cds.hana" stores HANA-specific builtins ---
418
433
  const hana = createNamespace( 'cds.hana', 'reserved' );
419
434
  model.definitions['cds.hana'] = hana;
@@ -534,5 +549,6 @@ module.exports = {
534
549
  isAnnotationExpression,
535
550
  isInReservedNamespace,
536
551
  isBuiltinType,
552
+ isMagicVariable,
537
553
  isGeoTypeName,
538
554
  };
@@ -15,6 +15,7 @@ const {
15
15
  forEachDefinition,
16
16
  forEachMember,
17
17
  forEachMemberRecursively,
18
+ isDeprecatedEnabled,
18
19
  } = require('../base/model');
19
20
  const { CompilerAssertion } = require('../base/error');
20
21
  const { typeParameters } = require('./builtins');
@@ -153,13 +154,18 @@ function check( model ) {
153
154
  while (effectiveType?.enum)
154
155
  effectiveType = (effectiveType._origin || effectiveType.type?._artifact)?._effectiveType;
155
156
 
156
- if (!effectiveType) {
157
- return; // e.g. illegal definition references, cycles, ...
157
+ if (!effectiveType || (effectiveType.type && !effectiveType.type._artifact)) {
158
+ return; // e.g. illegal definition references, cycles, unknown artifacts, …
158
159
  }
159
160
  else if (!art.type && !effectiveType.type && !effectiveType?.builtin) {
160
- error( 'type-missing-type', [ art.location, user ],
161
- { otherprop: 'type', prop: actualParams[0] },
162
- 'Missing $(OTHERPROP) property next to $(PROP)' );
161
+ // Special case for deprecated flag "ignore specified elements": The `type` property
162
+ // is lost in columns, but `length`,… are kept -> mismatch. This behavior is the
163
+ // same as in cds-compiler v3. See #12169 for details.
164
+ if (!isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' )) {
165
+ error( 'type-missing-type', [ art.location, user ],
166
+ { otherprop: 'type', prop: actualParams[0] },
167
+ 'Missing $(OTHERPROP) property next to $(PROP)' );
168
+ }
163
169
  return;
164
170
  }
165
171
 
@@ -815,6 +821,9 @@ function check( model ) {
815
821
  // Has been slightly adapted for model.vocabularies but comments need to be
816
822
  // adapted, etc.
817
823
  function checkAnnotationAssignment1( art, anno ) {
824
+ if (art.$contains?.$annotation)
825
+ checkAnnotationExpressions( anno, art );
826
+
818
827
  // Sanity checks (ignore broken assignments)
819
828
  if (!anno.name?.id)
820
829
  return;
@@ -882,7 +891,6 @@ function check( model ) {
882
891
  warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
883
892
  { '#': 'std', anno: anno.name.id } );
884
893
  }
885
-
886
894
  return;
887
895
  }
888
896
 
@@ -890,6 +898,22 @@ function check( model ) {
890
898
  checkValueAssignableTo( anno, anno, elementDecl, art );
891
899
  }
892
900
 
901
+ /**
902
+ * Check the expressions inside annotations.
903
+ */
904
+ function checkAnnotationExpressions( anno, art ) {
905
+ if (anno.$tokenTexts) {
906
+ checkGenericExpression( anno, art );
907
+ }
908
+ else if (anno.literal === 'array') {
909
+ anno.val.forEach( val => checkAnnotationExpressions( val, art ) );
910
+ }
911
+ else if (anno.literal === 'struct') {
912
+ const struct = Object.values(anno.struct);
913
+ struct.forEach(val => checkAnnotationExpressions( val, art ));
914
+ }
915
+ }
916
+
893
917
  // Check that annotation assignment 'value' (having 'path or 'literal' and
894
918
  // 'val') is potentially assignable to element 'element'. Complain on 'loc'
895
919
  // if not
@@ -940,6 +940,8 @@ function define( model ) {
940
940
  initExprForQuery( col.value, parent );
941
941
  initSelectItems( col, null, user ); // TODO: use col as user (i.e. remove param)
942
942
  }
943
+
944
+ initItemsLinks( col, parent._block );
943
945
  }
944
946
 
945
947
  if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
@@ -999,15 +1001,7 @@ function define( model ) {
999
1001
  // TODO: split extend from init
1000
1002
  const main = parent._main || parent;
1001
1003
  const isQueryExtension = construct.kind === 'extend' && main.query;
1002
- let obj = construct;
1003
- let { items } = obj;
1004
- while (items) {
1005
- setLink( items, '_outer', obj );
1006
- setLink( items, '_parent', obj._parent );
1007
- setLink( items, '_block', block );
1008
- obj = items;
1009
- items = obj.items;
1010
- }
1004
+ let obj = initItemsLinks( construct, block );
1011
1005
  if (obj.target && targetIsTargetAspect( obj )) {
1012
1006
  obj.targetAspect = obj.target;
1013
1007
  delete obj.target;
@@ -1172,6 +1166,14 @@ function define( model ) {
1172
1166
  elem.$syntax = 'calc';
1173
1167
  // TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
1174
1168
  createAndLinkCalcDepElement( elem );
1169
+
1170
+ // Special case (hack) for calculated elements that use associations+filter:
1171
+ // See "Notes on `$filtered`" in `ExposingAssocWithFilter.md` for details.
1172
+ if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
1173
+ delete elem.type;
1174
+ delete elem.on;
1175
+ delete elem.target;
1176
+ }
1175
1177
  }
1176
1178
  }
1177
1179
 
@@ -1199,6 +1201,26 @@ function define( model ) {
1199
1201
  }
1200
1202
  }
1201
1203
 
1204
+ /**
1205
+ * Initialize artifact links inside `obj.items` (for nested ones as well).
1206
+ * Does nothing, it `obj.items` does not exist.
1207
+ *
1208
+ * @param {XSN.Artifact} obj
1209
+ * @param {object} block
1210
+ * @return {XSN.Artifact}
1211
+ */
1212
+ function initItemsLinks( obj, block ) {
1213
+ let { items } = obj;
1214
+ while (items) {
1215
+ setLink( items, '_outer', obj );
1216
+ setLink( items, '_parent', obj._parent );
1217
+ setLink( items, '_block', block );
1218
+ obj = items;
1219
+ items = obj.items;
1220
+ }
1221
+ return obj;
1222
+ }
1223
+
1202
1224
  // To be reworked -------------------------------------------------------------
1203
1225
 
1204
1226
  // TODO: is only necessary for extensions - make special for extend/annotate
@@ -100,6 +100,8 @@ function populate( model ) {
100
100
  ? 'Composition'
101
101
  : true;
102
102
  const redirectInSubQueries = isDeprecatedEnabled( options, '_redirectInSubQueries' );
103
+ const ignoreSpecifiedElements
104
+ = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
103
105
 
104
106
  forEachDefinition( model, traverseElementEnvironments );
105
107
  while (newAutoExposed.length) {
@@ -542,7 +544,9 @@ function populate( model ) {
542
544
  ielem[prop].$priority = 'annotate';
543
545
  wasAnnotated = true;
544
546
  }
545
- else if (typePropertiesFromSpecifiedElements[prop]) {
547
+ else if (typePropertiesFromSpecifiedElements[prop] && !ignoreSpecifiedElements) {
548
+ // If ignoreSpecifiedElements is set, we ignore type properties of specified elements,
549
+ // similar to how it was done in cds-compiler v3. Only annotations are copied.
546
550
  if (!ielem.typeProps$)
547
551
  setLink( ielem, 'typeProps$', Object.create( null ) );
548
552
  // Note: At this point in time, effectiveType() was likely not called on the
@@ -519,8 +519,8 @@ function resolve( model ) {
519
519
  if (art.value) {
520
520
  if (art.$syntax === 'calc')
521
521
  checkCalculatedElement( art );
522
- else if (art.type && !art.$inferred )
523
- checkStructureCast( art );
522
+ else if (art.type && !art.$inferred)
523
+ checkColumnTypeCast( art );
524
524
  }
525
525
 
526
526
  resolveExprInAnnotations( art );
@@ -557,10 +557,12 @@ function resolve( model ) {
557
557
 
558
558
  // Check explicit types: If either side has one, so must the other.
559
559
  const sType = specifiedElement.type?._artifact;
560
- const iType = getInferredPropFromOrigin( 'type' )?._artifact || inferredElement;
560
+ const iTypeArt = getInferredPropFromOrigin( 'type' )?._artifact;
561
+ const iType = iTypeArt || inferredElement;
561
562
 
562
563
  // xor: could be missing a type;
563
564
  // FIXME: The coding above returns incorrect iType for expand on associations
565
+
564
566
  if (!specifiedElement.type && inferredElement.type) {
565
567
  error( 'query-mismatched-element', [ specifiedElement.location, user ], {
566
568
  '#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
@@ -568,8 +570,12 @@ function resolve( model ) {
568
570
  return;
569
571
  }
570
572
  // If specified type is `null`, type could not be resolved.
571
- else if (sType && sType !== iType) {
572
- const typeName = iType?.name && sType?.name ? 'typeName' : 'type';
573
+ else if (sType && sType !== iType &&
574
+ // Special case for $recompilation: allow one level of type indirection. See #12113.
575
+ (!model.options.$recompile || sType !== iType.type?._artifact)) {
576
+ const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
577
+ iType?.name && sType?.name && 'typeName' || // both types are named
578
+ 'type'; // unknown type names
573
579
  const othertype = typeName !== 'type' && iType || '';
574
580
  error( 'query-mismatched-element', [
575
581
  specifiedElement.type.location || specifiedElement.location, user,
@@ -815,24 +821,20 @@ function resolve( model ) {
815
821
  else if (!allowedInKind.includes( parent.kind )) {
816
822
  error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
817
823
  }
818
- else if (effectiveType( art )?.elements) {
824
+ else if (effectiveType( art )?.elements && !art.$inferred) {
819
825
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
820
- if (!art.$inferred) {
821
- if (art.type)
822
- error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
823
- else
824
- error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
825
- }
826
+ if (art.type)
827
+ error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
828
+ else
829
+ error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
826
830
  }
827
- else if (effectiveType( art )?.items) {
831
+ else if (effectiveType( art )?.items && !art.$inferred) {
828
832
  // For inferred (e.g. included) calc elements, this error is already emitted at the origin.
829
- if (!art.$inferred) {
830
- const isCast = art.type?.$inferred === 'cast';
831
- error( 'type-unexpected-many', [ (art.type || art.value).location, art ], {
832
- '#': (!art.type && 'calc-implicit') || (isCast && 'calc-cast') || 'calc',
833
- elemref: art.type ? undefined : { ref: art.value.path },
834
- } );
835
- }
833
+ const isCast = art.type?.$inferred === 'cast';
834
+ error( 'type-unexpected-many', [ (art.type || art.value).location, art ], {
835
+ '#': (!art.type && 'calc-implicit') || (isCast && 'calc-cast') || 'calc',
836
+ elemref: art.type ? undefined : { ref: art.value.path },
837
+ } );
836
838
  }
837
839
  else {
838
840
  const noTruthyAllowed = [ 'localized', 'key', 'virtual' ];
@@ -847,7 +849,7 @@ function resolve( model ) {
847
849
  }
848
850
  }
849
851
 
850
- function checkStructureCast( art ) {
852
+ function checkColumnTypeCast( art ) {
851
853
  const elem = (art.value.op?.val === 'cast')
852
854
  ? art.value.args[0]?._artifact
853
855
  : art.value._artifact;
@@ -856,6 +858,9 @@ function resolve( model ) {
856
858
  error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'to-structure' } );
857
859
  else if (elem.elements) // TODO: calc elements
858
860
  error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-structure' } );
861
+ else if (elem.target && !art.type._artifact?.target)
862
+ // allow cast to association -> that is already checked and denied elsewhere
863
+ error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-assoc' } );
859
864
  }
860
865
  }
861
866
 
@@ -105,7 +105,7 @@ function fns( model ) {
105
105
  dynamic: modelBuiltinsOrDefinitions,
106
106
  navigation: environment,
107
107
  notFound: undefinedDefinition,
108
- accept: acceptEntityOrAssoc,
108
+ accept: acceptQuerySource,
109
109
  noDep: '', // dependency special for from
110
110
  },
111
111
  type: {
@@ -335,9 +335,7 @@ function fns( model ) {
335
335
  if (expr.args && !first) {
336
336
  const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
337
337
  // TODO: re-think $expected
338
- first = callback.traverse // TODO: still in use?
339
- ? callback.traverse( args, exprCtx, user, callback )
340
- : args.find( e => traverseExpr( e, exprCtx, user, callback ) );
338
+ first = args.find( e => traverseExpr( e, exprCtx, user, callback ) );
341
339
  }
342
340
 
343
341
  first ??= expr.suffix && // fn( … ) OVER …
@@ -1281,15 +1279,17 @@ function fns( model ) {
1281
1279
  return false;
1282
1280
  }
1283
1281
 
1284
- function acceptEntityOrAssoc( art, user, ref ) { // for FROM
1282
+ function acceptQuerySource( art, user, ref ) { // for FROM
1285
1283
  const { path, scope } = ref;
1286
1284
  // see getPathItem(): how many path items are for the main artifact ref?
1287
1285
  const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
1288
- // at least the last main definition should be an entity
1286
+ // at least the last main definition should be an entity or an
1287
+ // event (if the user is also an event)
1289
1288
  // an additional check for target would need effectiveType()
1290
1289
  const source = path[artItemsCount - 1]._artifact;
1291
- if (source.kind !== 'entity') {
1292
- signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
1290
+ const variant = (user._main.kind === 'event') ? 'event' : 'std';
1291
+ if (source.kind !== 'entity' && !acceptEventProjectionSource( source, user )) {
1292
+ signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': variant } );
1293
1293
  return false;
1294
1294
  }
1295
1295
  if (source === art)
@@ -1297,10 +1297,20 @@ function fns( model ) {
1297
1297
  const assoc = Functions.effectiveType( art );
1298
1298
  if (assoc.target)
1299
1299
  return art; // TODO: use target here
1300
- signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
1300
+ signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': variant } );
1301
1301
  return false;
1302
1302
  }
1303
1303
 
1304
+ function acceptEventProjectionSource( source, user ) {
1305
+ if (user._main.kind !== 'event' || (source.kind !== 'event' && source.kind !== 'type'))
1306
+ return false;
1307
+ const effectiveType = Functions.effectiveType( source );
1308
+ if (!effectiveType)
1309
+ return false;
1310
+ const { kind } = effectiveType;
1311
+ return (kind === 'entity' || kind === 'event' || (kind === 'type' && effectiveType.elements));
1312
+ }
1313
+
1304
1314
  function acceptTypeOrElement( art, user, ref ) { // for type
1305
1315
  // was ['action', 'function'].includes( user._parent?.kind ))
1306
1316
  while (user._outer)