@sap/cds-dk 4.5.0 → 4.6.0

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 (42) hide show
  1. package/CHANGELOG.md +65 -9
  2. package/bin/bind.js +124 -8
  3. package/bin/cds-ts.js +1 -1
  4. package/bin/cds.js +1 -0
  5. package/bin/deploy/index.js +2 -1
  6. package/bin/env.js +50 -9
  7. package/bin/help.js +2 -1
  8. package/bin/import.js +126 -24
  9. package/bin/watch.js +45 -9
  10. package/lib/bind/bindingResolver.js +138 -0
  11. package/lib/bind/cfKeyProvider.js +353 -0
  12. package/lib/bind/userCdsrcJsonStorageProvider.js +144 -0
  13. package/lib/client/auth_manager.js +4 -4
  14. package/lib/client/settings_manager.js +1 -1
  15. package/lib/import/common.js +33 -1
  16. package/lib/import/importapi.js +4 -3
  17. package/lib/import/index.js +49 -23
  18. package/lib/import/message.js +11 -6
  19. package/lib/import/utils/MetadataConverter.js +25 -4
  20. package/lib/import/utils/MetadataConverterFactory.js +1 -2
  21. package/lib/import/utils/MultiMap.js +26 -0
  22. package/lib/import/v2parser.js +142 -24
  23. package/lib/import/v4parser.js +222 -149
  24. package/lib/init/constants.js +1 -1
  25. package/lib/init/index.js +2 -0
  26. package/lib/init/template/lint/files/.eslint/rules/no-entity-moo.js +36 -52
  27. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/rule.test.js +15 -0
  28. package/lib/init/template/lint/index.js +14 -6
  29. package/lib/init/template/mta/files/mta.yaml.hbs +0 -7
  30. package/lib/init/template/mtx/files/xs-security.json.hbs +3 -1
  31. package/lib/init/template/mtx/package/dependencies.package.json +1 -1
  32. package/lib/init/template/nodejs/files/.gitignore.hbs +3 -0
  33. package/lib/init/util/commandUtil.js +5 -0
  34. package/lib/lint/Linter.js +31 -20
  35. package/lib/lint/checks.js +5 -1
  36. package/lib/lint/generators.js +1 -1
  37. package/lib/lint/io.js +59 -35
  38. package/lib/util/term.js +2 -0
  39. package/lib/watch/node-watch.js +38 -19
  40. package/npm-shrinkwrap.json +1276 -1102
  41. package/package.json +4 -3
  42. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/test.js +0 -10
