@etohq/orchestration 1.0.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 (74) hide show
  1. package/dist/index.d.ts +4 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +20 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/joiner/helpers.d.ts +3 -0
  6. package/dist/joiner/helpers.d.ts.map +1 -0
  7. package/dist/joiner/helpers.js +69 -0
  8. package/dist/joiner/helpers.js.map +1 -0
  9. package/dist/joiner/index.d.ts +3 -0
  10. package/dist/joiner/index.d.ts.map +1 -0
  11. package/dist/joiner/index.js +19 -0
  12. package/dist/joiner/index.js.map +1 -0
  13. package/dist/joiner/remote-joiner.d.ts +39 -0
  14. package/dist/joiner/remote-joiner.d.ts.map +1 -0
  15. package/dist/joiner/remote-joiner.js +872 -0
  16. package/dist/joiner/remote-joiner.js.map +1 -0
  17. package/dist/transaction/datastore/abstract-storage.d.ts +42 -0
  18. package/dist/transaction/datastore/abstract-storage.d.ts.map +1 -0
  19. package/dist/transaction/datastore/abstract-storage.js +52 -0
  20. package/dist/transaction/datastore/abstract-storage.js.map +1 -0
  21. package/dist/transaction/datastore/base-in-memory-storage.d.ts +11 -0
  22. package/dist/transaction/datastore/base-in-memory-storage.d.ts.map +1 -0
  23. package/dist/transaction/datastore/base-in-memory-storage.js +33 -0
  24. package/dist/transaction/datastore/base-in-memory-storage.js.map +1 -0
  25. package/dist/transaction/distributed-transaction.d.ts +99 -0
  26. package/dist/transaction/distributed-transaction.d.ts.map +1 -0
  27. package/dist/transaction/distributed-transaction.js +260 -0
  28. package/dist/transaction/distributed-transaction.js.map +1 -0
  29. package/dist/transaction/errors.d.ts +27 -0
  30. package/dist/transaction/errors.d.ts.map +1 -0
  31. package/dist/transaction/errors.js +78 -0
  32. package/dist/transaction/errors.js.map +1 -0
  33. package/dist/transaction/index.d.ts +8 -0
  34. package/dist/transaction/index.d.ts.map +1 -0
  35. package/dist/transaction/index.js +24 -0
  36. package/dist/transaction/index.js.map +1 -0
  37. package/dist/transaction/orchestrator-builder.d.ts +36 -0
  38. package/dist/transaction/orchestrator-builder.d.ts.map +1 -0
  39. package/dist/transaction/orchestrator-builder.js +300 -0
  40. package/dist/transaction/orchestrator-builder.js.map +1 -0
  41. package/dist/transaction/transaction-orchestrator.d.ts +118 -0
  42. package/dist/transaction/transaction-orchestrator.d.ts.map +1 -0
  43. package/dist/transaction/transaction-orchestrator.js +924 -0
  44. package/dist/transaction/transaction-orchestrator.js.map +1 -0
  45. package/dist/transaction/transaction-step.d.ts +67 -0
  46. package/dist/transaction/transaction-step.d.ts.map +1 -0
  47. package/dist/transaction/transaction-step.js +146 -0
  48. package/dist/transaction/transaction-step.js.map +1 -0
  49. package/dist/transaction/types.d.ts +223 -0
  50. package/dist/transaction/types.d.ts.map +1 -0
  51. package/dist/transaction/types.js +23 -0
  52. package/dist/transaction/types.js.map +1 -0
  53. package/dist/tsconfig.tsbuildinfo +1 -0
  54. package/dist/workflow/global-workflow.d.ts +14 -0
  55. package/dist/workflow/global-workflow.d.ts.map +1 -0
  56. package/dist/workflow/global-workflow.js +93 -0
  57. package/dist/workflow/global-workflow.js.map +1 -0
  58. package/dist/workflow/index.d.ts +5 -0
  59. package/dist/workflow/index.d.ts.map +1 -0
  60. package/dist/workflow/index.js +21 -0
  61. package/dist/workflow/index.js.map +1 -0
  62. package/dist/workflow/local-workflow.d.ts +44 -0
  63. package/dist/workflow/local-workflow.d.ts.map +1 -0
  64. package/dist/workflow/local-workflow.js +327 -0
  65. package/dist/workflow/local-workflow.js.map +1 -0
  66. package/dist/workflow/scheduler.d.ts +12 -0
  67. package/dist/workflow/scheduler.d.ts.map +1 -0
  68. package/dist/workflow/scheduler.js +36 -0
  69. package/dist/workflow/scheduler.js.map +1 -0
  70. package/dist/workflow/workflow-manager.d.ts +38 -0
  71. package/dist/workflow/workflow-manager.d.ts.map +1 -0
  72. package/dist/workflow/workflow-manager.js +124 -0
  73. package/dist/workflow/workflow-manager.js.map +1 -0
  74. package/package.json +52 -0
