kuzzle 2.14.16 → 2.16.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 (38) hide show
  1. package/lib/api/controllers/adminController.js +5 -0
  2. package/lib/api/controllers/documentController.js +1 -5
  3. package/lib/api/controllers/serverController.js +24 -4
  4. package/lib/api/funnel.js +19 -0
  5. package/lib/{config → api}/httpRoutes.js +30 -14
  6. package/lib/api/openApiGenerator.d.ts +6 -0
  7. package/lib/api/openApiGenerator.js +167 -126
  8. package/lib/api/openapi/documents/document.d.ts +21 -0
  9. package/lib/api/openapi/documents/document.js +57 -0
  10. package/lib/api/openapi/tools.d.ts +2 -0
  11. package/lib/api/openapi/tools.js +10 -0
  12. package/lib/api/request/kuzzleRequest.d.ts +30 -32
  13. package/lib/api/request/kuzzleRequest.js +30 -102
  14. package/lib/api/request/requestContext.d.ts +17 -22
  15. package/lib/api/request/requestContext.js +44 -109
  16. package/lib/api/request/requestInput.d.ts +19 -22
  17. package/lib/api/request/requestInput.js +115 -173
  18. package/lib/api/request/requestResponse.d.ts +12 -8
  19. package/lib/api/request/requestResponse.js +35 -29
  20. package/lib/cluster/idCardHandler.d.ts +140 -0
  21. package/lib/cluster/idCardHandler.js +218 -214
  22. package/lib/cluster/node.js +11 -0
  23. package/lib/cluster/protobuf/sync.proto +4 -0
  24. package/lib/cluster/subscriber.js +9 -12
  25. package/lib/cluster/workers/IDCardRenewer.js +13 -7
  26. package/lib/config/default.config.js +1 -1
  27. package/lib/core/network/router.js +33 -0
  28. package/lib/core/plugin/pluginsManager.js +3 -1
  29. package/lib/core/realtime/hotelClerk.d.ts +7 -0
  30. package/lib/core/realtime/hotelClerk.js +14 -0
  31. package/lib/core/realtime/notifier.js +16 -18
  32. package/lib/core/storage/clientAdapter.js +11 -5
  33. package/lib/core/storage/indexCache.d.ts +55 -0
  34. package/lib/core/storage/indexCache.js +97 -130
  35. package/lib/kuzzle/kuzzle.js +11 -7
  36. package/lib/service/storage/elasticsearch.js +14 -9
  37. package/package-lock.json +286 -260
  38. package/package.json +18 -17
@@ -15,14 +15,15 @@ class IDCardRenewer {
15
15
  }
16
16
 