@@ -0,0 +1,144 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const readline = require('readline');
4
+
5
+ const { fse } = require('@sap/cds-foss');
6
+ const { highlight, warn } = require('../util/term');
7
+
8
+ const commandUtil = require('../init/util/commandUtil');
9
+
10
+ const CDSRC_FILENAME = '.cdsrc-private.json';
11
+ const GIT_IGNORE_FILE = '.gitignore';
12
+
13
+ class UserCdsrcJsonStorageProvider {
14
+
15
+ async askBooleanQuestion(question) {
16
+ const rl = readline.createInterface({
17
+ input: process.stdin,
18
+ output: process.stdout
19
+ });
20
+
21
+ question = question.trim() + ' ';
22
+ return new Promise(resolve => {
23
+ rl.question(question, answer => {
24
+ if (answer === 'y' || answer === 'Y') {
25
+ resolve(true);
26
+ } else {
27
+ resolve(false);
28
+ }
29
+ rl.close();
30
+ });
31
+ });
32
+ }
33
+
34
+ async ensureFileIsGitignored(file, logger = console) {
35
+ try {
36
+ await commandUtil.spawnCommand('git', ['check-ignore', '-n', '-v', file], {}, undefined, true);
37
+ return; // file is git ignored
38
+ } catch (err) {
39
+ if (err.code === 'ENOENT') { // git command not found
40
+ logger.debug('git command not found');
41
+ return;
42
+ }
43
+
44
+ if (!err.stdLog.match(/::/)) { // included or not a git repo
45
+ logger.debug(err);
46
+ return;
47
+ }
48
+ }
49
+
50
+ logger.log(`adding entry '${file}' to ${GIT_IGNORE_FILE}.`);
51
+ let gitignore = await this.readFileSafely(GIT_IGNORE_FILE);
52
+ gitignore = gitignore + `
53
+ # added by cds bind
54
+ ${file}
55
+ `;
56
+ await fse.writeFile(GIT_IGNORE_FILE, gitignore, 'utf8');
57
+ }
58
+
59
+ async readFileSafely(file) {
60
+ try {
61
+ return await fse.readFile(file, 'utf8');
62
+ } catch (err) {
63
+ return '';
64
+ }
65
+ }
66
+
67
+ async storeServices(services, options = {}) {
68
+ let jsonPath = options['output-file'] || CDSRC_FILENAME;
69
+
70
+ if (jsonPath === CDSRC_FILENAME) {
71
+ await this.ensureFileIsGitignored(CDSRC_FILENAME, options.logger);
72
+ }
73
+
74
+ jsonPath = jsonPath.replace(/~/g, os.homedir());
75
+ // const stat = await fse.stat(jsonPath);
76
+ if (path.extname(jsonPath) !== '.json') {
77
+ jsonPath = path.join(jsonPath, '.cdsrc.json');
78
+ }
79
+
80
+ // if (options['save-credentials'] && services.find(service => service.credentials) && process.stdin.isTTY) {
81
+ // const answer = await this.askBooleanQuestion(warn(`IF YOU CONTINUE, SENSITIVE DATA WILL BE WRITTEN TO AN UNENCRYPTED FILE [${jsonPath}]! Do you want to continue [y/N] ?`));
82
+ // if (!answer) {
83
+ // options.logger.log(`aborting cds bind; not writing any sensitive data to file [${jsonPath}]`);
84
+ // return;
85
+ // }
86
+ // }
87
+
88
+ const jsonObject = await this.readJsonSafely(jsonPath);
89
+ let cdsRoot;
90
+ if (path.basename(jsonPath) === 'package.json') {
91
+ jsonObject.cds = jsonObject.cds || {};
92
+ cdsRoot = jsonObject.cds;
93
+ } else {
94
+ cdsRoot = jsonObject;
95
+ }
96
+
97
+ const profileKey = `[${options.profile}]`;
98
+ cdsRoot.features = cdsRoot.features || {};
99
+ cdsRoot.features[profileKey] = cdsRoot.features[profileKey] || {};
100
+ cdsRoot.features[profileKey].emulate_vcap_services = true;
101
+
102
+ cdsRoot.requires = cdsRoot.requires || {};
103
+ let section;
104
+ if (options.profile) {
105
+ section = cdsRoot.requires[profileKey] = cdsRoot.requires[profileKey] || {};
106
+ } else {
107
+ section = cdsRoot.requires;
108
+ }
109
+
110
+ for (const service of services) {
111
+ const { name, kind, binding, credentials } = service;
112
+ const requireService = section[name] = section[name] || {};
113
+
114
+ Object.assign(requireService, { kind, binding, credentials });
115
+
116
+ options.logger.log(`Binding ${highlight(name)} to Cloud Foundry service ${highlight(binding.instance)} key ${highlight(binding.key)} with kind ${highlight(kind)}.`);
117
+ }
118
+
119
+ await this.writeJsonFile(jsonPath, jsonObject, options.logger);
120
+ if (options.profile) {
121
+ options.logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.profile)} .`);
122
+ } else {
123
+ options.logger.log(`Saving bindings to ${highlight(jsonPath)}.`);
124
+ }
125
+ }
126
+
127
+ async readJsonSafely(filePath) {
128
+ try {
129
+ return await fse.readJSON(filePath) || {};
130
+ } catch (err) {
131
+ return {};
132
+ }
133
+ }
134
+
135
+ async writeJsonFile(filePath, content) {
136
+ await fse.outputJSON(filePath, content, {
137
+ spaces: 2
138
+ });
139
+
140
+ await fse.chmod(filePath, 0o600); // -rw-------
141
+ }
142
+ }
143
+
144
+ module.exports = new UserCdsrcJsonStorageProvider();
@@ -26,12 +26,12 @@ function getTokenUrl(params) {
26
26
  tokenUrl.search = `subdomain=${params.get('subdomain')}`;
27
27
 
28
28
  if (params.has('refreshToken')) {
29
- tokenUrl.search += `&refresh_token=${params.get('refreshToken')}`;
29
+ tokenUrl.search += `&refresh_token=${encodeURIComponent(params.get('refreshToken'))}`;
30
30
  } else if (params.has('passcode')) {
31
- tokenUrl.search += `&passcode=${params.get('passcode')}`;
31
+ tokenUrl.search += `&passcode=${encodeURIComponent(params.get('passcode'))}`;
32
32
  }
33
33
  if (params.has('clientid')) {
34
- tokenUrl.search += `&clientid=${params.get('clientid')}&clientsecret=${params.get('clientsecret')}`;
34
+ tokenUrl.search += `&clientid=${encodeURIComponent(params.get('clientid'))}&clientsecret=${encodeURIComponent(params.get('clientsecret'))}`;
35
35
  }
36
36
  return tokenUrl.toString();
37
37
  }
@@ -58,7 +58,7 @@ async function retrieveTokenOrPasscodeUrl(params) {
58
58
 
59
59
  let response;
60
60
  try {
61
- response = await axios(tokenUrl.toString(), { method: 'GET' });
61
+ response = await axios(tokenUrl, { method: 'GET' });
62
62
  } catch (error) {
63
63
  throw new Error(`Failed to get authentication token: ${error}.\nResponse body: ${util.inspect(response && response.data)}`);
64
64
  }
@@ -55,7 +55,7 @@ function other(tokenStorage) {
55
55
 
56
56
  function getProjectFolder(params) {
57
57
  const relativePath = params.get('projectFolder') || params.get('directory') || (
58
- params.has('subdomain') && fse.existsSync(path.resolve(params.get('subdomain')))
58
+ params.has('subdomain') && fse.existsSync(params.get('subdomain'))
59
59
  ? params.get('subdomain')
60
60
  : '.'
61
61
  );
@@ -21,6 +21,23 @@ async function _generateEDMX2JSON(edmx) {
21
21
  return JSON.parse(cjson);
22
22
  }
23
23
 
24
+ function checkForEmptyKeys(element, oDataVersion) {
25
+ if (oDataVersion === "V2") {
26
+ return (element.includes(["cds.Association"]) && !element.includes(['"on":']));
27
+ }
28
+ else {
29
+ return (['cds.Association'].includes(element.type)) && !element.on && !element.keys;
30
+ }
31
+ }
32
+
33
+ //to replace the alias value with original schema namespace value
34
+ function replaceAliasValue(schemaContent, schemaNamespaceValue, aliasValue){
35
+ const re = RegExp(`([("/])${aliasValue}([/").])`, 'g');
36
+ const val = `$1${schemaNamespaceValue}$2`;
37
+ schemaContent = schemaContent.replace(re, val);
38
+ return JSON.parse(schemaContent);
39
+ }
40
+
24
41
  async function _getODataVersion(edmx) {
25
42
  let oDataVersion;
26
43
  let jsonObj;
@@ -91,6 +108,21 @@ async function getVersion(fileInputStream) {
91
108
  return oDataVersion;
92
109
  }
93
110
 
111
+ function getSuffix(outputFormat) {
112
+ const suffix = { edmx: 'xml', "edmx-v2": 'xml', "edmx-v4": 'xml', "edmx-w4": 'xml',
113
+ "edmx-x4": 'xml', openapi: 'openapi3.json', sql: 'sql', edm: 'json', "cdl": "cds" };
114
+ // find the output file extension based on the target conversion selected
115
+ return (outputFormat) ? (suffix[outputFormat] || outputFormat) : 'csn';
116
+ }
117
+
118
+ function supportedFormats() {
119
+ return ["cds", "csn", "json"];
120
+ }
121
+
94
122
  module.exports = {
95
- getVersion
123
+ getVersion,
124
+ getSuffix,
125
+ supportedFormats,
126
+ checkForEmptyKeys,
127
+ replaceAliasValue
96
128
  };
@@ -1,5 +1,7 @@
1
1
  const edm2csn = require('.');
2
-
2
+ let context={};
3
+ context.oDataInfo = {};
4
+ context.namespaces = [];
3
5
  /**
4
6
  * This is the import API function to generate CSN from XML/EDMX files.
5
7
  * @param {string} filepath
@@ -10,11 +12,10 @@ const edm2csn = require('.');
10
12
  * - other import options not supported
11
13
  * @returns {object} CSN
12
14
  */
13
- const oDataInfo={};
14
15
  module.exports = async function cds_import(filepath, options = {keepNamespace : false}) //namespace will be taken as false in case it is not mentioned
15
16
  {
16
17
  filepath = await edm2csn.isValidInputFile(filepath);
17
18
  let edmx= await edm2csn.getMetadataFromFile(filepath);
18
- let csn = await edm2csn.edmx2csn(edmx, !options.keepNamespace && filepath,oDataInfo);
19
+ let csn = await edm2csn.edmx2csn(edmx, !options.keepNamespace && filepath, context);
19
20
  return csn;
20
21
  }
@@ -42,36 +42,35 @@ async function isValidInputFile(inputFileLoc) {
42
42
  return file;
43
43
  }
44
44
 
45
- function validateAndComputeOutputFilePath(outputFileLoc) {
46
- let filePath, index, extension;
45
+ function validateAndComputeOutputFilePath(outputFileLoc, as) {
46
+ let index, extension, expectedExtension, supportedExtension;
47
47
  if (typeof(outputFileLoc) !== 'boolean') {
48
48
  index = outputFileLoc.lastIndexOf(".");
49
49
  extension = outputFileLoc.substring(index + 1);
50
+ expectedExtension = (as) ? common.getSuffix(as) : "csn";
51
+ supportedExtension = (index > 0) ? common.supportedFormats().includes(extension) : false;
50
52
  }
53
+
51
54
  if (outputFileLoc === true || // if output file is not mentioned
55
+ outputFileLoc[0] === "-" || // if output file is missing and "--as" is considered as output file
52
56
  outputFileLoc.trim() === "") {
53
57
  throw new Error(messages.SPECIFY_OUTPUT_FILE);
54
- } else if (outputFileLoc.endsWith(".json") || outputFileLoc.endsWith(".csn")) {
55
- filePath = outputFileLoc;
56
- } else if (index > 0 &&
57
- !extension.endsWith("/") && // to ignore "../test/lib/import/output/tempOutput/"
58
- !extension.endsWith("\\") && // to ignore "c:\windows\output\test\"
59
- extension !== 'json' &&
60
- extension !== 'csn') {
61
- // Output file extension is not either .json or .csn ("test.abcde", "./srv/external/test.abcde")
58
+ } else if (index > 0 && !supportedExtension) {
59
+ // extension not supported, eg., "test.abs", "./sv/external.test.abcs", "./a."
62
60
  throw new Error(messages.INVALID_OUTPUT_FILE);
63
- } else if (!outputFileLoc.endsWith(".json") &&
64
- !outputFileLoc.endsWith(".csn") &&
65
- !outputFileLoc.endsWith("\\") &&
66
- !outputFileLoc.endsWith("/")) {
67
- // Output file extension is not mentioned ("./srv/external/test", "test", "c:\windows\output\test")
68
- filePath = outputFileLoc + ".csn";
61
+ } else if (index > 0 && !as && extension != "json" && extension != "csn") {
62
+ // --as option is not provided but output file is like: "test.cds", "./srv/external/test.cds"
63
+ throw new Error(messages.INCORRECT_EXTENSION);
64
+ } else if (index > 0 && as && extension != expectedExtension){
65
+ // extension not supported by --as option, e.g., "cds import <file> --out test.yml --as cds"
66
+ throw new Error(messages.OUTPUT_FILE_MISMATCH);
67
+ } else if (supportedExtension || (!outputFileLoc.endsWith("\\") && !outputFileLoc.endsWith("/"))) {
68
+ // eg., "./a.csn", "tst.cds", "./srv/external/test", "test", "c:\windows\output\test"
69
+ return outputFileLoc;
69
70
  } else if (outputFileLoc.endsWith("/") || outputFileLoc.endsWith("\\")){
70
71
  // File name not specified ("./srv/external/", "c:\windows\output\test\")
71
- filePath = outputFileLoc + path.parse(defaultFileName).name + ".csn";
72
+ return outputFileLoc + path.parse(defaultFileName).name;
72
73
  }
73
-
74
- return filePath;
75
74
  }
76
75
 
77
76
  async function getMetadataFromFile(path) {
@@ -117,7 +116,15 @@ function _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace) {
117
116
  </Schema>
118
117
  </edmx:DataServices>
119
118
  */
120
- edmx = edmx.replace(multipleSchemas, '').replace(/<Schema Namespace="[^"]+"/, `<Schema Namespace="${namespace}"`);
119
+
120
+ // Remove the ending and beginning of the first and second schema tag
121
+ edmx = edmx.replace(multipleSchemas, '');
122
+ // Now capture the schema tag details
123
+ let schemaDetails = edmx.match(/(<Schema [^>]*>)/);
124
+ // Replace the Namespace value with the correct one
125
+ schemaDetails = schemaDetails[0].replace(/Namespace="[^"]+"/, `Namespace="${namespace}"`);
126
+ // Place the above computed schema details in the edmx content
127
+ edmx = edmx.replace(/(<Schema [^>]*>)/, `${schemaDetails}`);
121
128
 
122
129
  return edmx;
123
130
  }
@@ -156,17 +163,29 @@ async function generateCSN(edmx, ignorePersistenceSkip, mockServerUc, context) {
156
163
  let csn;
157
164
  let odataVersion;
158
165
  let edmx2jsonModel;
166
+ let defaultNamespaces;
167
+ let namespaces = [];
168
+ if(context.namespaces) namespaces.push(...(context.namespaces));
159
169
  odataVersion = await common.getVersion(edmx);
160
170
  if (odataVersion === "V2") {
161
171
  if(context){
162
172
  context.oDataVersion = "V2";
163
173
  }
174
+ //default namespaces to be present
175
+ if(!context.include_all_namespaces)
176
+ {
177
+ defaultNamespaces = ['edmx','sap','m'];
178
+ namespaces = namespaces.concat(defaultNamespaces);
179
+ namespaces = namespaces.filter((item,pos) => namespaces.indexOf(item) === pos); //removing duplicate values
180
+ }
181
+ context.namespaces = namespaces;
164
182
  edmx2jsonModel = await _generateEDMX2JSON(edmx);
165
183
  return new Promise(function getCsn(resolve, reject) {
166
184
  csn = edmxv2CSN.getEdmxv2CSN(
167
185
  edmx2jsonModel,
168
186
  ignorePersistenceSkip,
169
- mockServerUc
187
+ mockServerUc,
188
+ context
170
189
  );
171
190
  if (csn) {
172
191
  resolve(csn);
@@ -179,13 +198,20 @@ async function generateCSN(edmx, ignorePersistenceSkip, mockServerUc, context) {
179
198
  if(context){
180
199
  context.oDataVersion = "V4";
181
200
  }
182
- return edmxv4CSN.getEdmxv4CSN(edmx, ignorePersistenceSkip, mockServerUc);
201
+ //default namespaces to be present
202
+ if(!context.include_all_namespaces)
203
+ {
204
+ defaultNamespaces = ['edmx'];
205
+ namespaces = namespaces.concat(defaultNamespaces);
206
+ namespaces = namespaces.filter((item,pos) => namespaces.indexOf(item) === pos); //removing duplicate values
207
+ }
208
+ context.namespaces = namespaces;
209
+ return edmxv4CSN.getEdmxv4CSN(edmx, ignorePersistenceSkip, mockServerUc, context);
183
210
  }
184
211
  return new Promise(function invalidFile(resolve, reject) {
185
212
  reject(new Error(messages.INVALID_EDMX_METADATA));
186
213
  });
187
214
  }
188
-
189
215
  async function saveCSNModel(csnDataModel, fileLoc) {
190
216
  await fs.write(fileLoc, csnDataModel, "utf-8");
191
217
  }
@@ -1,14 +1,19 @@
1
1
  "use strict";
2
2
 
3
3
  const messages = {
4
- INVALID_EDMX_METADATA: "The OData model is not valid. Specify the correct OData model.",
5
- INVALID_OUTPUT_FILE: "Please provide an output file with either .json or .csn extension.",
6
- MISSING_ENTITIES: "There are no entities in the OData model. Specify the correct OData model in the provided file.",
7
- SPECIFY_INPUT_FILE: "Specify an input file that exists.",
8
- SPECIFY_OUTPUT_FILE: "Specify an output file.",
4
+ INVALID_EDMX_METADATA: 'The OData model is not valid. Specify the correct OData model.',
5
+ INVALID_OUTPUT_FILE: 'Please provide an output file with one of these [cds, csn, json] file extension.',
6
+ MISSING_ENTITIES: 'There are no entities in the OData model. Specify the correct OData model in the provided file.',
7
+ SPECIFY_INPUT_FILE: 'Specify an input file that exists.',
8
+ SPECIFY_OUTPUT_FILE: 'Specify an output file or correct the output file path provided.',
9
9
  MISSING_ENTITY_CONTAINER: 'There is no entity container in the OData model. Specify the correct OData model.',
10
10
  MISSING_ENTITY_SETS: 'There are no entity sets in the OData model. Specify the correct OData model.',
11
- INVALID_INPUT_FILE: 'Input file provided is not valid. Please provide a file with either .edmx or .xml extension.'
11
+ INVALID_INPUT_FILE: 'Input file provided is not valid. Please provide a file with either .edmx or .xml extension.',
12
+ FILE_MODIFIED: 'The file with the same name exists and is been modified. Use --force flag to forcefully overwrite the file.',
13
+ OUTPUT_FILE_MISMATCH: 'Specify the file extension that --as option supports.',
14
+ INVALID_AS_OPTION: 'For --as option, please specify one of these output file format [cds, csn, json].',
15
+ INCORRECT_EXTENSION: 'If --as option is not provided, then please provide either .json or .csn output file extension.',
16
+ INCORRECT_INPUT_NAMESPACES: 'Invalid value for --include-namespaces option. Specify namespaces as comma-separated list or "*"'
12
17
  };
13
18
 
14
19
  function getMessages() {
@@ -396,7 +396,7 @@ class Expression {
396
396
  const target = this.getTarget();
397
397
  const element = this.getElement();
398
398
  const attribs = element.attributes;
399
-
399
+ let custom_namespace = MetadataConverter.custom_namespace;
400
400
  if (attribs == null) return;
401
401
  let realType = attribs.Type || attribs.EntityType || attribs.UnderlyingType;
402
402
  const propertyName = attribs.UnderlyingType == null ? '$Type' : '$UnderlyingType';
@@ -574,9 +574,29 @@ class Expression {
574
574
  !(target.$Kind === 'NavigationProperty' && target.$Collection === true)) {
575
575
  target.$Nullable = true;
576
576
  }
577
+ const nonStdAttributes = this.getNonStdAttributes(element.attributes);
578
+ for (let i = 0; i < nonStdAttributes.length; i++) {
579
+ let attr = nonStdAttributes[i];
580
+ let [prefix, attrName] = attr.split(':');
581
+ if(attrName)
582
+ attrName = attrName.replace(/-/g,'.');
583
+ let attribute;
584
+ if ((custom_namespace.includes(prefix) && attrName !== undefined) || custom_namespace.includes(true))
585
+ attribute = "@" + prefix + "." + attrName;
586
+ else if (attrName === undefined)
587
+ attribute = "$" + prefix;
588
+ if (attribute !== undefined)
589
+ target[attribute] = element.attributes[attr];
590
+ }
591
+ }
592
+ getNonStdAttributes(attributes) {
593
+ const attributeList = [];
594
+ const standardArrtibutes = ['MaxLength', 'Precision', 'Scale', 'Nullable', 'DefaultValue',
595
+ 'Name', 'Type', 'HasStream', 'IsFlags', 'Alias', 'Namespace', 'BaseType', 'Partner',
596
+ 'IsComposable', 'ContainsTarget', 'IsBound', 'EntitySet', 'EntitySetPath', 'EntityType'];
597
+ attributeList.push(...Object.keys(attributes).filter(attribute => standardArrtibutes.indexOf(attribute) === -1));
598
+ return attributeList;
577
599
  }
578
-
579
-
580
600
  }
581
601
 
582
602
  class EdmPrimitiveType {
@@ -2361,7 +2381,8 @@ class OnDeleteExpression extends Expression {
2361
2381
  }
2362
2382
 
2363
2383
 
2364
- MetadataConverter.createOdataV4MetadataXmlToOdataV4CsdlStrategy = () => {
2384
+ MetadataConverter.createOdataV4MetadataXmlToOdataV4CsdlStrategy = (name_space) => {
2385
+ MetadataConverter.custom_namespace = name_space;
2365
2386
  return new MetadataConverter.DefaultXmlStrategy()
2366
2387
  .use('http://docs.oasis-open.org/odata/ns/edmx:Edmx', (element, parentExpression, context) => {
2367
2388
  return new EdmxRootExpression(element, null, context);
@@ -1,5 +1,4 @@
1
1
  'use strict';
2
-
3
2
  const xmljs = require('xml-js');
4
3
  const MetadataConverterV4 = require('../utils/MetadataConverter');
5
4
 
@@ -61,7 +60,7 @@ class MetadataConverterFactory {
61
60
  *
62
61
  */
63
62
  static createEdmxV40XmlToV40Json(options = {}) {
64
- const defaultStrategy = MetadataConverterV4.createOdataV4MetadataXmlToOdataV4CsdlStrategy();
63
+ const defaultStrategy = MetadataConverterV4.createOdataV4MetadataXmlToOdataV4CsdlStrategy(options);
65
64
 
66
65
  if (options.astFactory) {
67
66
  defaultStrategy.setASTFactory(options.astFactory);
@@ -67,6 +67,32 @@ var Multimap = (function () {
67
67
  return this;
68
68
  };
69
69
 
70
+ /**
71
+ * @return {Array} all the keys in the map
72
+ */
73
+ Multimap.prototype.keys = function () {
74
+ if (this._map)
75
+ return makeIterator(this._.keys());
76
+
77
+ return makeIterator(Object.keys(this._));
78
+ };
79
+
80
+ function makeIterator(iterator) {
81
+ if (Array.isArray(iterator)) {
82
+ var nextIndex = 0;
83
+
84
+ return {
85
+ next: function () {
86
+ return nextIndex < iterator.length ?
87
+ { value: iterator[nextIndex++], done: false } :
88
+ { done: true };
89
+ }
90
+ };
91
+ }
92
+
93
+ return iterator;
94
+ }
95
+
70
96
  return Multimap;
71
97
  })();
72
98