@api-client/core 0.5.8 → 0.5.11

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 (74) hide show
  1. package/build/browser.d.ts +2 -0
  2. package/build/browser.js +2 -0
  3. package/build/browser.js.map +1 -1
  4. package/build/index.d.ts +2 -0
  5. package/build/index.js +2 -0
  6. package/build/index.js.map +1 -1
  7. package/build/src/lib/calculators/DataCalculator.d.ts +3 -3
  8. package/build/src/lib/calculators/DataCalculator.js +3 -3
  9. package/build/src/lib/calculators/DataCalculator.js.map +1 -1
  10. package/build/src/lib/events/Utils.d.ts +1 -0
  11. package/build/src/lib/events/Utils.js +6 -0
  12. package/build/src/lib/events/Utils.js.map +1 -0
  13. package/build/src/lib/parsers/UriTemplate.d.ts +94 -0
  14. package/build/src/lib/parsers/UriTemplate.js +419 -0
  15. package/build/src/lib/parsers/UriTemplate.js.map +1 -0
  16. package/build/src/lib/parsers/UrlEncoder.d.ts +5 -0
  17. package/build/src/lib/parsers/UrlEncoder.js +49 -0
  18. package/build/src/lib/parsers/UrlEncoder.js.map +1 -1
  19. package/build/src/models/Environment.js +1 -0
  20. package/build/src/models/Environment.js.map +1 -1
  21. package/build/src/models/HttpProject.d.ts +4 -1
  22. package/build/src/models/HttpProject.js +9 -6
  23. package/build/src/models/HttpProject.js.map +1 -1
  24. package/build/src/models/ProjectParent.d.ts +9 -0
  25. package/build/src/models/ProjectParent.js +25 -0
  26. package/build/src/models/ProjectParent.js.map +1 -1
  27. package/build/src/models/Property.d.ts +8 -0
  28. package/build/src/models/Property.js +17 -0
  29. package/build/src/models/Property.js.map +1 -1
  30. package/build/src/models/Request.js +1 -1
  31. package/build/src/models/Request.js.map +1 -1
  32. package/build/src/models/RequestAuthorization.js +2 -1
  33. package/build/src/models/RequestAuthorization.js.map +1 -1
  34. package/build/src/models/Server.d.ts +14 -1
  35. package/build/src/models/Server.js +31 -1
  36. package/build/src/models/Server.js.map +1 -1
  37. package/build/src/models/store/Capabilities.d.ts +1 -1
  38. package/build/src/models/store/File.d.ts +59 -8
  39. package/build/src/models/store/File.js +144 -27
  40. package/build/src/models/store/File.js.map +1 -1
  41. package/build/src/models/store/Permission.d.ts +16 -1
  42. package/build/src/models/store/Permission.js +27 -1
  43. package/build/src/models/store/Permission.js.map +1 -1
  44. package/build/src/runtime/actions/runnable/DeleteCookieRunnable.d.ts +1 -1
  45. package/build/src/runtime/actions/runnable/SetCookieRunnable.d.ts +1 -1
  46. package/build/src/runtime/actions/runnable/SetVariableRunnable.d.ts +1 -1
  47. package/build/src/runtime/http-engine/CoreEngine.d.ts +1 -1
  48. package/build/src/runtime/store/FilesSdk.d.ts +16 -0
  49. package/build/src/runtime/store/FilesSdk.js +53 -0
  50. package/build/src/runtime/store/FilesSdk.js.map +1 -1
  51. package/build/src/runtime/store/RouteBuilder.d.ts +4 -0
  52. package/build/src/runtime/store/RouteBuilder.js +6 -0
  53. package/build/src/runtime/store/RouteBuilder.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/lib/calculators/DataCalculator.ts +3 -3
  56. package/src/lib/events/Utils.ts +5 -0
  57. package/src/lib/parsers/UriTemplate.ts +494 -0
  58. package/src/lib/parsers/UrlEncoder.ts +51 -0
  59. package/src/models/Environment.ts +1 -0
  60. package/src/models/HttpProject.ts +10 -7
  61. package/src/models/ProjectParent.ts +27 -0
  62. package/src/models/Property.ts +18 -0
  63. package/src/models/Request.ts +1 -1
  64. package/src/models/RequestAuthorization.ts +2 -1
  65. package/src/models/Server.ts +32 -1
  66. package/src/models/store/Capabilities.ts +1 -1
  67. package/src/models/store/File.ts +169 -28
  68. package/src/models/store/Permission.ts +30 -2
  69. package/src/runtime/actions/runnable/DeleteCookieRunnable.ts +1 -1
  70. package/src/runtime/actions/runnable/SetCookieRunnable.ts +1 -1
  71. package/src/runtime/actions/runnable/SetVariableRunnable.ts +1 -1
  72. package/src/runtime/http-engine/CoreEngine.ts +1 -1
  73. package/src/runtime/store/FilesSdk.ts +54 -0
  74. package/src/runtime/store/RouteBuilder.ts +7 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@api-client/core",