17
17
  async init (config) {
18
- if (!this.disposed) {
19
- return; // Already intialized
18
+ if (! this.disposed) {
19
+ return; // Already initialized
20
20
  }
21
21
 
22
22
  this.disposed = false;
23
23
  this.nodeIdKey = config.nodeIdKey;
24
24
  this.refreshDelay = config.refreshDelay || 2000;
25
-
25
+ this.refreshMultiplier = config.refreshMultiplier;
26
+
26
27
  /**
27
28
  * Since we do not have access to the Kuzzle Context we can't use kuzzle.ask('core:cache:internal:*',...),
28
29
  * so we need to have an instance of Redis similar to the one used in the Cache Engine
@@ -48,6 +49,9 @@ class IDCardRenewer {
48
49
  this.renewIDCard.bind(this),
49
50
  this.refreshDelay);
50
51
  }
52
+
53
+ // Notify that the worker is running and updating the ID Card
54
+ this.parentPort.postMessage({ initialized: true });
51
55
  }
52
56
 
53
57
  async initRedis (config, name) {
@@ -63,7 +67,7 @@ class IDCardRenewer {
63
67
  try {
64
68
  const refreshed = await this.redis.commands.pexpire(
65
69
  this.nodeIdKey,
66
- this.refreshDelay * 3);
70
+ this.refreshDelay * this.refreshMultiplier);
67
71
  // Unable to refresh the key in time before it expires
68
72
  // => this node is too slow, we need to remove it from the cluster
69
73
  if (refreshed === 0) {
@@ -99,7 +103,8 @@ class IDCardRenewer {
99
103
 
100
104
  if (!isMainThread) {
101
105
  const idCardRenewer = new IDCardRenewer();
102
- parentPort.on('message', async (message) => {
106
+
107
+ parentPort.on('message', async message => {
103
108
  if (message.action === 'start') {
104
109
  // Simulate basic global Kuzzle Context
105
110
  global.kuzzle = { ...message.kuzzle };
@@ -111,11 +116,12 @@ if (!isMainThread) {
111
116
  };
112
117
  // Should never throw
113
118
  await idCardRenewer.init(message);
114
- } else if (message.action === 'dispose') {
119
+ }
120
+ else if (message.action === 'dispose') {
115
121
  // Should never throw
116
122
  await idCardRenewer.dispose();
117
123
  }
118
- });
124
+ });
119
125
  }
120
126
 
121
127
  module.exports = { IDCardRenewer };
@@ -46,7 +46,7 @@ module.exports = {
46
46
  (see https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)
47
47
  */
48
48
  http: {
49
- routes: require('./httpRoutes'),
49
+ routes: require('./../api/httpRoutes'),
50
50
  accessControlAllowOrigin: '*',
51
51
  accessControlAllowOriginUseRegExp: false,
52
52
  accessControlAllowMethods: 'GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD',
@@ -138,6 +138,39 @@ class Router {
138
138
  this._executeFromHttp(route.verb, request, cb);
139
139
  });
140
140
  }
141
+
142
+ /**
143
+ * Returns inner metrics from the router
144
+ * @returns {Object}
145
+ */
146
+ global.kuzzle.onAsk(
147
+ 'core:network:router:metrics',
148
+ () => this.metrics());
149
+ }
150
+
151
+ /**
152
+ * Returns the metrics of the router
153
+ * @returns {Object}
154
+ */
155
+ metrics () {
156
+ const connectionsByProtocol = {};
157
+
158
+ for (const connection of this.connections.values()) {
159
+ const protocol = connection.connection.protocol.toLowerCase();
160
+ if (protocol === 'internal') {
161
+ continue;
162
+ }
163
+
164
+ if (connectionsByProtocol[protocol] === undefined) {
165
+ connectionsByProtocol[protocol] = 0;
166
+ }
167
+
168
+ connectionsByProtocol[protocol]++;
169
+ }
170
+
171
+ return {
172
+ connections: connectionsByProtocol,
173
+ };
141
174
  }
142
175
 
143
176
  /**
@@ -265,7 +265,9 @@ class PluginsManager {
265
265
 
266
266
  debug('[%s] plugin started', plugin.name);
267
267
 
268
- this.loadedPlugins.push(plugin.name);
268
+ if (! plugin.application) {
269
+ this.loadedPlugins.push(plugin.name);
270
+ }
269
271
 
270
272
  return null;
271
273
  });
@@ -118,6 +118,13 @@ export declare class HotelClerk {
118
118
  *
119
119
  */
120
120
  unsubscribe(connectionId: string, roomId: string, notify?: boolean): Promise<void>;
121
+ /**
122
+ * Returns inner metrics from the HotelClerk
123
+ */
124
+ metrics(): {
125
+ rooms: number;
126
+ subscriptions: number;
127
+ };
121
128
  /**
122
129
  * Deletes a room if no user has subscribed to it, and removes it also from the
123
130
  * real-time engine
@@ -136,6 +136,11 @@ class HotelClerk {
136
136
  global.kuzzle.onAsk('core:realtime:unsubscribe', (connectionId, roomId, notify) => {
137
137
  return this.unsubscribe(connectionId, roomId, notify);
138
138
  });
139
+ /**
140
+ * Returns inner metrics from the HotelClerk
141
+ * @return {{rooms: number, subscriptions: number}}
142
+ */
143
+ global.kuzzle.onAsk('core:realtime:hotelClerk:metrics', () => this.metrics());
139
144
  /**
140
145
  * Clear the hotel clerk and properly disconnect connections.
141
146
  */
@@ -410,6 +415,15 @@ class HotelClerk {
410
415
  subscription,
411
416
  });
412
417
  }
