@opra/elastic 1.0.0-alpha.32 → 1.0.0-beta.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.
@@ -0,0 +1,392 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ElasticEntityService = void 0;
4
+ const common_1 = require("@opra/common");
5
+ const valgen_1 = require("valgen");
6
+ const elastic_adapter_js_1 = require("./elastic-adapter.js");
7
+ const elastic_service_js_1 = require("./elastic-service.js");
8
+ /**
9
+ * @class ElasticEntityService
10
+ * @template T - The type of the documents in the collection.
11
+ */
12
+ class ElasticEntityService extends elastic_service_js_1.ElasticService {
13
+ /**
14
+ * Constructs a new instance
15
+ *
16
+ * @param {Type | string} dataType - The data type of the array elements.
17
+ * @param {ElasticEntityService.Options} [options] - The options for the array service.
18
+ * @constructor
19
+ */
20
+ constructor(dataType, options) {
21
+ super(options);
22
+ this._inputCodecs = {};
23
+ this._outputCodecs = {};
24
+ this._dataType_ = dataType;
25
+ if (options?.indexName)
26
+ this.indexName = options?.indexName;
27
+ else {
28
+ if (typeof dataType === 'string')
29
+ this.indexName = dataType;
30
+ if (typeof dataType === 'function') {
31
+ const metadata = Reflect.getMetadata(common_1.DATATYPE_METADATA, dataType);
32
+ if (metadata)
33
+ this.indexName = metadata.name;
34
+ }
35
+ }
36
+ this.resourceName = options?.resourceName;
37
+ this.idGenerator = options?.idGenerator;
38
+ }
39
+ /**
40
+ * Retrieves the index name.
41
+ *
42
+ * @protected
43
+ * @returns The index name.
44
+ * @throws {Error} If the index name is not defined.
45
+ */
46
+ getIndexName() {
47
+ const out = typeof this.indexName === 'function' ? this.indexName(this) : this.indexName;
48
+ if (out)
49
+ return out;
50
+ throw new Error('indexName is not defined');
51
+ }
52
+ /**
53
+ * Retrieves the resource name.
54
+ *
55
+ * @protected
56
+ * @returns {string} The resource name.
57
+ * @throws {Error} If the resource name is not defined.
58
+ */
59
+ getResourceName() {
60
+ const out = typeof this.resourceName === 'function' ? this.resourceName(this) : this.resourceName || this.getIndexName();
61
+ if (out)
62
+ return out;
63
+ throw new Error('resourceName is not defined');
64
+ }
65
+ /**
66
+ * Retrieves the OPRA data type
67
+ *
68
+ * @throws {NotAcceptableError} If the data type is not a ComplexType.
69
+ */
70
+ get dataType() {
71
+ if (!this._dataType)
72
+ this._dataType = this.context.document.node.getComplexType(this._dataType_);
73
+ return this._dataType;
74
+ }
75
+ /**
76
+ * Adds a JSON document to the specified data stream or index and makes it searchable.
77
+ * If the target is an index and the document already exists,
78
+ * the request updates the document and increments its version.
79
+ *
80
+ * @param {ElasticEntityService.CreateCommand} command
81
+ * @protected
82
+ */
83
+ async _create(command) {
84
+ const input = command.input;
85
+ (0, valgen_1.isNotNullish)(input, { label: 'input' });
86
+ (0, valgen_1.isNotNullish)(input._id, { label: 'input._id' });
87
+ const inputCodec = this._getInputCodec('create');
88
+ const doc = inputCodec(input);
89
+ const { options } = command;
90
+ const request = {
91
+ ...options?.request,
92
+ index: this.getIndexName(),
93
+ id: input._id,
94
+ document: doc,
95
+ };
96
+ const client = this.getClient();
97
+ const r = await client.create(request, options?.transport);
98
+ /* istanbul ignore next */
99
+ if (!(r._id && (r.result === 'created' || r.result === 'updated'))) {
100
+ throw new common_1.InternalServerError(`Unknown error while creating document for "${this.getResourceName()}"`);
101
+ }
102
+ return r;
103
+ }
104
+ /**
105
+ * Returns the count of documents in the collection based on the provided options.
106
+ *
107
+ * @param {ElasticEntityService.CountCommand} command
108
+ * @protected
109
+ */
110
+ async _count(command) {
111
+ const { options } = command;
112
+ const filterQuery = elastic_adapter_js_1.ElasticAdapter.prepareFilter([options?.filter, options?.request?.query]);
113
+ let query = {
114
+ ...options?.request?.query,
115
+ ...filterQuery,
116
+ };
117
+ if (!Object.keys(query).length)
118
+ query = undefined;
119
+ const request = {
120
+ index: this.getIndexName(),
121
+ ...options?.request,
122
+ query,
123
+ };
124
+ const client = this.getClient();
125
+ return client.count(request, options?.transport);
126
+ }
127
+ /**
128
+ * Deletes a document from the collection.
129
+ *
130
+ * @param {ElasticEntityService.DeleteCommand} command
131
+ * @protected
132
+ */
133
+ async _delete(command) {
134
+ (0, valgen_1.isNotNullish)(command.documentId, { label: 'documentId' });
135
+ const { options } = command;
136
+ const filterQuery = elastic_adapter_js_1.ElasticAdapter.prepareFilter([
137
+ { ids: { values: [command.documentId] } },
138
+ options?.filter,
139
+ options?.request?.query,
140
+ ]);
141
+ let query = {
142
+ ...options?.request?.query,
143
+ ...filterQuery,
144
+ };
145
+ if (!Object.keys(query).length)
146
+ query = { match_all: {} };
147
+ const request = {
148
+ index: this.getIndexName(),
149
+ ...options?.request,
150
+ query,
151
+ };
152
+ const client = this.getClient();
153
+ return client.deleteByQuery(request, options?.transport);
154
+ }
155
+ /**
156
+ * Deletes multiple documents from the collection that meet the specified filter criteria.
157
+ *
158
+ * @param {ElasticEntityService.DeleteManyCommand} command
159
+ * @protected
160
+ */
161
+ async _deleteMany(command) {
162
+ const { options } = command;
163
+ const filterQuery = elastic_adapter_js_1.ElasticAdapter.prepareFilter([options?.filter, options?.request?.query]);
164
+ let query = {
165
+ ...options?.request?.query,
166
+ ...filterQuery,
167
+ };
168
+ if (!Object.keys(query).length)
169
+ query = { match_all: {} };
170
+ const request = {
171
+ ...options?.request,
172
+ index: this.getIndexName(),
173
+ query,
174
+ };
175
+ const client = this.getClient();
176
+ return client.deleteByQuery(request, options?.transport);
177
+ }
178
+ /**
179
+ * Returns search hits that match the query defined in the request
180
+ *
181
+ * @param {ElasticEntityService.SearchCommand} command
182
+ */
183
+ async _search(command) {
184
+ const { options } = command;
185
+ const filterQuery = elastic_adapter_js_1.ElasticAdapter.prepareFilter([
186
+ command.documentId ? { ids: { values: [command.documentId] } } : undefined,
187
+ options?.filter,
188
+ options?.request?.query,
189
+ ]);
190
+ let query = {
191
+ ...options?.request?.query,
192
+ ...filterQuery,
193
+ };
194
+ if (!Object.keys(query).length)
195
+ query = { match_all: {} };
196
+ const request = {
197
+ from: options?.skip,
198
+ size: options?.limit,
199
+ sort: options?.sort ? elastic_adapter_js_1.ElasticAdapter.prepareSort(options?.sort) : undefined,
200
+ _source: elastic_adapter_js_1.ElasticAdapter.prepareProjection(this.dataType, options?.projection),
201
+ index: this.getIndexName(),
202
+ ...options?.request,
203
+ query,
204
+ };
205
+ const client = this.getClient();
206
+ return client.search(request, options?.transport);
207
+ }
208
+ /**
209
+ * Updates multiple documents in the collection based on the specified input and options.
210
+ *
211
+ * @param {ElasticEntityService.UpdateCommand<T>} command
212
+ */
213
+ async _updateMany(command) {
214
+ if (command.byId)
215
+ (0, valgen_1.isNotNullish)(command.documentId, { label: 'documentId' });
216
+ const { options } = command;
217
+ const input = command.input;
218
+ const requestScript = command.options?.request?.script;
219
+ let script;
220
+ const inputKeysLen = Object.keys(input).length;
221
+ (0, valgen_1.isNotNullish)(inputKeysLen || script, { label: 'input' });
222
+ if (requestScript) {
223
+ script = typeof requestScript === 'string' ? { source: requestScript } : { ...requestScript };
224
+ script.lang = script.lang || 'painless';
225
+ if (inputKeysLen > 0 && script.lang !== 'painless') {
226
+ throw new TypeError(`You cannot provide 'input' and 'script' arguments at the same time unless the script lang is 'painless'`);
227
+ }
228
+ }
229
+ if (inputKeysLen) {
230
+ delete input._id;
231
+ const inputCodec = this._getInputCodec('update');
232
+ const doc = inputCodec(input);
233
+ const scr = elastic_adapter_js_1.ElasticAdapter.preparePatch(doc);
234
+ if (script) {
235
+ script.source = (script.source ? script.source + '\n' + script.source : '') + scr.source;
236
+ script.params = { ...script.params, ...scr.params };
237
+ }
238
+ else
239
+ script = scr;
240
+ }
241
+ script.source = script?.source || 'return;';
242
+ const filterQuery = elastic_adapter_js_1.ElasticAdapter.prepareFilter([
243
+ command.byId ? { ids: { values: [command.documentId] } } : undefined,
244
+ options?.filter,
245
+ options?.request?.query,
246
+ ]);
247
+ let query = {
248
+ ...options?.request?.query,
249
+ ...filterQuery,
250
+ };
251
+ if (!Object.keys(query).length)
252
+ query = { match_all: {} };
253
+ const request = {
254
+ ...options?.request,
255
+ index: this.getIndexName(),
256
+ script,
257
+ query,
258
+ };
259
+ const client = this.getClient();
260
+ return client.updateByQuery(request, options?.transport);
261
+ }
262
+ /**
263
+ * Generates an ID.
264
+ *
265
+ * @protected
266
+ * @returns The generated ID.
267
+ */
268
+ _generateId(command) {
269
+ return typeof this.idGenerator === 'function' ? this.idGenerator(command, this) : undefined;
270
+ }
271
+ /**
272
+ * Retrieves the codec for the specified operation.
273
+ *
274
+ * @param operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
275
+ */
276
+ _getInputCodec(operation) {
277
+ let validator = this._inputCodecs[operation];
278
+ if (validator)
279
+ return validator;
280
+ const options = { projection: '*' };
281
+ if (operation === 'update')
282
+ options.partial = 'deep';
283
+ const dataType = this.dataType;
284
+ validator = dataType.generateCodec('decode', options);
285
+ this._inputCodecs[operation] = validator;
286
+ return validator;
287
+ }
288
+ /**
289
+ * Retrieves the codec.
290
+ */
291
+ _getOutputCodec(operation) {
292
+ let validator = this._outputCodecs[operation];
293
+ if (validator)
294
+ return validator;
295
+ const options = { projection: '*', partial: 'deep' };
296
+ const dataType = this.dataType;
297
+ validator = dataType.generateCodec('decode', options);
298
+ this._outputCodecs[operation] = validator;
299
+ return validator;
300
+ }
301
+ async _executeCommand(command, commandFn) {
302
+ try {
303
+ const result = await super._executeCommand(command, async () => {
304
+ /** Call before[X] hooks */
305
+ if (command.crud === 'create')
306
+ await this._beforeCreate(command);
307
+ else if (command.crud === 'update' && command.byId) {
308
+ await this._beforeUpdate(command);
309
+ }
310
+ else if (command.crud === 'update' && !command.byId) {
311
+ await this._beforeUpdateMany(command);
312
+ }
313
+ else if (command.crud === 'delete' && command.byId) {
314
+ await this._beforeDelete(command);
315
+ }
316
+ else if (command.crud === 'delete' && !command.byId) {
317
+ await this._beforeDeleteMany(command);
318
+ }
319
+ /** Call command function */
320
+ return commandFn();
321
+ });
322
+ /** Call after[X] hooks */
323
+ if (command.crud === 'create')
324
+ await this._afterCreate(command, result);
325
+ else if (command.crud === 'update' && command.byId) {
326
+ await this._afterUpdate(command, result);
327
+ }
328
+ else if (command.crud === 'update' && !command.byId) {
329
+ await this._afterUpdateMany(command, result);
330
+ }
331
+ else if (command.crud === 'delete' && command.byId) {
332
+ await this._afterDelete(command, result);
333
+ }
334
+ else if (command.crud === 'delete' && !command.byId) {
335
+ await this._afterDeleteMany(command, result);
336
+ }
337
+ return result;
338
+ }
339
+ catch (e) {
340
+ Error.captureStackTrace(e, this._executeCommand);
341
+ await this.onError?.(e, this);
342
+ throw e;
343
+ }
344
+ }
345
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
346
+ async _beforeCreate(command) {
347
+ // Do nothing
348
+ }
349
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
350
+ async _beforeUpdate(command) {
351
+ // Do nothing
352
+ }
353
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
354
+ async _beforeUpdateMany(command) {
355
+ // Do nothing
356
+ }
357
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
358
+ async _beforeDelete(command) {
359
+ // Do nothing
360
+ }
361
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
362
+ async _beforeDeleteMany(command) {
363
+ // Do nothing
364
+ }
365
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
366
+ async _afterCreate(command, result) {
367
+ // Do nothing
368
+ }
369
+ async _afterUpdate(
370
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
371
+ command,
372
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
373
+ result) {
374
+ // Do nothing
375
+ }
376
+ async _afterUpdateMany(
377
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
378
+ command,
379
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
380
+ affected) {
381
+ // Do nothing
382
+ }
383
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
384
+ async _afterDelete(command, affected) {
385
+ // Do nothing
386
+ }
387
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
388
+ async _afterDeleteMany(command, affected) {
389
+ // Do nothing
390
+ }
391
+ }
392
+ exports.ElasticEntityService = ElasticEntityService;
@@ -11,88 +11,14 @@ class ElasticService extends core_1.ServiceBase {
11
11
  /**
12
12
  * Constructs a new instance
13
13
  *
14
- * @param dataType - The data type of the returning results
15
- * @param indexName - The name of the index, or a function that returns the index name
16
14
  * @param [options] - The options for the service
17
15
  * @constructor
18
16
  */
19
- constructor(dataType, indexName, options) {
17
+ constructor(options) {
20
18
  super();
21
- this._inputCodecs = {};
22
- this._outputCodecs = {};
23
- this._dataType_ = dataType;
19
+ this.interceptor = options?.interceptor;
24
20
  this.client = options?.client;
25
- this.$commonFilter = this.$commonFilter || options?.commonFilter;
26
- this.$interceptor = this.$interceptor || options?.interceptor;
27
- this.$indexName = indexName;
28
- this.$resourceName = options?.resourceName;
29
- this.$idGenerator = options?.idGenerator;
30
- }
31
- /**
32
- * Retrieves the index name.
33
- *
34
- * @protected
35
- * @returns The index name.
36
- * @throws {Error} If the index name is not defined.
37
- */
38
- getIndexName() {
39
- const out = typeof this.$indexName === 'function' ? this.$indexName(this) : this.$indexName;
40
- if (out)
41
- return out;
42
- throw new Error('indexName is not defined');
43
- }
44
- /**
45
- * Retrieves the resource name.
46
- *
47
- * @protected
48
- * @returns {string} The resource name.
49
- * @throws {Error} If the resource name is not defined.
50
- */
51
- getResourceName() {
52
- const out = typeof this.$resourceName === 'function' ? this.$resourceName(this) : this.$resourceName || this.getIndexName();
53
- if (out)
54
- return out;
55
- throw new Error('resourceName is not defined');
56
- }
57
- /**
58
- * Retrieves the OPRA data type
59
- *
60
- * @throws {NotAcceptableError} If the data type is not a ComplexType.
61
- */
62
- get dataType() {
63
- if (!this._dataType)
64
- this._dataType = this.context.document.node.getComplexType(this._dataType_);
65
- return this._dataType;
66
- }
67
- /**
68
- * Retrieves the codec for the specified operation.
69
- *
70
- * @param operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
71
- */
72
- getInputCodec(operation) {
73
- let validator = this._inputCodecs[operation];
74
- if (validator)
75
- return validator;
76
- const options = { projection: '*' };
77
- if (operation === 'update')
78
- options.partial = 'deep';
79
- const dataType = this.dataType;
80
- validator = dataType.generateCodec('decode', options);
81
- this._inputCodecs[operation] = validator;
82
- return validator;
83
- }
84
- /**
85
- * Retrieves the codec.
86
- */
87
- getOutputCodec(operation) {
88
- let validator = this._outputCodecs[operation];
89
- if (validator)
90
- return validator;
91
- const options = { projection: '*', partial: 'deep' };
92
- const dataType = this.dataType;
93
- validator = dataType.generateCodec('decode', options);
94
- this._outputCodecs[operation] = validator;
95
- return validator;
21
+ this.onError = options?.onError;
96
22
  }
97
23
  /**
98
24
  * Retrieves the ElasticSearch client.
@@ -108,35 +34,26 @@ class ElasticService extends core_1.ServiceBase {
108
34
  throw new Error(`Client not set!`);
109
35
  return db;
110
36
  }
111
- /**
112
- * Generates an ID.
113
- *
114
- * @protected
115
- * @returns The generated ID.
116
- */
117
- _generateId() {
118
- return typeof this.$idGenerator === 'function' ? this.$idGenerator(this) : undefined;
119
- }
120
- /**
121
- * Retrieves the common filter used for querying documents.
122
- * This method is mostly used for security issues like securing multi-tenant applications.
123
- *
124
- * @protected
125
- * @returns {QueryDslQueryContainer | Promise<QueryDslQueryContainer> | undefined} The common filter or a Promise
126
- * that resolves to the common filter, or undefined if not available.
127
- */
128
- _getCommonFilter(info) {
129
- return typeof this.$commonFilter === 'function' ? this.$commonFilter(info, this) : this.$commonFilter;
130
- }
131
- async _intercept(callback, info) {
37
+ async _executeCommand(command, commandFn) {
38
+ let proto;
39
+ const next = async () => {
40
+ proto = proto ? Object.getPrototypeOf(proto) : this;
41
+ while (proto) {
42
+ if (proto.interceptor && Object.prototype.hasOwnProperty.call(proto, 'interceptor')) {
43
+ return await proto.interceptor.call(this, next, command, this);
44
+ }
45
+ proto = Object.getPrototypeOf(proto);
46
+ if (!(proto instanceof ElasticService))
47
+ break;
48
+ }
49
+ return commandFn();
50
+ };
132
51
  try {
133
- if (this.$interceptor)
134
- return this.$interceptor(callback, info, this);
135
- return callback();
52
+ return await next();
136
53
  }
137
54
  catch (e) {
138
- Error.captureStackTrace(e, this._intercept);
139
- await this.$onError?.(e, this);
55
+ Error.captureStackTrace(e, this._executeCommand);
56
+ await this.onError?.(e, this);
140
57
  throw e;
141
58
  }
142
59
  }
package/cjs/index.js CHANGED
@@ -2,3 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./elastic-adapter.js"), exports);
5
+ tslib_1.__exportStar(require("./elastic-collection-service.js"), exports);
6
+ tslib_1.__exportStar(require("./elastic-entity-service.js"), exports);
7
+ tslib_1.__exportStar(require("./elastic-service.js"), exports);