@openeo/js-client 2.0.0 → 2.3.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.
@@ -3,6 +3,7 @@ const Parameter = require('./parameter');
3
3
  const axios = require('axios').default;
4
4
  const Utils = require('@openeo/js-commons/src/utils');
5
5
  const ProcessUtils = require("@openeo/js-commons/src/processUtils");
6
+ const ProcessRegistry = require('@openeo/js-commons/src/processRegistry');
6
7
 
7
8
  const PROCESS_META = [
8
9
  "id", "summary", "description", "categories", "parameters", "returns",
@@ -83,7 +84,7 @@ class Builder {
83
84
  *
84
85
  * @async
85
86
  * @static
86
- * @param {?string} version
87
+ * @param {?string} [version=null]
87
88
  * @returns {Promise<Builder>}
88
89
  * @throws {Error}
89
90
  */
@@ -103,7 +104,7 @@ class Builder {
103
104
  *
104
105
  * @async
105
106
  * @static
106
- * @param {?string} url
107
+ * @param {string | null} url
107
108
  * @returns {Promise<Builder>}
108
109
  * @throws {Error}
109
110
  */
@@ -117,16 +118,17 @@ class Builder {
117
118
  *
118
119
  * Each process passed to the constructor is made available as object method.
119
120
  *
120
- * @param {Array.<Process>|Processes} processes - Either an array containing processes or an object compatible with `GET /processes` of the API.
121
+ * @param {Array.<Process>|Processes|ProcessRegistry} processes - Either an array containing processes or an object compatible with `GET /processes` of the API.
121
122
  * @param {?Builder} parent - The parent builder, usually only used by the Builder itself.
122
123
  * @param {string} id - A unique identifier for the process.
123
124
  */
124
125
  constructor(processes, parent = null, id = undefined) {
125
126
  /**
126
- * List of all process specifications.
127
- * @type {Array.<Process>}
127
+ * A unique identifier for the process.
128
+ * @public
129
+ * @type {string}
128
130
  */
129
- this.processes = [];
131
+ this.id = id;
130
132
  /**
131
133
  * The parent builder.
132
134
  * @type {?Builder}
@@ -149,57 +151,75 @@ class Builder {
149
151
  this.parameters = undefined;
150
152
 
151
153
  /**
152
- * A unique identifier for the process.
153
- * @public
154
- * @type {string}
154
+ * List of all non-namespaced process specifications.
155
+ * @type {ProcessRegistry}
155
156
  */
156
- this.id = id;
157
+ this.processes = null;
157
158
 
158
- if (Utils.isObject(processes) && Array.isArray(processes.processes)) {
159
- processes = processes.processes;
159
+ // Create process registry if not available yet
160
+ if (processes instanceof ProcessRegistry) {
161
+ this.processes = processes;
160
162
  }
161
- if (Array.isArray(processes)) {
162
- for(let process of processes) {
163
- try {
164
- this.addProcessSpec(process);
165
- } catch (error) {
166
- console.warn(error);
167
- }
168
- }
163
+ else if (Utils.isObject(processes) && Array.isArray(processes.processes)) {
164
+ this.processes = new ProcessRegistry(processes.processes);
165
+ }
166
+ else if (Array.isArray(processes)) {
167
+ this.processes = new ProcessRegistry(processes);
169
168
  }
170
169
  else {
171
170
  throw new Error("Processes are invalid; must be array or object according to the API.");
172
171
  }
172
+
173
+ // Create processes
174
+ this.processes.all().forEach(process => this.createFunction(process));
175
+ }
176
+
177
+ /**
178
+ * Creates a callable function on the builder object for a process.
179
+ *
180
+ * @param {Process} process
181
+ * @throws {Error}
182
+ */
183
+ createFunction(process) {
184
+ if (typeof this[process.id] !== 'undefined') {
185
+ throw new Error("Can't create function for process '" + process.id + "'. Already exists in Builder class.");
186
+ }
187
+
188
+ /**
189
+ * Implicitly calls the process with the given name on the back-end by adding it to the process.
190
+ *
191
+ * This is a shortcut for {@link Builder#process}.
192
+ *
193
+ * @param {...*} args - The arguments for the process.
194
+ * @returns {BuilderNode}
195
+ * @see Builder#process
196
+ */
197
+ this[process.id] = function(...args) {
198
+ // Don't use arrow functions, they don't support the arguments keyword.
199
+ return this.process(process.id, args);
200
+ };
173
201
  }
174
202
 
175
203
  /**
176
204
  * Adds a process specification to the builder so that it can be used to create a process graph.
177
205
  *
178
206
  * @param {Process} process - Process specification compliant to openEO API
207
+ * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
179
208
  * @throws {Error}
180
209
  */
181
- addProcessSpec(process) {
210
+ addProcessSpec(process, namespace = null) {
182
211
  if (!Utils.isObject(process)) {
183
212
  throw new Error("Process '" + process.id + "' must be an object.");
184
213
  }
185
- if (typeof this[process.id] === 'undefined') {
186
- this.processes.push(process);
187
- /**
188
- * Implicitly calls the process with the given name on the back-end by adding it to the process.
189
- *
190
- * This is a shortcut for {@link Builder#process}.
191
- *
192
- * @param {...*} args - The arguments for the process.
193
- * @returns {BuilderNode}
194
- * @see Builder#process
195
- */
196
- this[process.id] = function(...args) {
197
- // Don't use arrow functions, they don't support the arguments keyword.
198
- return this.process(process.id, args);
199
- };
214
+
215
+ if (!namespace) {
216
+ namespace = 'backend';
200
217
  }
201
- else {
202
- throw new Error("Can't create function for process '" + process.id + "'. Already exists in Builder class.");
218
+ this.processes.add(process, namespace);
219
+
220
+ // Create callable function for pre-defined processes
221
+ if (namespace === 'backend') {
222
+ this.createFunction(process);
203
223
  }
204
224
  }
205
225
 
@@ -281,13 +301,14 @@ class Builder {
281
301
  }
282
302
 
283
303
  /**
284
- * Returns the process specification for the given process identifier.
304
+ * Returns the process specification for the given process identifier and namespace (or `null`).
285
305
  *
286
- * @param {string} id
287
- * @returns {Process}
306
+ * @param {string} id - Process identifier
307
+ * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. user or backend namespace). EXPERIMENTAL!
308
+ * @returns {Process | null}
288
309
  */
289
- spec(id) {
290
- return this.processes.find(process => process.id === id);
310
+ spec(id, namespace = null) {
311
+ return this.processes.get(id, namespace);
291
312
  }
292
313
 
293
314
  /**
@@ -308,25 +329,32 @@ class Builder {
308
329
  }
309
330
 
310
331
  /**
311
- * Checks whether a process with the given id is supported by the back-end.
332
+ * Checks whether a process with the given id and namespace is supported by the back-end.
312
333
  *
313
- * @param {string} processId - The id of the process to call.
334
+ * @param {string} processId - The id of the process.
335
+ * @param {?string} [namespace=null] - Namespace of the process (default to `null`, i.e. pre-defined processes). EXPERIMENTAL!
314
336
  * @returns {boolean}
315
337
  */
316
- supports(processId) {
317
- return Utils.isObject(this.spec(processId));
338
+ supports(processId, namespace = null) {
339
+ return Boolean(this.spec(processId, namespace));
318
340
  }
319
341
 
320
342
  /**
321
343
  * Adds another process call to the process chain.
322
344
  *
323
- * @param {string} processId - The id of the process to call.
324
- * @param {object.<string, *>|Array} args - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process.
325
- * @param {?string} description - An optional description for the process call.
345
+ * @param {string} processId - The id of the process to call. To access a namespaced process, use the `process@namespace` notation.
346
+ * @param {object.<string, *>|Array} [args={}] - The arguments as key-value pairs or as array. For objects, they keys must be the parameter names and the values must be the arguments. For arrays, arguments must be specified in the same order as in the corresponding process.
347
+ * @param {?string} [description=null] - An optional description for the process call.
326
348
  * @returns {BuilderNode}
327
349
  */
328
350
  process(processId, args = {}, description = null) {
329
- let node = new BuilderNode(this, processId, args, description);
351
+ let namespace = null;
352
+ if (processId.includes('@')) {
353
+ let rest;
354
+ [processId, ...rest] = processId.split('@');
355
+ namespace = rest.join('@');
356
+ }
357
+ let node = new BuilderNode(this, processId, args, description, namespace);
330
358
  this.nodes[node.id] = node;
331
359
  return node;
332
360
  }
@@ -8,13 +8,15 @@ const BuilderNode = require('./node');
8
8
  * Operators: - (subtract), + (add), / (divide), * (multiply), ^ (power)
9
9
  *
10
10
  * It supports all mathematical functions (i.e. expects a number and returns a number) the back-end implements, e.g. `sqrt(x)`.
11
+ * For namespaced processes, use for example `process@namespace(x)` - EXPERIMENTAL!
11
12
  *
12
13
  * Only available if a builder is specified in the constructor:
13
14
  * You can refer to output from processes with a leading `#`, e.g. `#loadco1` if the node to refer to has the key `loadco1`.
14
15
  *
15
16
  * Only available if a parent node is set via `setNode()`:
16
17
  * Parameters can be accessed simply by name.
17
- * If the first parameter is a (labeled) array, the value for a specific index or label can be accessed by typing the numeric index or textual label with a $ in front, for example $B1 for the label B1 or $0 for the first element in the array. Numeric labels are not supported.
18
+ * If the first parameter is a (labeled) array, the value for a specific index or label can be accessed by typing the numeric index or textual label with a `$` in front, for example `$B1` for the label `B1` or `$0` for the first element in the array. Numeric labels are not supported.
19
+ * You can access subsequent parameters by adding additional `$` at the beginning, e.g. `$$0` to access the first element of an array in the second parameter, `$$$0` for the same in the third parameter etc.
18
20
  *
19
21
  * An example that computes an EVI (assuming the labels for the bands are `NIR`, `RED` and `BLUE`): `2.5 * ($NIR - $RED) / (1 + $NIR + 6 * $RED + (-7.5 * $BLUE))`
20
22
  */
@@ -145,18 +147,20 @@ class Formula {
145
147
 
146
148
  let callbackParams = this.builder.getParentCallbackParameters();
147
149
  // Array labels / indices
148
- if (typeof value === 'string' && value.startsWith('$') && callbackParams.length > 0) {
149
- let ref = value.substring(1);
150
- // Array access always refers to the first parameter passed
151
- return callbackParams[0][ref];
150
+ if (typeof value === 'string' && callbackParams.length > 0) {
151
+ let prefix = value.match(/^\$+/);
152
+ let count = prefix ? prefix[0].length : 0;
153
+ if (count > 0 && callbackParams.length >= count) {
154
+ let ref = value.substring(count);
155
+ return callbackParams[count-1][ref];
156
+ }
152
157
  }
158
+
153
159
  // Everything else is a parameter
154
- else {
155
- let parameter = new Parameter(value);
156
- // Add new parameter if it doesn't exist
157
- this.builder.addParameter(parameter);
158
- return parameter;
159
- }
160
+ let parameter = new Parameter(value);
161
+ // Add new parameter if it doesn't exist
162
+ this.builder.addParameter(parameter);
163
+ return parameter;
160
164
  }
161
165
 
162
166
  /**
@@ -13,8 +13,9 @@ class BuilderNode {
13
13
  * @param {string} processId
14
14
  * @param {object.<string, *>} [processArgs={}]
15
15
  * @param {?string} [processDescription=null]
16
+ * @param {?string} [processNamespace=null]
16
17
  */
17
- constructor(parent, processId, processArgs = {}, processDescription = null) {
18
+ constructor(parent, processId, processArgs = {}, processDescription = null, processNamespace = null) {
18
19
  /**
19
20
  * The parent builder.
20
21
  * @type {Builder}
@@ -26,8 +27,8 @@ class BuilderNode {
26
27
  * @type {Process}
27
28
  * @readonly
28
29
  */
29
- this.spec = this.parent.spec(processId);
30
- if (!Utils.isObject(this.spec)) {
30
+ this.spec = this.parent.spec(processId, processNamespace);
31
+ if (!this.spec) {
31
32
  throw new Error("Process doesn't exist: " + processId);
32
33
  }
33
34
 
@@ -36,6 +37,11 @@ class BuilderNode {
36
37
  * @type {string}
37
38
  */
38
39
  this.id = parent.generateId(processId);
40
+ /**
41
+ * The namespace of the process - EXPERIMENTAL!
42
+ * @type {string}
43
+ */
44
+ this.namespace = processNamespace;
39
45
  /**
40
46
  * The arguments for the process.
41
47
  * @type {object.<string, *>}
@@ -176,7 +182,7 @@ class BuilderNode {
176
182
  *
177
183
  * @protected
178
184
  * @param {?BuilderNode} [parentNode=null]
179
- * @param {?string} parentParameter
185
+ * @param {?string} [parentParameter=null]
180
186
  * @returns {BuilderNode}
181
187
  */
182
188
  createBuilder(parentNode = null, parentParameter = null) {
@@ -228,6 +234,9 @@ class BuilderNode {
228
234
  process_id: this.spec.id,
229
235
  arguments: {}
230
236
  };
237
+ if (this.namespace) {
238
+ obj.namespace = this.namespace;
239
+ }
231
240
  for(let name in this.arguments) {
232
241
  if (typeof this.arguments[name] !== 'undefined') {
233
242
  obj.arguments[name] = this.exportArgument(this.arguments[name], name);
@@ -88,15 +88,14 @@ class Parameter {
88
88
  */
89
89
  set(target, name, value, receiver) {
90
90
  if (!Reflect.has(target, name)) {
91
- console.warn('Simplified array access is read-only');
91
+ throw new Error('Simplified array access is read-only');
92
92
  }
93
93
  return Reflect.set(target, name, value, receiver);
94
94
  }
95
95
  });
96
96
  }
97
97
  else {
98
- console.warn('Simplified array access not supported, use array_element directly');
99
- return parameter;
98
+ throw new Error('Simplified array access not supported, use array_element directly');
100
99
  }
101
100
  }
102
101
 
@@ -119,23 +119,34 @@ TapDigit.Lexer = function () {
119
119
  return (ch === '_') || (ch === '#') || (ch === '$') || isLetter(ch);
120
120
  }
121
121
 
122
- function isIdentifierPart(ch) {
123
- return (ch === '_') || isLetter(ch) || isDecimalDigit(ch);
122
+ function isAdditionalNamespaceChar(ch) {
123
+ return (ch === '-') || (ch === '.') || (ch === '~') || (ch === '@');
124
124
  }
125
125
 
126
- function scanIdentifier() {
127
- let ch
128
- let id;
126
+ function isIdentifierPart(ch, ns = false) {
127
+ return (ch === '_') || isLetter(ch) || isDecimalDigit(ch) || (ns && isAdditionalNamespaceChar(ch));
128
+ }
129
129
 
130
- ch = peekNextChar();
131
- if (!isIdentifierStart(ch)) {
130
+ function scanIdentifier() {
131
+ let startCh = peekNextChar();
132
+ if (!isIdentifierStart(startCh)) {
132
133
  return undefined;
133
134
  }
134
135
 
135
- id = getNextChar();
136
+ let id = getNextChar();
137
+ let ns = false;
136
138
  while (true) {
137
- ch = peekNextChar();
138
- if (!isIdentifierPart(ch)) {
139
+ let ch = peekNextChar();
140
+ // If the first character is a $, it is allowed that more $ follow directly after
141
+ if (startCh === '$') {
142
+ if (ch !== '$') {
143
+ startCh = ''; // Stop allowing $ once the first non-$ has been found
144
+ } // else: allowed
145
+ }
146
+ else if (ch === '@') {
147
+ ns = true;
148
+ }
149
+ else if (!isIdentifierPart(ch, ns)) {
139
150
  break;
140
151
  }
141
152
  id += getNextChar();
@@ -190,7 +190,7 @@ class Capabilities {
190
190
  /**
191
191
  * Get the billing currency.
192
192
  *
193
- * @returns {?string} The billing currency or `null` if not available.
193
+ * @returns {string | null} The billing currency or `null` if not available.
194
194
  */
195
195
  currency() {
196
196
  return (Utils.isObject(this.data.billing) && typeof this.data.billing.currency === 'string' ? this.data.billing.currency : null);