3
3
  "description": "The API Client's core client library. Works in NodeJS and in a ES enabled browser.",
4
- "version": "0.5.8",
4
+ "version": "0.5.11",
5
5
  "license": "Apache-2.0",
6
6
  "main": "build/index.js",
7
7
  "module": "build/index.js",
@@ -24,7 +24,7 @@ export class DataCalculator {
24
24
  * @param str A string to compute size from.
25
25
  * @returns The size of the string.
26
26
  */
27
- stringSize(str: string): number {
27
+ static stringSize(str: string): number {
28
28
  if (!str || !str.length || typeof str !== 'string') {
29
29
  return 0;
30
30
  }
@@ -49,7 +49,7 @@ export class DataCalculator {
49
49
  * @param data The size of the form data
50
50
  * @returns The size of the form data
51
51
  */
52
- async formDataSize(data: FormData): Promise<number> {
52
+ static async formDataSize(data: FormData): Promise<number> {
53
53
  if (typeof Request === 'undefined') {
54
54
  return 0;
55
55
  }
@@ -70,7 +70,7 @@ export class DataCalculator {
70
70
  * @param payload The payload to compute te size for
71
71
  * @returns The size of the payload
72
72
  */
73
- async payloadSize(payload: unknown): Promise<number> {
73
+ static async payloadSize(payload: unknown): Promise<number> {
74
74
  if (!payload) {
75
75
  return 0;
76
76
  }
@@ -0,0 +1,5 @@
1
+ export function cancelEvent(e: Event): void {
2
+ e.preventDefault();
3
+ e.stopImmediatePropagation();
4
+ e.stopPropagation();
5
+ }
@@ -0,0 +1,494 @@
1
+ import { UrlEncoder } from './UrlEncoder.js';
2
+
3
+ const cache = new Map<string, UriTemplate>();
4
+
5
+ /**
6
+ * pattern to identify expressions [operator, variable-list] in template
7
+ */
8
+ const EXPRESSION_PATTERN = /\{([^a-zA-Z0-9%_]?)([^}]+)(\}|$)/g;
9
+
10
+ /**
11
+ * pattern to identify variables [name, explode, maxlength] in variable-list
12
+ */
13
+ const VARIABLE_PATTERN = /^([^*:.](?:\.?[^*:.])*)((\*)|:(\d+))?$/;
14
+
15
+ /**
16
+ * pattern to verify variable name integrity
17
+ */
18
+ const VARIABLE_NAME_PATTERN = /[^a-zA-Z0-9%_.]/;
19
+
20
+ /**
21
+ * pattern to verify literal integrity
22
+ */
23
+ const LITERAL_PATTERN = /[<>{}"`^| \\]/;
24
+
25
+ enum DataType {
26
+ // undefined/null
27
+ nil,
28
+ // string
29
+ string,
30
+ // object
31
+ object,
32
+ // array
33
+ array,
34
+ }
35
+
36
+ interface IData {
37
+ /**
38
+ * type of data 0: undefined/null, 1: string, 2: object, 3: array
39
+ */
40
+ type: DataType;
41
+ /**
42
+ * original values (except undefined/null)
43
+ */
44
+ val: (string | undefined)[][];
45
+ /**
46
+ * cache for encoded values (only for non-maxlength expansion)
47
+ */
48
+ strictEncode: (string | undefined)[][];
49
+ encodeReserved: (string | undefined)[][]
50
+ }
51
+
52
+ class Data {
53
+ cache: Record<string, IData>;
54
+ constructor(public data: Record<string, any>) {
55
+ this.cache = {};
56
+ }
57
+
58
+ get(key: string): IData {
59
+ const { data } = this;
60
+ const d: IData = {
61
+ type: DataType.nil,
62
+ val: [],
63
+ strictEncode: [],
64
+ encodeReserved: [],
65
+ };
66
+
67
+ if (this.cache[key] !== undefined) {
68
+ // we've already processed this key
69
+ return this.cache[key];
70
+ }
71
+
72
+ this.cache[key] = d;
73
+
74
+ let value: any;
75
+ if (typeof data === 'function') {
76
+ value = data(key);
77
+ } else if (typeof data[key] === 'function') {
78
+ value = data[key](key);
79
+ } else {
80
+ value = data[key];
81
+ }
82
+
83
+ // generalize input into [ [name1, value1], [name2, value2], … ]
84
+ // so expansion has to deal with a single data structure only
85
+ if (value === undefined || value === null) {
86
+ // undefined and null values are to be ignored completely
87
+ return d;
88
+ } else if (Array.isArray(value)) {
89
+ value.forEach((v) => {
90
+ if (v !== undefined && v !== null) {
91
+ // arrays don't have names
92
+ d.val.push([undefined, v])
93
+ }
94
+ });
95
+ if (d.val.length) {
96
+ d.type = DataType.array;
97
+ }
98
+ } else if (String(Object.prototype.toString.call(value)) === '[object Object]') {
99
+ Object.keys(value).forEach((k) => {
100
+ const v = value[k];
101
+ if (v !== undefined && v !== null) {
102
+ d.val.push([k, v])
103
+ }
104
+ });
105
+ if (d.val.length) {
106
+ d.type = DataType.object;
107
+ }
108
+ } else {
109
+ d.type = DataType.string;
110
+ d.val.push([undefined, String(value)]);
111
+ }
112
+
113
+ return d;
114
+ }
115
+ }
116
+
117
+ interface IOperator {
118
+ prefix: string,
119
+ separator: string;
120
+ named: boolean;
121
+ empty_name_separator: boolean;
122
+ encode: 'strictEncode' | 'encodeReserved';
123
+ }
124
+
125
+ interface IVariable {
126
+ name: string;
127
+ explode: boolean;
128
+ maxlength?: number;
129
+ }
130
+
131
+ interface IPart {
132
+ expression: string;
133
+ operator: string;
134
+ variables: IVariable[];
135
+ }
136
+
137
+ export interface IUriTemplateOptions {
138
+ /**
139
+ * Throws when a variable is not found to replaces a value in the template.
140
+ */
141
+ strict?: boolean;
142
+ /**
143
+ * When set it ignores replacing the value in the template when the variable is missing.
144
+ */
145
+ ignoreMissing?: boolean;
146
+ }
147
+
148
+ const operators: Record<string, IOperator> = {
149
+ // Simple string expansion
150
+ '': {
151
+ prefix: '',
152
+ separator: ',',
153
+ named: false,
154
+ empty_name_separator: false,
155
+ encode: 'strictEncode'
156
+ },
157
+ // Reserved character strings
158
+ '+': {
159
+ prefix: '',
160
+ separator: ',',
161
+ named: false,
162
+ empty_name_separator: false,
163
+ encode: 'encodeReserved'
164
+ },
165
+ // Fragment identifiers prefixed by '#'
166
+ '#': {
167
+ prefix: '#',
168
+ separator: ',',
169
+ named: false,
170
+ empty_name_separator: false,
171
+ encode: 'encodeReserved'
172
+ },
173
+ // Name labels or extensions prefixed by '.'
174
+ '.': {
175
+ prefix: '.',
176
+ separator: '.',
177
+ named: false,
178
+ empty_name_separator: false,
179
+ encode: 'strictEncode'
180
+ },
181
+ // Path segments prefixed by '/'
182
+ '/': {
183
+ prefix: '/',
184
+ separator: '/',
185
+ named: false,
186
+ empty_name_separator: false,
187
+ encode: 'strictEncode'
188
+ },
189
+ // Path parameter name or name=value pairs prefixed by ';'
190
+ ';': {
191
+ prefix: ';',
192
+ separator: ';',
193
+ named: true,
194
+ empty_name_separator: false,
195
+ encode: 'strictEncode'
196
+ },
197
+ // Query component beginning with '?' and consisting
198
+ // of name=value pairs separated by '&'; an
199
+ '?': {
200
+ prefix: '?',
201
+ separator: '&',
202
+ named: true,
203
+ empty_name_separator: true,
204
+ encode: 'strictEncode'
205
+ },
206
+ // Continuation of query-style &name=value pairs
207
+ // within a literal query component.
208
+ '&': {
209
+ prefix: '&',
210
+ separator: '&',
211
+ named: true,
212
+ empty_name_separator: true,
213
+ encode: 'strictEncode'
214
+ }
215
+
216
+ // The operator characters equals ("="), comma (","), exclamation ("!"),
217
+ // at sign ("@"), and pipe ("|") are reserved for future extensions.
218
+ };
219
+
220
+ /**
221
+ * Processor for URI templates: http://tools.ietf.org/html/rfc6570
222
+ */
223
+ export class UriTemplate {
224
+ parts?: (string | IPart)[];
225
+
226
+ /**
227
+ * Allows to ache and reuse cached instances.
228
+ *
229
+ * @param uri The URI string.
230
+ * @returns
231
+ */
232
+ static fromUri(uri: string): UriTemplate {
233
+ let cached = cache.get(uri);
234
+ if (!cached) {
235
+ cached = new UriTemplate(uri);
236
+ cache.set(uri, cached);
237
+ }
238
+ return cached;
239
+ }
240
+
241
+ /**
242
+ * @param expression The URI string.
243
+ */
244
+ constructor(protected expression: string) { }
245
+
246
+ /**
247
+ * Expands the template with the given map values.
248
+ *
249
+ * @param map The map with values
250
+ * @param opts Processing options
251
+ * @returns The expanded URI.
252
+ */
253
+ expand(map: Record<string, any>, opts: IUriTemplateOptions = {}): string {
254
+ let result = '';
255
+ if (!this.parts || !this.parts.length) {
256
+ this.parse();
257
+ }
258
+ const data = new Data(map);
259
+
260
+ for (const part of this.parts!) {
261
+ const item = typeof part === 'string' ? part : UriTemplate.expand(part, data, opts);
262
+ result += item;
263
+ }
264
+ return result;
265
+ }
266
+
267
+ /**
268
+ * Parses the template into action tokens.
269
+ */
270
+ parse(): void {
271
+ const { expression } = this;
272
+ const parts: (string | IPart)[] = [];
273
+ let pos = 0;
274
+
275
+ function checkLiteral(literal: string): string {
276
+ if (literal.match(LITERAL_PATTERN)) {
277
+ throw new Error(`Invalid Literal "${literal}"`);
278
+ }
279
+ return literal;
280
+ }
281
+
282
+ EXPRESSION_PATTERN.lastIndex = 0;
283
+
284
+ // eslint-disable-next-line no-constant-condition
285
+ while (true) {
286
+ const eMatch = EXPRESSION_PATTERN.exec(expression);
287
+ if (eMatch === null) {
288
+ // push trailing literal
289
+ parts.push(checkLiteral(expression.substring(pos)));
290
+ break;
291
+ } else {
292
+ // push leading literal
293
+ parts.push(checkLiteral(expression.substring(pos, eMatch.index)));
294
+ pos = eMatch.index + eMatch[0].length;
295
+ }
296
+ if (!operators[eMatch[1]]) {
297
+ throw new Error(`Unknown Operator "${eMatch[1]}" in "${eMatch[0]}"`);
298
+ } else if (!eMatch[3]) {
299
+ throw new Error(`Unclosed Expression "${eMatch[0]}"`);
300
+ }
301
+
302
+ // parse variable-list
303
+ const varParts = eMatch[2].split(',');
304
+ const vars: IVariable[] = [];
305
+
306
+ for (var i = 0, l = varParts.length; i < l; i++) {
307
+ const vMatch = varParts[i].match(VARIABLE_PATTERN);
308
+ if (vMatch === null) {
309
+ throw new Error(`Invalid Variable "${varParts[i]}" in "${eMatch[0]}"`);
310
+ } else if (vMatch[1].match(VARIABLE_NAME_PATTERN)) {
311
+ throw new Error(`Invalid Variable Name "${vMatch[1]}" in ""${eMatch[0]}"`);
312
+ }
313
+
314
+ vars[i] = {
315
+ name: vMatch[1],
316
+ explode: !!vMatch[3],
317
+ maxlength: vMatch[4] && parseInt(vMatch[4], 10) || undefined,
318
+ };
319
+ }
320
+
321
+ if (!vars.length) {
322
+ throw new Error(`Expression Missing Variable(s) "${eMatch[0]}"`);
323
+ }
324
+ parts.push({
325
+ expression: eMatch[0],
326
+ operator: eMatch[1],
327
+ variables: vars,
328
+ });
329
+ }
330
+
331
+ if (!parts.length) {
332
+ // template doesn't contain any expressions
333
+ // so it is a simple literal string
334
+ // this probably should fire a warning or something?
335
+ parts.push(checkLiteral(expression));
336
+ }
337
+
338
+ this.parts = parts;
339
+ }
340
+
341
+ protected static expand(expression: IPart, data: Data, opts: IUriTemplateOptions = {}): string {
342
+ const options = operators[expression.operator];
343
+ const type = options.named ? 'Named' : 'Unnamed';
344
+ const { variables } = expression;
345
+ const buffer = [];
346
+
347
+ for (const variable of variables) {
348
+ const d = data.get(variable.name);
349
+ if (d.type === DataType.nil && opts && opts.strict) {
350
+ throw new Error(`Missing expansion value for variable "${variable.name}"`);
351
+ }
352
+ if (d.type === DataType.nil && opts.ignoreMissing) {
353
+ buffer.push(expression.expression)
354
+ continue
355
+ }
356
+ if (!d.val.length) {
357
+ if (d.type !== DataType.nil) {
358
+ // empty variables (empty string) still lead to a separator being appended!
359
+ buffer.push('');
360
+ }
361
+ continue;
362
+ }
363
+ if (d.type > DataType.string && variable.maxlength) {
364
+ throw new Error(`Invalid expression: Prefix modifier not applicable to variable "${variable.name}"`);
365
+ }
366
+ buffer.push(UriTemplate[`expand${type}`](
367
+ d,
368
+ options,
369
+ variable.explode,
370
+ variable.explode && options.separator || ',',
371
+ variable.maxlength,
372
+ variable.name
373
+ ));
374
+ }
375
+
376
+ if (buffer.length) {
377
+ return options.prefix + buffer.join(options.separator);
378
+ }
379
+ // prefix is not prepended for empty expressions
380
+ return '';
381
+ }
382
+
383
+ /**
384
+ * Expands a named variable.
385
+ */
386
+ protected static expandNamed(d: IData, options: IOperator, explode: boolean, separator: string, length?: number, name?: string): string {
387
+ let result = '';
388
+ const { encode, empty_name_separator } = options;
389
+ const _encode = !d[encode].length;
390
+ let _name = d.type === DataType.object ? '' : UrlEncoder[encode](name!);
391
+
392
+ d.val.forEach((item, index) => {
393
+ let _value;
394
+ if (length) {
395
+ // maxlength must be determined before encoding can happen
396
+ _value = UrlEncoder[encode](item[1]!.substring(0, length));
397
+ if (d.type === DataType.object) {
398
+ // apply maxlength to keys of objects as well
399
+ _name = UrlEncoder[encode](item[0]!.substring(0, length));
400
+ }
401
+ } else if (_encode) {
402
+ // encode value
403
+ _value = UrlEncoder[encode](item[1]!);
404
+ if (d.type === DataType.object) {
405
+ // encode name and cache encoded value
406
+ _name = UrlEncoder[encode](item[0]!);
407
+ d[encode].push([_name, _value]);
408
+ } else {
409
+ // cache encoded value
410
+ d[encode].push([undefined, _value]);
411
+ }
412
+ } else {
413
+ // values are already encoded and can be pulled from cache
414
+ _value = d[encode][index][1];
415
+ if (d.type === DataType.object) {
416
+ _name = d[encode][index][0]!;
417
+ }
418
+ }
419
+
420
+ if (result) {
421
+ result += separator;
422
+ }
423
+
424
+ if (!explode) {
425
+ if (!index) {
426
+ result += UrlEncoder[encode](name!) + (empty_name_separator || _value ? '=' : '');
427
+ }
428
+ if (d.type === DataType.object) {
429
+ result += `${_name},`;
430
+ }
431
+ result += _value;
432
+ } else {
433
+ result += _name + (empty_name_separator || _value ? '=' : '') + _value;
434
+ }
435
+ });
436
+
437
+ return result;
438
+ }
439
+
440
+ /**
441
+ * Expands an unnamed variable.
442
+ */
443
+ protected static expandUnnamed(d: IData, options: IOperator, explode: boolean, separator: string, length?: number): string {
444
+ let result = '';
445
+ const { encode, empty_name_separator } = options;
446
+ const _encode = !d[encode].length;
447
+
448
+ d.val.forEach((item, index) => {
449
+ let _value: string;
450
+ if (length) {
451
+ // maxlength must be determined before encoding can happen
452
+ _value = UrlEncoder[encode](item[1]!.substring(0, length));
453
+ } else if (_encode) {
454
+ // encode and cache value
455
+ _value = UrlEncoder[encode](item[1]!);
456
+ d[encode].push([
457
+ d.type === DataType.object ? UrlEncoder[encode](item[0]!) : undefined,
458
+ _value
459
+ ]);
460
+ } else {
461
+ // value already encoded, pull from cache
462
+ _value = d[encode][index][1]!;
463
+ }
464
+
465
+ if (result) {
466
+ // unless we're the first value, prepend the separator
467
+ result += separator;
468
+ }
469
+
470
+ if (d.type === DataType.object) {
471
+ let _name;
472
+ if (length) {
473
+ // maxlength also applies to keys of objects
474
+ _name = UrlEncoder[encode](item[0]!.substring(0, length));
475
+ } else {
476
+ // at this point the name must already be encoded
477
+ _name = d[encode][index][0];
478
+ }
479
+
480
+ result += _name;
481
+ if (explode) {
482
+ // explode-modifier separates name and value by "="
483
+ result += (empty_name_separator || _value ? '=' : '');
484
+ } else {
485
+ // no explode-modifier separates name and value by ","
486
+ result += ',';
487
+ }
488
+ }
489
+
490
+ result += _value;
491
+ });
492
+ return result;
493
+ }
494
+ }
@@ -71,4 +71,55 @@ export class UrlEncoder {
71
71
  }
72
72
  return decodeURIComponent(result);
73
73
  }
74
+
75
+ static strictEncode(str: string): string {
76
+ if (!str) {
77
+ return str;
78
+ }
79
+ const escaped = {
80
+ '!': '%21',
81
+ "'": '%27',
82
+ '(': '%28',
83
+ ')': '%29',
84
+ };
85
+ return encodeURIComponent(str).replace(/\*/g, '%2A')
86
+ // @ts-ignore
87
+ .replace(/[!'()*]/g, (c) => escaped[c] );
88
+ }
89
+
90
+ /**
91
+ * For URI templates encodes the URL string without encoding the reserved characters.
92
+ */
93
+ static encodeReserved(str: string): string {
94
+ if (!str) {
95
+ return str;
96
+ }
97
+ const expression = /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig;
98
+ const map = {
99
+ // gen-delims
100
+ '%3A': ':',
101
+ '%2F': '/',
102
+ '%3F': '?',
103
+ '%23': '#',
104
+ '%5B': '[',
105
+ '%5D': ']',
106
+ '%40': '@',
107
+ // sub-delims
108
+ '%21': '!',
109
+ '%24': '$',
110
+ '%26': '&',
111
+ '%27': '\'',
112
+ '%28': '(',
113
+ '%29': ')',
114
+ '%2A': '*',
115
+ '%2B': '+',
116
+ '%2C': ',',
117
+ '%3B': ';',
118
+ '%3D': '='
119
+ };
120
+ let result = UrlEncoder.strictEncode(str);
121
+ // @ts-ignore
122
+ result = result.replace(expression, (c) => map[c]);
123
+ return result;
124
+ }
74
125
  }
@@ -197,6 +197,7 @@ export class Environment {
197
197
  result.server = server.toJSON();
198
198
  }
199
199
  if (this.security) {
200
+ // TODO: When this is defined then call toJSON().
200
201
  result.security = this.security;
201
202
  }
202
203
  return result;
@@ -1107,6 +1107,16 @@ export class HttpProject extends ProjectParent {
1107
1107
  return result.reverse();
1108
1108
  }
1109
1109
 
1110
+ /**
1111
+ * @returns Returns the effective environments. If the project has been initialized with an environment then it is returned. Otherwise other environments.
1112
+ */
1113
+ getEnvironments(): Environment[] {
1114
+ if (Array.isArray(this.initEnvironments)) {
1115
+ return this.initEnvironments;
1116
+ }
1117
+ return super.getEnvironments();
1118
+ }
1119
+
1110
1120
  /**
1111
1121
  * Makes a copy of this project.
1112
1122
  */
@@ -1257,13 +1267,6 @@ export class HttpProject extends ProjectParent {
1257
1267
  return this.definitions.schemas;
1258
1268
  }
1259
1269
 
1260
- getEnvironments(): Environment[] {
1261
- if (Array.isArray(this.initEnvironments)) {
1262
- return this.initEnvironments;
1263
- }
1264
- return super.getEnvironments();
1265
- }
1266
-
1267
1270
  /**
1268
1271
  * Iterates over requests in the project.
1269
1272
  */
@@ -110,4 +110,31 @@ export abstract class ProjectParent implements ProjectDefinitionProperty {
110
110
  });
111
111
  return result;
112
112
  }
113
+
114
+ /**
115
+ * @param key The environment key to read.
116
+ */
117
+ getEnvironment(key: string): Environment | undefined {
118
+ const { environments } = this;
119
+ if (!environments.length) {
120
+ return undefined;
121
+ }
122
+ const has = environments.includes(key);
123
+ if (!has) {
124
+ return undefined;
125
+ }
126
+ const project = this.getProject();
127
+ if (!project.definitions.environments) {
128
+ return undefined;
129
+ }
130
+ return project.definitions.environments.find(i => i.key === key);
131
+ }
132
+
133
+ /**
134
+ * This is a link to the `getEnvironments()`. The difference is that on the
135
+ * project level it won't return environments defined with the class initialization.
136
+ */
137
+ listEnvironments(): Environment[] {
138
+ return this.getEnvironments();
139
+ }
113
140
  }
@@ -420,4 +420,22 @@ export class Property {
420
420
  }
421
421
  return result;
422
422
  }
423
+
424
+ /**
425
+ * Maps the list of properties to a single map.
426
+ * It overrides previously defined properties on the map in case of multiple variables with the same name.
427
+ *
428
+ * @param properties The list of properties to map.
429
+ * @returns A map where keys are property names and values are the property values.
430
+ */
431
+ static toMap(properties: (IProperty | Property)[]): Record<string, any> {
432
+ const result: Record<string, any> = {};
433
+ properties.forEach(p => {
434
+ if (p.enabled === false) {
435
+ return;
436
+ }
437
+ result[p.name] = p.value;
438
+ });
439
+ return result;
440
+ }
423
441
  }
@@ -401,7 +401,7 @@ export class Request {
401
401
  result.actions = this.actions.toJSON();
402
402
  }
403
403
  if (this.clientCertificate) {
404
- result.clientCertificate = this.clientCertificate;
404
+ result.clientCertificate = { ...this.clientCertificate };
405
405
  }
406
406
  return result;
407
407
  }