@@ -0,0 +1,872 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RemoteJoiner = void 0;
4
+ const utils_1 = require("@etohq/utils");
5
+ const BASE_PATH = "_root";
6
+ class RemoteJoiner {
7
+ static filterFields(data, fields, expands) {
8
+ if (!fields || !data) {
9
+ return data;
10
+ }
11
+ let filteredData = {};
12
+ if (fields.includes("*")) {
13
+ // select all fields
14
+ filteredData = data;
15
+ }
16
+ else {
17
+ filteredData = fields.reduce((acc, field) => {
18
+ const fieldValue = data?.[field];
19
+ if ((0, utils_1.isDefined)(fieldValue)) {
20
+ acc[field] = data?.[field];
21
+ }
22
+ return acc;
23
+ }, {});
24
+ }
25
+ if (expands) {
26
+ for (const key of Object.keys(expands ?? {})) {
27
+ const expand = expands[key];
28
+ if (expand) {
29
+ if (Array.isArray(data[key])) {
30
+ filteredData[key] = data[key].map((item) => RemoteJoiner.filterFields(item, expand.fields, expand.expands));
31
+ }
32
+ else {
33
+ const filteredFields = RemoteJoiner.filterFields(data[key], expand.fields, expand.expands);
34
+ if ((0, utils_1.isDefined)(filteredFields)) {
35
+ filteredData[key] = RemoteJoiner.filterFields(data[key], expand.fields, expand.expands);
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ return (Object.keys(filteredData).length && filteredData) || undefined;
42
+ }
43
+ static getNestedItems(items, property) {
44
+ const result = [];
45
+ for (const item of items) {
46
+ const allValues = item?.[property] ?? [];
47
+ const values = Array.isArray(allValues) ? allValues : [allValues];
48
+ for (const value of values) {
49
+ if ((0, utils_1.isDefined)(value)) {
50
+ result.push(value);
51
+ }
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ static createRelatedDataMap(relatedDataArray, joinFields) {
57
+ return relatedDataArray.reduce((acc, data) => {
58
+ const joinValues = joinFields.map((field) => data[field]);
59
+ const key = joinValues.length === 1 ? joinValues[0] : joinValues.join(",");
60
+ let isArray = Array.isArray(acc[key]);
61
+ if ((0, utils_1.isDefined)(acc[key]) && !isArray) {
62
+ acc[key] = [acc[key]];
63
+ isArray = true;
64
+ }
65
+ if (isArray) {
66
+ acc[key].push(data);
67
+ }
68
+ else {
69
+ acc[key] = data;
70
+ }
71
+ return acc;
72
+ }, {});
73
+ }
74
+ static parseQuery(graphqlQuery, variables) {
75
+ const parser = new utils_1.GraphQLUtils.GraphQLParser(graphqlQuery, variables);
76
+ return parser.parseQuery();
77
+ }
78
+ constructor(serviceConfigs, remoteFetchData, options = {}) {
79
+ this.remoteFetchData = remoteFetchData;
80
+ this.options = options;
81
+ this.serviceConfigCache = new Map();
82
+ this.entityMap = new Map();
83
+ this.options.autoCreateServiceNameAlias ??= true;
84
+ if (this.options.entitiesMap) {
85
+ this.entityMap = utils_1.GraphQLUtils.extractRelationsFromGQL(this.options.entitiesMap);
86
+ }
87
+ this.buildReferences(JSON.parse(JSON.stringify(serviceConfigs), (key, value) => {
88
+ if (key === "schema") {
89
+ return;
90
+ }
91
+ return value;
92
+ }));
93
+ }
94
+ setFetchDataCallback(remoteFetchData) {
95
+ this.remoteFetchData = remoteFetchData;
96
+ }
97
+ buildReferences(serviceConfigs) {
98
+ const expandedRelationships = new Map();
99
+ for (const service of serviceConfigs) {
100
+ const service_ = service;
101
+ if (this.serviceConfigCache.has(service_.serviceName)) {
102
+ throw new Error(`Service "${service_.serviceName}" is already defined.`);
103
+ }
104
+ service_.fieldAlias ??= {};
105
+ service_.extends ??= [];
106
+ service_.relationships ??= new Map();
107
+ if (Array.isArray(service_.relationships)) {
108
+ const relationships = new Map();
109
+ for (const relationship of service_.relationships) {
110
+ relationships.set(relationship.alias, relationship);
111
+ }
112
+ service_.relationships = relationships;
113
+ }
114
+ // add aliases
115
+ const isReadOnlyDefinition = !(0, utils_1.isDefined)(service_.serviceName) || service_.isReadOnlyLink;
116
+ if (!isReadOnlyDefinition) {
117
+ service_.alias ??= [];
118
+ if (!Array.isArray(service_.alias)) {
119
+ service_.alias = [service_.alias];
120
+ }
121
+ if (this.options.autoCreateServiceNameAlias) {
122
+ service_.alias.push({ name: service_.serviceName });
123
+ }
124
+ // handle alias.name as array
125
+ for (let idx = 0; idx < service_.alias.length; idx++) {
126
+ const alias = service_.alias[idx];
127
+ if (!Array.isArray(alias.name)) {
128
+ continue;
129
+ }
130
+ for (const name of alias.name) {
131
+ service_.alias.push({
132
+ name,
133
+ entity: alias.entity,
134
+ args: alias.args,
135
+ });
136
+ }
137
+ service_.alias.splice(idx, 1);
138
+ idx--;
139
+ }
140
+ // self-reference
141
+ for (const alias of service_.alias) {
142
+ if (this.serviceConfigCache.has(`alias_${alias.name}`)) {
143
+ const defined = this.serviceConfigCache.get(`alias_${alias.name}`);
144
+ if (service_.serviceName === defined?.serviceName) {
145
+ continue;
146
+ }
147
+ throw new Error(`Cannot add alias "${alias.name}" for "${service_.serviceName}". It is already defined for Service "${defined?.serviceName}".`);
148
+ }
149
+ const args = service_.args || alias.args
150
+ ? { ...service_.args, ...alias.args }
151
+ : undefined;
152
+ const aliasName = alias.name;
153
+ const rel = {
154
+ alias: aliasName,
155
+ entity: alias.entity,
156
+ foreignKey: alias.name + "_id",
157
+ primaryKey: "id",
158
+ serviceName: service_.serviceName,
159
+ args,
160
+ };
161
+ if (service_.relationships?.has(aliasName)) {
162
+ const existing = service_.relationships.get(aliasName);
163
+ const newRelation = Array.isArray(existing)
164
+ ? existing.concat(rel)
165
+ : [existing, rel];
166
+ service_.relationships?.set(aliasName, newRelation);
167
+ }
168
+ else {
169
+ service_.relationships?.set(aliasName, rel);
170
+ }
171
+ this.cacheServiceConfig(serviceConfigs, { serviceAlias: alias });
172
+ }
173
+ this.cacheServiceConfig(serviceConfigs, {
174
+ serviceName: service_.serviceName,
175
+ });
176
+ }
177
+ for (const extend of service_.extends) {
178
+ if (!expandedRelationships.has(extend.serviceName)) {
179
+ expandedRelationships.set(extend.serviceName, {
180
+ fieldAlias: {},
181
+ relationships: new Map(),
182
+ });
183
+ }
184
+ const service_ = expandedRelationships.get(extend.serviceName);
185
+ const aliasName = extend.relationship.alias;
186
+ const rel = extend.relationship;
187
+ if (service_.relationships?.has(aliasName)) {
188
+ const existing = service_.relationships.get(aliasName);
189
+ const newRelation = Array.isArray(existing)
190
+ ? existing.concat(rel)
191
+ : [existing, rel];
192
+ service_.relationships?.set(aliasName, newRelation);
193
+ }
194
+ else {
195
+ service_.relationships?.set(aliasName, rel);
196
+ }
197
+ Object.assign(service_.fieldAlias ?? {}, extend.fieldAlias);
198
+ }
199
+ }
200
+ for (const [serviceName, { fieldAlias, relationships },] of expandedRelationships) {
201
+ if (!this.serviceConfigCache.has(serviceName)) {
202
+ throw new Error(`Service "${serviceName}" was not found`);
203
+ }
204
+ const service_ = this.serviceConfigCache.get(serviceName);
205
+ relationships.forEach((relationship, alias) => {
206
+ const rel = relationship;
207
+ if (service_.relationships?.has(alias)) {
208
+ const existing = service_.relationships.get(alias);
209
+ const newRelation = Array.isArray(existing)
210
+ ? existing.concat(rel)
211
+ : [existing, rel];
212
+ service_.relationships?.set(alias, newRelation);
213
+ }
214
+ else {
215
+ service_.relationships?.set(alias, rel);
216
+ }
217
+ });
218
+ Object.assign(service_.fieldAlias, fieldAlias ?? {});
219
+ if (Object.keys(service_.fieldAlias).length) {
220
+ const conflictAliases = Array.from(service_.relationships.keys()).filter((alias) => fieldAlias[alias]);
221
+ if (conflictAliases.length) {
222
+ throw new Error(`Conflict configuration for service "${serviceName}". The following aliases are already defined as relationships: ${conflictAliases.join(", ")}`);
223
+ }
224
+ }
225
+ }
226
+ return serviceConfigs;
227
+ }
228
+ getServiceConfig({ serviceName, serviceAlias, entity, }) {
229
+ if (entity) {
230
+ const name = `entity_${entity}`;
231
+ const serviceConfig = this.serviceConfigCache.get(name);
232
+ if (serviceConfig) {
233
+ return serviceConfig;
234
+ }
235
+ }
236
+ if (serviceAlias) {
237
+ const name = `alias_${serviceAlias}`;
238
+ return this.serviceConfigCache.get(name);
239
+ }
240
+ return this.serviceConfigCache.get(serviceName);
241
+ }
242
+ cacheServiceConfig(serviceConfigs, params) {
243
+ const { serviceName, serviceAlias } = params;
244
+ if (serviceAlias) {
245
+ const name = `alias_${serviceAlias.name}`;
246
+ if (!this.serviceConfigCache.has(name)) {
247
+ let aliasConfig;
248
+ const config = serviceConfigs.find((conf) => {
249
+ const aliases = conf.alias;
250
+ const hasArgs = aliases?.find((alias) => alias.name === serviceAlias.name);
251
+ aliasConfig = hasArgs;
252
+ return hasArgs;
253
+ });
254
+ if (config) {
255
+ const serviceConfig = { ...config, entity: serviceAlias.entity };
256
+ if (aliasConfig) {
257
+ serviceConfig.args = { ...config?.args, ...aliasConfig?.args };
258
+ }
259
+ this.serviceConfigCache.set(name, serviceConfig);
260
+ const entity = serviceAlias.entity;
261
+ if (entity) {
262
+ const name = `entity_${entity}`;
263
+ this.serviceConfigCache.set(name, serviceConfig);
264
+ }
265
+ }
266
+ }
267
+ return;
268
+ }
269
+ const config = serviceConfigs.find((config) => config.serviceName === serviceName);
270
+ this.serviceConfigCache.set(serviceName, config);
271
+ }
272
+ async fetchData(params) {
273
+ const { expand, pkField, ids, relationship, options } = params;
274
+ let uniqueIds;
275
+ if (ids != null) {
276
+ const isIdsUsingOperatorMap = (0, utils_1.isObject)(ids) &&
277
+ Object.keys(ids).some((key) => !!utils_1.FilterOperatorMap[key]);
278
+ uniqueIds = isIdsUsingOperatorMap ? ids : Array.isArray(ids) ? ids : [ids];
279
+ }
280
+ if (uniqueIds && Array.isArray(uniqueIds)) {
281
+ const isCompositeKey = Array.isArray(uniqueIds[0]);
282
+ if (isCompositeKey) {
283
+ const seen = new Set();
284
+ uniqueIds = uniqueIds.filter((idArray) => {
285
+ const key = JSON.stringify(idArray);
286
+ const isNew = !seen.has(key);
287
+ seen.add(key);
288
+ return isNew;
289
+ });
290
+ }
291
+ else {
292
+ uniqueIds = Array.from(new Set(uniqueIds.flat()));
293
+ }
294
+ uniqueIds = uniqueIds.filter((id) => (0, utils_1.isDefined)(id));
295
+ }
296
+ let pkFieldAdjusted = pkField;
297
+ if (relationship) {
298
+ pkFieldAdjusted = relationship.inverse
299
+ ? relationship.foreignKey.split(".").pop()
300
+ : relationship.primaryKey;
301
+ }
302
+ const response = await this.remoteFetchData(expand, pkFieldAdjusted, uniqueIds, relationship);
303
+ const isObj = (0, utils_1.isDefined)(response.path);
304
+ let resData = isObj ? response.data[response.path] : response.data;
305
+ resData = (0, utils_1.isDefined)(resData)
306
+ ? Array.isArray(resData)
307
+ ? resData
308
+ : [resData]
309
+ : [];
310
+ this.checkIfKeysExist({
311
+ uniqueIds,
312
+ resData,
313
+ expand,
314
+ pkField: pkFieldAdjusted,
315
+ relationship,
316
+ options,
317
+ });
318
+ const filteredDataArray = resData.map((data) => RemoteJoiner.filterFields(data, expand.fields, expand.expands));
319
+ if (isObj) {
320
+ response.data[response.path] = filteredDataArray;
321
+ }
322
+ else {
323
+ response.data = filteredDataArray;
324
+ }
325
+ return response;
326
+ }
327
+ checkIfKeysExist(params) {
328
+ const { uniqueIds, resData, expand, pkField, relationship, options } = params;
329
+ if (!((0, utils_1.isDefined)(uniqueIds) &&
330
+ ((options?.throwIfKeyNotFound && !(0, utils_1.isDefined)(relationship)) ||
331
+ (options?.throwIfRelationNotFound && (0, utils_1.isDefined)(relationship))))) {
332
+ return;
333
+ }
334
+ if ((0, utils_1.isDefined)(relationship)) {
335
+ if (Array.isArray(options?.throwIfRelationNotFound) &&
336
+ !options?.throwIfRelationNotFound.includes(relationship.serviceName)) {
337
+ return;
338
+ }
339
+ }
340
+ const notFound = new Set(uniqueIds);
341
+ resData.forEach((data) => {
342
+ notFound.delete(data[pkField]);
343
+ });
344
+ if (notFound.size > 0) {
345
+ const entityName = expand.serviceConfig.entity ??
346
+ expand.serviceConfig.args?.methodSuffix ??
347
+ expand.serviceConfig.serviceName;
348
+ throw new utils_1.EtoError(utils_1.EtoError.Types.NOT_FOUND, `${entityName} ${pkField} not found: ` + Array.from(notFound).join(", "));
349
+ }
350
+ }
351
+ handleFieldAliases(params) {
352
+ const { items, parsedExpands, implodeMapping } = params;
353
+ const getChildren = (item, prop) => {
354
+ if (Array.isArray(item)) {
355
+ return item.flatMap((currentItem) => currentItem[prop]);
356
+ }
357
+ else {
358
+ return item[prop];
359
+ }
360
+ };
361
+ const removeChildren = (item, prop) => {
362
+ if (Array.isArray(item)) {
363
+ item.forEach((currentItem) => delete currentItem[prop]);
364
+ }
365
+ else {
366
+ delete item[prop];
367
+ }
368
+ };
369
+ const cleanup = [];
370
+ for (const alias of implodeMapping) {
371
+ const propPath = alias.path;
372
+ let itemsLocation = items;
373
+ for (const locationProp of alias.location) {
374
+ propPath.shift();
375
+ itemsLocation = RemoteJoiner.getNestedItems(itemsLocation, locationProp);
376
+ }
377
+ itemsLocation.forEach((locationItem) => {
378
+ if (!locationItem) {
379
+ return;
380
+ }
381
+ let currentItems = locationItem;
382
+ let parentRemoveItems = null;
383
+ const curPath = [BASE_PATH].concat(alias.location);
384
+ for (const prop of propPath) {
385
+ if (!(0, utils_1.isDefined)(currentItems)) {
386
+ break;
387
+ }
388
+ curPath.push(prop);
389
+ const config = parsedExpands.get(curPath.join("."));
390
+ if (config?.isAliasMapping && parentRemoveItems === null) {
391
+ parentRemoveItems = [currentItems, prop];
392
+ }
393
+ currentItems = getChildren(currentItems, prop);
394
+ }
395
+ if (Array.isArray(currentItems)) {
396
+ if (currentItems.length < 2 && !alias.isList) {
397
+ locationItem[alias.property] = currentItems.shift();
398
+ }
399
+ else {
400
+ locationItem[alias.property] = currentItems;
401
+ }
402
+ }
403
+ else {
404
+ locationItem[alias.property] = alias.isList
405
+ ? (0, utils_1.isDefined)(currentItems)
406
+ ? [currentItems]
407
+ : []
408
+ : currentItems;
409
+ }
410
+ if (parentRemoveItems !== null) {
411
+ cleanup.push(parentRemoveItems);
412
+ }
413
+ });
414
+ }
415
+ for (const parentRemoveItems of cleanup) {
416
+ const [remItems, path] = parentRemoveItems;
417
+ removeChildren(remItems, path);
418
+ }
419
+ }
420
+ async handleExpands(params) {
421
+ const { items, parsedExpands, implodeMapping = [], options } = params;
422
+ if (!parsedExpands) {
423
+ return;
424
+ }
425
+ for (const [expandedPath, expand] of parsedExpands.entries()) {
426
+ if (expandedPath === BASE_PATH) {
427
+ continue;
428
+ }
429
+ let nestedItems = items;
430
+ const expandedPathLevels = expandedPath.split(".");
431
+ for (let idx = 1; idx < expandedPathLevels.length - 1; idx++) {
432
+ nestedItems = RemoteJoiner.getNestedItems(nestedItems, expandedPathLevels[idx]);
433
+ }
434
+ if (nestedItems.length > 0) {
435
+ await this.expandProperty({
436
+ items: nestedItems,
437
+ parentServiceConfig: expand.parentConfig,
438
+ expand,
439
+ options,
440
+ });
441
+ }
442
+ }
443
+ this.handleFieldAliases({ items, parsedExpands, implodeMapping });
444
+ }
445
+ getEntityRelationship(params) {
446
+ const { parentServiceConfig, property, entity } = params;
447
+ const propEntity = entity ?? parentServiceConfig?.entity;
448
+ const rel = parentServiceConfig?.relationships?.get(property);
449
+ if (Array.isArray(rel)) {
450
+ if (!propEntity) {
451
+ return rel[0];
452
+ }
453
+ const entityRel = rel.find((r) => r.entity === propEntity);
454
+ if (entityRel) {
455
+ return entityRel;
456
+ }
457
+ // If entity is not found, return the relationship where the primary key matches
458
+ const serviceEntity = this.getServiceConfig({
459
+ entity: propEntity,
460
+ });
461
+ return rel.find((r) => serviceEntity.primaryKeys.includes(r.primaryKey));
462
+ }
463
+ return rel;
464
+ }
465
+ async expandProperty(params) {
466
+ const { items, parentServiceConfig, expand, options } = params;
467
+ if (!expand) {
468
+ return;
469
+ }
470
+ const relationship = this.getEntityRelationship({
471
+ parentServiceConfig,
472
+ property: expand.property,
473
+ entity: expand.entity,
474
+ });
475
+ if (!relationship) {
476
+ return;
477
+ }
478
+ await this.expandRelationshipProperty({
479
+ items,
480
+ expand,
481
+ relationship,
482
+ options,
483
+ });
484
+ }
485
+ async expandRelationshipProperty(params) {
486
+ const { items, expand, relationship, options } = params;
487
+ const field = relationship.inverse
488
+ ? relationship.primaryKey
489
+ : relationship.foreignKey.split(".").pop();
490
+ const fieldsArray = field.split(",");
491
+ const idsToFetch = [];
492
+ items.forEach((item) => {
493
+ const values = fieldsArray.map((field) => item?.[field]);
494
+ if (values.length === fieldsArray.length && !item?.[relationship.alias]) {
495
+ if (fieldsArray.length === 1) {
496
+ if (!idsToFetch.includes(values[0])) {
497
+ idsToFetch.push(values[0]);
498
+ }
499
+ }
500
+ else {
501
+ // composite key
502
+ const valuesString = values.join(",");
503
+ if (!idsToFetch.some((id) => id.join(",") === valuesString)) {
504
+ idsToFetch.push(values);
505
+ }
506
+ }
507
+ }
508
+ });
509
+ if (idsToFetch.length === 0) {
510
+ return;
511
+ }
512
+ const relatedDataArray = await this.fetchData({
513
+ expand,
514
+ pkField: field,
515
+ ids: idsToFetch,
516
+ relationship,
517
+ options,
518
+ });
519
+ const joinFields = relationship.inverse
520
+ ? relationship.foreignKey.split(",")
521
+ : relationship.primaryKey.split(",");
522
+ const relData = relatedDataArray.path
523
+ ? relatedDataArray.data[relatedDataArray.path]
524
+ : relatedDataArray.data;
525
+ const relatedDataMap = RemoteJoiner.createRelatedDataMap(relData, joinFields);
526
+ items.forEach((item) => {
527
+ if (!item || item[relationship.alias]) {
528
+ return;
529
+ }
530
+ const itemKey = fieldsArray.map((field) => item[field]).join(",");
531
+ if (Array.isArray(item[field])) {
532
+ item[relationship.alias] = item[field].map((id) => {
533
+ if (relationship.isList && !Array.isArray(relatedDataMap[id])) {
534
+ relatedDataMap[id] = (0, utils_1.isDefined)(relatedDataMap[id])
535
+ ? [relatedDataMap[id]]
536
+ : [];
537
+ }
538
+ return relatedDataMap[id];
539
+ });
540
+ }
541
+ else {
542
+ if (relationship.isList && !Array.isArray(relatedDataMap[itemKey])) {
543
+ relatedDataMap[itemKey] = (0, utils_1.isDefined)(relatedDataMap[itemKey])
544
+ ? [relatedDataMap[itemKey]]
545
+ : [];
546
+ }
547
+ item[relationship.alias] = relatedDataMap[itemKey];
548
+ }
549
+ });
550
+ }
551
+ parseExpands(params) {
552
+ const { initialService, query, serviceConfig, expands, implodeMapping } = params;
553
+ const parsedExpands = this.parseProperties({
554
+ initialService,
555
+ query,
556
+ serviceConfig,
557
+ expands,
558
+ implodeMapping,
559
+ });
560
+ const groupedExpands = this.groupExpands(parsedExpands);
561
+ return groupedExpands;
562
+ }
563
+ parseProperties(params) {
564
+ const { initialService, query, serviceConfig, expands, implodeMapping } = params;
565
+ const aliasRealPathMap = new Map();
566
+ const parsedExpands = new Map();
567
+ parsedExpands.set(BASE_PATH, initialService);
568
+ const forwardArgumentsOnPath = [];
569
+ for (const expand of expands || []) {
570
+ const properties = expand.property.split(".");
571
+ const currentPath = [];
572
+ const currentAliasPath = [];
573
+ let currentServiceConfig = serviceConfig;
574
+ for (const prop of properties) {
575
+ const fieldAlias = currentServiceConfig.fieldAlias ?? {};
576
+ if (fieldAlias[prop]) {
577
+ const aliasPath = [BASE_PATH, ...currentPath, prop].join(".");
578
+ const lastServiceConfig = this.parseAlias({
579
+ aliasPath,
580
+ aliasRealPathMap,
581
+ expands,
582
+ expand,
583
+ property: prop,
584
+ parsedExpands,
585
+ currentServiceConfig,
586
+ currentPath,
587
+ implodeMapping,
588
+ forwardArgumentsOnPath,
589
+ });
590
+ currentAliasPath.push(prop);
591
+ currentServiceConfig = lastServiceConfig;
592
+ continue;
593
+ }
594
+ const fullPath = [BASE_PATH, ...currentPath, prop].join(".");
595
+ const fullAliasPath = [BASE_PATH, ...currentAliasPath, prop].join(".");
596
+ let entity = currentServiceConfig.entity;
597
+ if (entity) {
598
+ const completePath = fullPath.split(".");
599
+ for (let i = 1; i < completePath.length; i++) {
600
+ entity = this.getEntity({ entity, prop: completePath[i] }) ?? entity;
601
+ }
602
+ }
603
+ const relationship = this.getEntityRelationship({
604
+ parentServiceConfig: currentServiceConfig,
605
+ property: prop,
606
+ entity,
607
+ });
608
+ const isCurrentProp = fullPath === BASE_PATH + "." + expand.property ||
609
+ fullAliasPath == BASE_PATH + "." + expand.property;
610
+ let fields = isCurrentProp ? expand.fields ?? [] : [];
611
+ const args = isCurrentProp ? expand.args : [];
612
+ if (relationship) {
613
+ const parentExpand = parsedExpands.get([BASE_PATH, ...currentPath].join(".")) || query;
614
+ if (parentExpand) {
615
+ const parRelField = relationship.inverse
616
+ ? relationship.primaryKey
617
+ : relationship.foreignKey.split(".").pop();
618
+ parentExpand.fields ??= [];
619
+ parentExpand.fields = parentExpand.fields
620
+ .concat(parRelField.split(","))
621
+ .filter((field) => field !== relationship.alias);
622
+ parentExpand.fields = (0, utils_1.deduplicate)(parentExpand.fields);
623
+ const relField = relationship.inverse
624
+ ? relationship.foreignKey.split(".").pop()
625
+ : relationship.primaryKey;
626
+ fields = fields.concat(relField.split(","));
627
+ }
628
+ currentServiceConfig = this.getServiceConfig({
629
+ serviceName: relationship.serviceName,
630
+ entity: relationship.entity,
631
+ });
632
+ if (!currentServiceConfig) {
633
+ throw new Error(`Target service not found: ${relationship.serviceName}`);
634
+ }
635
+ }
636
+ const isAliasMapping = expand.isAliasMapping;
637
+ if (!parsedExpands.has(fullPath)) {
638
+ let parentPath = [BASE_PATH, ...currentPath].join(".");
639
+ if (aliasRealPathMap.has(parentPath)) {
640
+ parentPath = aliasRealPathMap
641
+ .get(parentPath)
642
+ .slice(0, -1)
643
+ .join(".");
644
+ }
645
+ parsedExpands.set(fullPath, {
646
+ property: prop,
647
+ serviceConfig: currentServiceConfig,
648
+ entity: entity,
649
+ fields,
650
+ args: isAliasMapping
651
+ ? forwardArgumentsOnPath.includes(fullPath)
652
+ ? args
653
+ : undefined
654
+ : args,
655
+ isAliasMapping: isAliasMapping,
656
+ parent: parentPath,
657
+ parentConfig: parsedExpands.get(parentPath).serviceConfig,
658
+ });
659
+ }
660
+ else {
661
+ const exp = parsedExpands.get(fullPath);
662
+ if (forwardArgumentsOnPath.includes(fullPath) && args) {
663
+ exp.args = (exp.args || []).concat(args);
664
+ }
665
+ exp.isAliasMapping ??= isAliasMapping;
666
+ if (fields) {
667
+ exp.fields = (0, utils_1.deduplicate)((exp.fields ?? []).concat(fields));
668
+ }
669
+ }
670
+ currentPath.push(prop);
671
+ currentAliasPath.push(prop);
672
+ }
673
+ }
674
+ return parsedExpands;
675
+ }
676
+ getEntity({ entity, prop }) {
677
+ return this.entityMap.get(entity)?.get(prop);
678
+ }
679
+ parseAlias({ aliasPath, aliasRealPathMap, expands, expand, property, parsedExpands, currentServiceConfig, currentPath, implodeMapping, forwardArgumentsOnPath, }) {
680
+ const serviceConfig = currentServiceConfig;
681
+ const fieldAlias = currentServiceConfig.fieldAlias ?? {};
682
+ const alias = fieldAlias[property];
683
+ const path = (0, utils_1.isString)(alias) ? alias : alias.path;
684
+ const fieldAliasIsList = (0, utils_1.isString)(alias) ? false : !!alias.isList;
685
+ const fullPath = [...currentPath.concat(path.split("."))];
686
+ if (aliasRealPathMap.has(aliasPath)) {
687
+ currentPath.push(...path.split("."));
688
+ const fullPath = [BASE_PATH, ...currentPath].join(".");
689
+ return parsedExpands.get(fullPath).serviceConfig;
690
+ }
691
+ const parentPath = [BASE_PATH, ...currentPath].join(".");
692
+ const parentExpands = parsedExpands.get(parentPath);
693
+ parentExpands.fields = parentExpands.fields?.filter((field) => field !== property);
694
+ forwardArgumentsOnPath.push(...(alias?.forwardArgumentsOnPath || []).map((forPath) => BASE_PATH + "." + currentPath.concat(forPath).join(".")));
695
+ const parentFieldAlias = fullPath[Math.max(fullPath.length - 2, 0)];
696
+ implodeMapping.push({
697
+ location: [...currentPath],
698
+ property,
699
+ path: fullPath,
700
+ isList: fieldAliasIsList ||
701
+ !!serviceConfig.relationships?.get(parentFieldAlias)?.isList,
702
+ });
703
+ const extMapping = expands;
704
+ const fullAliasProp = fullPath.join(".");
705
+ const middlePath = path.split(".");
706
+ let curMiddlePath = currentPath;
707
+ for (const path of middlePath) {
708
+ curMiddlePath = curMiddlePath.concat(path);
709
+ const midProp = curMiddlePath.join(".");
710
+ const existingExpand = expands.find((exp) => exp.property === midProp);
711
+ const extraExtends = {
712
+ ...(midProp === fullAliasProp ? expand : {}),
713
+ property: midProp,
714
+ isAliasMapping: !existingExpand,
715
+ };
716
+ if (forwardArgumentsOnPath.includes(BASE_PATH + "." + midProp)) {
717
+ extraExtends.args = (existingExpand?.args ?? []).concat(expand?.args ?? []);
718
+ }
719
+ extMapping.push(extraExtends);
720
+ }
721
+ const partialPath = [];
722
+ for (const partial of path.split(".")) {
723
+ const completePath = [
724
+ BASE_PATH,
725
+ ...currentPath.concat(partialPath),
726
+ partial,
727
+ ];
728
+ const parentPath = completePath.slice(0, -1).join(".");
729
+ let entity = serviceConfig.entity;
730
+ if (entity) {
731
+ for (let i = 1; i < completePath.length; i++) {
732
+ entity = this.getEntity({ entity, prop: completePath[i] }) ?? entity;
733
+ }
734
+ }
735
+ const relationship = this.getEntityRelationship({
736
+ parentServiceConfig: currentServiceConfig,
737
+ property: partial,
738
+ entity,
739
+ });
740
+ if (relationship) {
741
+ currentServiceConfig = this.getServiceConfig({
742
+ serviceName: relationship.serviceName,
743
+ entity: relationship.entity,
744
+ });
745
+ if (!currentServiceConfig) {
746
+ throw new Error(`Target service not found: ${relationship.serviceName}`);
747
+ }
748
+ }
749
+ partialPath.push(partial);
750
+ parsedExpands.set(completePath.join("."), {
751
+ property: partial,
752
+ serviceConfig: currentServiceConfig,
753
+ entity: entity,
754
+ parent: parentPath,
755
+ parentConfig: parsedExpands.get(parentPath).serviceConfig,
756
+ });
757
+ }
758
+ currentPath.push(...path.split("."));
759
+ aliasRealPathMap.set(aliasPath, [BASE_PATH, ...currentPath]);
760
+ return currentServiceConfig;
761
+ }
762
+ groupExpands(parsedExpands) {
763
+ const mergedExpands = new Map(parsedExpands);
764
+ const mergedPaths = new Map();
765
+ for (const [path, expand] of mergedExpands.entries()) {
766
+ const currentServiceName = expand.serviceConfig.serviceName;
767
+ let parentPath = expand.parent;
768
+ while (parentPath) {
769
+ const parentExpand = mergedExpands.get(parentPath) ?? mergedPaths.get(parentPath);
770
+ if (!parentExpand ||
771
+ parentExpand.serviceConfig.serviceName !== currentServiceName) {
772
+ break;
773
+ }
774
+ const nestedKeys = path.split(".").slice(parentPath.split(".").length);
775
+ let targetExpand = parentExpand;
776
+ for (const key of nestedKeys) {
777
+ targetExpand.expands ??= {};
778
+ targetExpand = targetExpand.expands[key] ??= {};
779
+ }
780
+ targetExpand.fields = [...new Set(expand.fields)];
781
+ targetExpand.args = expand.args;
782
+ mergedExpands.delete(path);
783
+ mergedPaths.set(path, expand);
784
+ parentPath = parentExpand.parent;
785
+ }
786
+ }
787
+ return mergedExpands;
788
+ }
789
+ async query(queryObj, options) {
790
+ const serviceConfig = this.getServiceConfig({
791
+ serviceName: queryObj.service,
792
+ serviceAlias: queryObj.alias,
793
+ });
794
+ if (!serviceConfig) {
795
+ if (queryObj.alias) {
796
+ throw new Error(`Service with alias "${queryObj.alias}" was not found.`);
797
+ }
798
+ throw new Error(`Service "${queryObj.service}" was not found.`);
799
+ }
800
+ const { primaryKeyArg, otherArgs, pkName } = gerPrimaryKeysAndOtherFilters({
801
+ serviceConfig,
802
+ queryObj,
803
+ });
804
+ const implodeMapping = [];
805
+ const parseExpandsConfig = {
806
+ initialService: {
807
+ property: "",
808
+ parent: "",
809
+ serviceConfig,
810
+ entity: serviceConfig.entity,
811
+ fields: queryObj.fields,
812
+ },
813
+ query: queryObj,
814
+ serviceConfig,
815
+ expands: queryObj.expands,
816
+ implodeMapping,
817
+ options,
818
+ };
819
+ if (otherArgs) {
820
+ parseExpandsConfig.initialService.args = otherArgs;
821
+ }
822
+ const parsedExpands = this.parseExpands(parseExpandsConfig);
823
+ const root = parsedExpands.get(BASE_PATH);
824
+ const response = await this.fetchData({
825
+ expand: root,
826
+ pkField: pkName,
827
+ ids: primaryKeyArg?.value,
828
+ options,
829
+ });
830
+ const data = response.path ? response.data[response.path] : response.data;
831
+ await this.handleExpands({
832
+ items: Array.isArray(data) ? data : [data],
833
+ parsedExpands,
834
+ implodeMapping,
835
+ options,
836
+ });
837
+ return response.data;
838
+ }
839
+ }
840
+ exports.RemoteJoiner = RemoteJoiner;
841
+ function gerPrimaryKeysAndOtherFilters({ serviceConfig, queryObj }) {
842
+ let pkName = serviceConfig.primaryKeys[0];
843
+ let primaryKeyArg = queryObj.args?.find((arg) => {
844
+ const include = serviceConfig.primaryKeys.includes(arg.name);
845
+ if (include) {
846
+ pkName = arg.name;
847
+ }
848
+ return include;
849
+ });
850
+ let otherArgs = queryObj.args?.filter((arg) => !serviceConfig.primaryKeys.includes(arg.name));
851
+ const filters = queryObj.args?.find((arg) => arg.name === "filters")?.value ?? {};
852
+ if (!primaryKeyArg) {
853
+ const primaryKeyFilter = Object.keys(filters).find((key) => {
854
+ return serviceConfig.primaryKeys.includes(key);
855
+ });
856
+ if (primaryKeyFilter) {
857
+ pkName = primaryKeyFilter;
858
+ primaryKeyArg = {
859
+ name: primaryKeyFilter,
860
+ value: filters[primaryKeyFilter],
861
+ };
862
+ delete filters[primaryKeyFilter];
863
+ }
864
+ }
865
+ otherArgs = otherArgs?.length ? otherArgs : undefined;
866
+ return {
867
+ primaryKeyArg,
868
+ otherArgs,
869
+ pkName,
870
+ };
871
+ }
872
+ //# sourceMappingURL=remote-joiner.js.map