418
+ /**
419
+ * Returns inner metrics from the HotelClerk
420
+ */
421
+ metrics() {
422
+ return {
423
+ rooms: this.roomsCount,
424
+ subscriptions: this.subscriptions.size,
425
+ };
426
+ }
413
427
  /**
414
428
  * Deletes a room if no user has subscribed to it, and removes it also from the
415
429
  * real-time engine
@@ -249,9 +249,7 @@ class NotifierController {
249
249
  if (cache !== null) {
250
250
  const stopListening = difference(JSON.parse(cache), rooms);
251
251
 
252
- await this.notifyDocument(stopListening, request, 'out', 'replace', {
253
- _id: document._id,
254
- });
252
+ await this.notifyDocument(stopListening, request, 'out', 'replace', document);
255
253
  }
256
254
 
257
255
  return rooms;
@@ -281,25 +279,29 @@ class NotifierController {
281
279
  : [];
282
280
 
283
281
  const result = await Bluebird.map(documents, (doc, index) => {
284
- switch(action) {
282
+ switch (action) {
285
283
  case actionEnum.CREATE:
286
284
  return this.notifyDocumentCreate(request, doc);
285
+
287
286
  case actionEnum.DELETE:
288
287
  return this.notifyDocumentDelete(request, doc);
288
+
289
289
  case actionEnum.REPLACE:
290
290
  return this.notifyDocumentReplace(request, doc, cache[index]);
291
+
291
292
  case actionEnum.UPDATE:
292
293
  return this.notifyDocumentUpdate(request, doc, cache[index]);
294
+
293
295
  case actionEnum.UPSERT:
294
- if (doc.created) {
295
- return this.notifyDocumentCreate(request, doc);
296
- }
297
- return this.notifyDocumentUpdate(request, doc, cache[index]);
296
+ return doc.created
297
+ ? this.notifyDocumentCreate(request, doc)
298
+ : this.notifyDocumentUpdate(request, doc, cache[index]);
299
+
298
300
  case actionEnum.WRITE:
299
- if (doc.created) {
300
- return this.notifyDocumentCreate(request, doc);
301
- }
302
- return this.notifyDocumentReplace(request, doc, cache[index]);
301
+ return doc.created
302
+ ? this.notifyDocumentCreate(request, doc)
303
+ : this.notifyDocumentReplace(request, doc, cache[index]);
304
+
303
305
  default:
304
306
  throw kerror.get('core', 'fatal', 'assertion_failed', `unknown notify action "${doc.action}"`);
305
307
  }
@@ -342,9 +344,7 @@ class NotifierController {
342
344
  if (cache !== null) {
343
345
  const stopListening = difference(JSON.parse(cache), rooms);
344
346
 
345
- await this.notifyDocument(stopListening, request, 'out', 'update', {
346
- _id: document._id,
347
- });
347
+ await this.notifyDocument(stopListening, request, 'out', 'update', document);
348
348
  }
349
349
 
350
350
  return rooms;
@@ -361,9 +361,7 @@ class NotifierController {
361
361
  const rooms = this._test(request, document._source, document._id);
362
362
 
363
363
  if (rooms.length > 0) {
364
- await this.notifyDocument(rooms, request, 'out', 'delete', {
365
- _id: document._id,
366
- });
364
+ await this.notifyDocument(rooms, request, 'out', 'delete', document);
367
365
  }
368
366
 
369
367
  return [];
@@ -22,7 +22,7 @@
22
22
  'use strict';
23
23
 
24
24
  const Elasticsearch = require('../../service/storage/elasticsearch');
25
- const IndexCache = require('./indexCache');
25
+ const { IndexCache } = require('./indexCache');
26
26
  const { isPlainObject } = require('../../util/safeObject');
27
27
  const kerror = require('../../kerror');
28
28
  const { Mutex } = require('../../util/mutex');
@@ -42,7 +42,7 @@ class ClientAdapter {
42
42
  global.kuzzle.config.services.storageEngine,
43
43
  scope);
44
44
  this.scope = scope;
45
- this.cache = new IndexCache(scope);
45
+ this.cache = new IndexCache();
46
46
  }
47
47
 
48
48
  async init () {
@@ -57,6 +57,13 @@ class ClientAdapter {
57
57
 
58
58
  // Global store events registration
59
59
 
60
+ /**
61
+ * Manually refresh the index cache (e.g. after alias creation)
62
+ */
63
+ global.kuzzle.onAsk(
64
+ `core:storage:${this.scope}:cache:refresh`,
65
+ () => this.populateCache());
66
+
60
67
  /**
61
68
  * Return information about the instantiated ES service
62
69
  * @returns {Promise.<Object>}
@@ -77,7 +84,6 @@ class ClientAdapter {
77
84
  }
78
85
 
79
86
  async createIndex (index, { indexCacheOnly=false, propagate=true } = {}) {
80
-
81
87
  if (this.cache.hasIndex(index)) {
82
88
  throw servicesError.get('index_already_exists', this.scope, index);
83
89
  }
@@ -264,7 +270,7 @@ class ClientAdapter {
264
270
  `core:storage:${this.scope}:collection:update`,
265
271
  (index, collection, changes) => {
266
272
  this.cache.assertCollectionExists(index, collection);
267
- this.client.updateCollection(index, collection, changes);
273
+ return this.client.updateCollection(index, collection, changes);
268
274
  });
269
275
  }
270
276
 
@@ -856,7 +862,7 @@ class ClientAdapter {
856
862
  * @returns {Promise}
857
863
  */
858
864
  async loadMappings (
859
- fixtures = {},
865
+ fixtures = {},
860
866
  options = {
861
867
  indexCacheOnly: false,
862
868
  propagate: true,
@@ -0,0 +1,55 @@
1
+ export declare class IndexCache {
2
+ /**
3
+ * Index map: each entry holds a set of collection names
4
+ *
5
+ * Map<index, Set<collection>>
6
+ */
7
+ private indexes;
8
+ constructor();
9
+ /**
10
+ * Cache a new index
11
+ *
12
+ * @return true if an index was added, false if there is no modification
13
+ */
14
+ addIndex(index: string): boolean;
15
+ /**
16
+ * Cache a new collection
17
+ */
18
+ addCollection(index: string, collection: string): void;
19
+ /**
20
+ * Check an index existence
21
+ */
22
+ hasIndex(index: string): boolean;
23
+ /**
24
+ * Check a collection existence
25
+ */
26
+ hasCollection(index: string, collection: string): boolean;
27
+ /**
28
+ * Return the list of cached indexes
29
+ */
30
+ listIndexes(): string[];
31
+ /**
32
+ * Return the list of an index collections
33
+ *
34
+ * @throws If the provided index does not exist
35
+ */
36
+ listCollections(index: string): string[];
37
+ /**
38
+ * Remove an index from the cache
39
+ */
40
+ removeIndex(index: string): void;
41
+ /**
42
+ * Remove a collection from the cache
43
+ */
44
+ removeCollection(index: string, collection: string): void;
45
+ /**
46
+ * Assert that the provided index exists
47
+ *
48
+ * @throws If the index does not exist
49
+ */
50
+ assertIndexExists(index: string): void;
51
+ /**
52
+ * Assert that the provided index and collection exist
53
+ */
54
+ assertCollectionExists(index: string, collection: string): void;
55
+ }
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /*
2
3
  * Kuzzle, a backend software, self-hostable and ready to use
3
4
  * to power modern apps
@@ -18,142 +19,108 @@
18
19
  * See the License for the specific language governing permissions and
19
20
  * limitations under the License.
20
21
  */
21
-
22
- 'use strict';
23
-
24
- const kerror = require('../../kerror').wrap('services', 'storage');
25
-
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.IndexCache = void 0;
27
+ const kerror_1 = __importDefault(require("../../kerror"));
28
+ const storageError = kerror_1.default.wrap('services', 'storage');
26
29
  class IndexCache {
27
- /**
28
- * @param {storeScopeEnum} scope
29
- */
30
- constructor (scope) {
31
- this.scope = scope;
32
-
30
+ constructor() {
31
+ /**
32
+ * Index map: each entry holds a set of collection names
33
+ *
34
+ * Map<index, Set<collection>>
35
+ */
36
+ this.indexes = new Map();
37
+ this.indexes = new Map();
38
+ }
39
+ /**
40
+ * Cache a new index
41
+ *
42
+ * @return true if an index was added, false if there is no modification
43
+ */
44
+ addIndex(index) {
45
+ if (this.indexes.has(index)) {
46
+ return false;
47
+ }
48
+ this.indexes.set(index, new Set());
49
+ return true;
50
+ }
51
+ /**
52
+ * Cache a new collection
53
+ */
54
+ addCollection(index, collection) {
55
+ this.addIndex(index);
56
+ const collections = this.indexes.get(index);
57
+ collections.add(collection);
58
+ }
59
+ /**
60
+ * Check an index existence
61
+ */
62
+ hasIndex(index) {
63
+ return this.indexes.has(index);
64
+ }
65
+ /**
66
+ * Check a collection existence
67
+ */
68
+ hasCollection(index, collection) {
69
+ const collections = this.indexes.get(index);
70
+ if (!collections) {
71
+ return false;
72
+ }
73
+ return collections.has(collection);
74
+ }
75
+ /**
76
+ * Return the list of cached indexes
77
+ */
78
+ listIndexes() {
79
+ return Array.from(this.indexes.keys());
80
+ }
33
81
  /**
34
- * Index map: each entry holds a set of collection names
35
- * @type {Map.<String, Set>}
82
+ * Return the list of an index collections
83
+ *
84
+ * @throws If the provided index does not exist
36
85
  */
37
- this.indexes = new Map();
38
- }
39
-
40
- /**
41
- * Cache a new index
42
- * @param {string} index
43
- * @return {boolean} true if an index was added, false if there is no
44
- * modification
45
- */
46
- addIndex (index) {
47
- if (this.indexes.has(index)) {
48
- return false;
86
+ listCollections(index) {
87
+ this.assertIndexExists(index);
88
+ return Array.from(this.indexes.get(index));
49
89
  }
50
-
51
- this.indexes.set(index, new Set());
52
-
53
- return true;
54
- }
55
-
56
- /**
57
- * Cache a new collection
58
- * @param {string} index
59
- * @param {string} collection
60
- */
61
- addCollection (index, collection) {
62
- this.addIndex(index);
63
- const collections = this.indexes.get(index);
64
-
65
- collections.add(collection);
66
- }
67
-
68
- /**
69
- * Check an index existence
70
- * @param {string} index
71
- * @returns {boolean}
72
- */
73
- hasIndex (index) {
74
- return this.indexes.has(index);
75
- }
76
-
77
- /**
78
- * Check a collection existence
79
- * @param {string} index
80
- * @param {string} collection
81
- * @returns {boolean}
82
- */
83
- hasCollection (index, collection) {
84
- const collections = this.indexes.get(index);
85
-
86
- if (!collections) {
87
- return false;
90
+ /**
91
+ * Remove an index from the cache
92
+ */
93
+ removeIndex(index) {
94
+ this.indexes.delete(index);
88
95
  }
89
-
90
- return collections.has(collection);
91
- }
92
-
93
- /**
94
- * Return the list of cached indexes
95
- * @returns {string[]}
96
- */
97
- listIndexes () {
98
- return Array.from(this.indexes.keys());
99
- }
100
-
101
- /**
102
- * Return the list of an index' collections
103
- * @param {string} index
104
- * @returns {string[]}
105
- * @throws If the provided index does not exist
106
- */
107
- listCollections (index) {
108
- this.assertIndexExists(index);
109
-
110
- return Array.from(this.indexes.get(index));
111
- }
112
-
113
- /**
114
- * Remove an index from the cache
115
- * @param {string} index
116
- */
117
- removeIndex (index) {
118
- this.indexes.delete(index);
119
- }
120
-
121
- /**
122
- * Remove a collection from the cache
123
- * @param {string} index
124
- * @param {string} collection
125
- */
126
- removeCollection (index, collection) {
127
- const collections = this.indexes.get(index);
128
-
129
- if (collections) {
130
- collections.delete(collection);
96
+ /**
97
+ * Remove a collection from the cache
98
+ */
99
+ removeCollection(index, collection) {
100
+ const collections = this.indexes.get(index);
101
+ if (collections) {
102
+ collections.delete(collection);
103
+ }
131
104
  }
132
- }
133
-
134
- /**
135
- * Assert that the provided index exists
136
- * @param {string} index
137
- * @throws If the index does not exist
138
- */
139
- assertIndexExists (index) {
140
- if (!this.indexes.has(index)) {
141
- throw kerror.get('unknown_index', index);
105
+ /**
106
+ * Assert that the provided index exists
107
+ *
108
+ * @throws If the index does not exist
109
+ */
110
+ assertIndexExists(index) {
111
+ if (!this.indexes.has(index)) {
112
+ throw storageError.get('unknown_index', index);
113
+ }
142
114
  }
143
- }
144
-
145
- /**
146
- * Assert that the provided index and collection exist
147
- * @param {string} index
148
- * @param {string} collection
149
- */
150
- assertCollectionExists (index, collection) {
151
- this.assertIndexExists(index);
152
-
153
- if (!this.indexes.get(index).has(collection)) {
154
- throw kerror.get('unknown_collection', index, collection);
115
+ /**
116
+ * Assert that the provided index and collection exist
117
+ */
118
+ assertCollectionExists(index, collection) {
119
+ this.assertIndexExists(index);
120
+ if (!this.indexes.get(index).has(collection)) {
121
+ throw storageError.get('unknown_collection', index, collection);
122
+ }
155
123
  }
156
- }
157
124
  }
158
-
159
- module.exports = IndexCache;
125
+ exports.IndexCache = IndexCache;
126
+ //# sourceMappingURL=indexCache.js.map
@@ -28,7 +28,7 @@ const murmurhash_native_1 = require("murmurhash-native");
28
28
  const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
29
29
  const koncorde_1 = require("koncorde");
30
30
  const bluebird_1 = __importDefault(require("bluebird"));
31
- const segfault_handler_1 = __importDefault(require("segfault-handler"));
31
+ const node_segfault_handler_1 = __importDefault(require("node-segfault-handler"));
32
32
  const lodash_1 = __importDefault(require("lodash"));
33
33
  const kuzzleStateEnum_1 = __importDefault(require("./kuzzleStateEnum"));
34
34
  const kuzzleEventEmitter_1 = __importDefault(require("./event/kuzzleEventEmitter"));
@@ -54,21 +54,25 @@ const realtime_1 = __importDefault(require("../core/realtime"));
54
54
  const cluster_1 = __importDefault(require("../cluster"));
55
55
  const package_json_1 = require("../../package.json");
56
56
  const BACKEND_IMPORT_KEY = 'backend:init:import';
57
- let _kuzzle = null;
57
+ Reflect.defineProperty(global, '_kuzzle', {
58
+ value: null,
59
+ writable: true,
60
+ });
61
+ /* eslint-disable dot-notation */
58
62
  Reflect.defineProperty(global, 'kuzzle', {
59
63
  configurable: true,
60
64
  enumerable: false,
61
65
  get() {
62
- if (_kuzzle === null) {
66
+ if (global['_kuzzle'] === null) {
63
67
  throw new Error('Kuzzle instance not found. Did you try to use a live-only feature before starting your application?');
64
68
  }
65
- return _kuzzle;
69
+ return global['_kuzzle'];
66
70
  },
67
71
  set(value) {
68
- if (_kuzzle !== null) {
72
+ if (global['_kuzzle'] !== null) {
69
73
  throw new Error('Cannot build a Kuzzle instance: another one already exists');
70
74
  }
71
- _kuzzle = value;
75
+ global['_kuzzle'] = value;
72
76
  },
73
77
  });
74
78
  class Kuzzle extends kuzzleEventEmitter_1.default {
@@ -444,7 +448,7 @@ class Kuzzle extends kuzzleEventEmitter_1.default {
444
448
  this.shutdown();
445
449
  });
446
450
  }
447
- segfault_handler_1.default.registerHandler();
451
+ node_segfault_handler_1.default.registerHandler();
448
452
  }
449
453
  async dumpAndExit(suffix) {
450
454
  if (this.config.dump.enabled) {
@@ -128,12 +128,11 @@ class ElasticSearch extends Service {
128
128
  '_source_includes'
129
129
  ];
130
130
 
131
- // Forbidden keys in a query
132
- this.scriptKeys = [
133
- 'script',
134
- '_script'
135
- ];
136
-
131
+ /**
132
+ * Only allow stored-scripts in queries
133
+ */
134
+ this.scriptKeys = ['script', '_script'];
135
+ this.scriptAllowedArgs = ['id', 'params'];
137
136
 
138
137
  this.maxScrollDuration = this._loadMsConfig('maxScrollDuration');
139
138
 
@@ -2979,14 +2978,20 @@ class ElasticSearch extends Service {
2979
2978
  }
2980
2979
 
2981
2980
  /**
2982
- * Throw if any script keyword is contained in the object
2981
+ * Throw if a script is used in the query.
2982
+ *
2983
+ * Only Stored Scripts are accepted
2983
2984
  *
2984
2985
  * @param {Object} object
2985
2986
  */
2986
- _scriptCheck(object) {
2987
+ _scriptCheck (object) {
2987
2988
  for (const [key, value] of Object.entries(object)) {
2988
2989
  if (this.scriptKeys.includes(key)) {
2989
- throw kerror.get('invalid_query_keyword', key);
2990
+ for (const scriptArg of Object.keys(value)) {
2991
+ if (! this.scriptAllowedArgs.includes(scriptArg)) {
2992
+ throw kerror.get('invalid_query_keyword', `${key}.${scriptArg}`);
2993
+ }
2994
+ }
2990
2995
  }
2991
2996
  // Every object must be checked here, even the ones nested into an array
2992
2997
  else if (typeof value === 'object' && value !== null) {