@acmekit/orchestration 2.13.1

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 +43 -0
  14. package/dist/joiner/remote-joiner.d.ts.map +1 -0
  15. package/dist/joiner/remote-joiner.js +1279 -0
  16. package/dist/joiner/remote-joiner.js.map +1 -0
  17. package/dist/transaction/datastore/abstract-storage.d.ts +44 -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 +12 -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 +35 -0
  24. package/dist/transaction/datastore/base-in-memory-storage.js.map +1 -0
  25. package/dist/transaction/distributed-transaction.d.ts +116 -0
  26. package/dist/transaction/distributed-transaction.d.ts.map +1 -0
  27. package/dist/transaction/distributed-transaction.js +488 -0
  28. package/dist/transaction/distributed-transaction.js.map +1 -0
  29. package/dist/transaction/errors.d.ts +41 -0
  30. package/dist/transaction/errors.d.ts.map +1 -0
  31. package/dist/transaction/errors.js +117 -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 +207 -0
  42. package/dist/transaction/transaction-orchestrator.d.ts.map +1 -0
  43. package/dist/transaction/transaction-orchestrator.js +1292 -0
  44. package/dist/transaction/transaction-orchestrator.js.map +1 -0
  45. package/dist/transaction/transaction-step.d.ts +69 -0
  46. package/dist/transaction/transaction-step.d.ts.map +1 -0
  47. package/dist/transaction/transaction-step.js +153 -0
  48. package/dist/transaction/transaction-step.js.map +1 -0
  49. package/dist/transaction/types.d.ts +264 -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 +105 -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 +47 -0
  63. package/dist/workflow/local-workflow.d.ts.map +1 -0
  64. package/dist/workflow/local-workflow.js +390 -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 +35 -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 +41 -0
@@ -0,0 +1,1279 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RemoteJoiner = void 0;
4
+ const utils_1 = require("@acmekit/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
+ // compute ids to fetch for a relationship
75
+ computeIdsForRelationship(items, relationship) {
76
+ const field = relationship.inverse
77
+ ? relationship.primaryKey
78
+ : relationship.foreignKey.split(".").pop();
79
+ const fieldsArray = relationship.inverse
80
+ ? relationship.primaryKeyArr
81
+ : relationship.foreignKeyArr;
82
+ const idsToFetch = new Set();
83
+ for (let i = 0; i < items.length; i++) {
84
+ const item = items[i];
85
+ if (!item) {
86
+ continue;
87
+ }
88
+ const values = fieldsArray.map((f) => item?.[f]);
89
+ if (values.length !== fieldsArray.length) {
90
+ continue;
91
+ }
92
+ if (fieldsArray.length === 1) {
93
+ const val = values[0];
94
+ if (Array.isArray(val)) {
95
+ for (let x = 0; x < val.length; x++) {
96
+ idsToFetch.add(val[x]);
97
+ }
98
+ }
99
+ else {
100
+ idsToFetch.add(val);
101
+ }
102
+ }
103
+ else {
104
+ idsToFetch.add(values);
105
+ }
106
+ }
107
+ return { field, fieldsArray, idsToFetch };
108
+ }
109
+ // assign fetched related data to items
110
+ assignRelatedToItems(params) {
111
+ const { items, relationship, relatedDataMap, field, fieldsArray } = params;
112
+ items.forEach((item) => {
113
+ if (!item) {
114
+ return;
115
+ }
116
+ const itemKey = fieldsArray.map((f) => item[f]).join(",");
117
+ if (item[relationship.alias]) {
118
+ if (Array.isArray(item[field])) {
119
+ for (let i = 0; i < item[relationship.alias].length; i++) {
120
+ const it = item[relationship.alias][i];
121
+ item[relationship.alias][i] = Object.assign(it, relatedDataMap[it[relationship.primaryKey]]);
122
+ }
123
+ return;
124
+ }
125
+ item[relationship.alias] = Object.assign(item[relationship.alias], relatedDataMap[itemKey]);
126
+ return;
127
+ }
128
+ if (Array.isArray(item[field])) {
129
+ item[relationship.alias] = item[field].map((id) => {
130
+ if (relationship.isList && !Array.isArray(relatedDataMap[id])) {
131
+ relatedDataMap[id] = (0, utils_1.isDefined)(relatedDataMap[id])
132
+ ? [relatedDataMap[id]]
133
+ : [];
134
+ }
135
+ return relatedDataMap[id];
136
+ });
137
+ }
138
+ else {
139
+ if (relationship.isList && !Array.isArray(relatedDataMap[itemKey])) {
140
+ relatedDataMap[itemKey] = (0, utils_1.isDefined)(relatedDataMap[itemKey])
141
+ ? [relatedDataMap[itemKey]]
142
+ : [];
143
+ }
144
+ item[relationship.alias] = relatedDataMap[itemKey];
145
+ }
146
+ });
147
+ }
148
+ static parseQuery(graphqlQuery, variables) {
149
+ const parser = new utils_1.GraphQLUtils.GraphQLParser(graphqlQuery, variables);
150
+ return parser.parseQuery();
151
+ }
152
+ constructor(serviceConfigs, remoteFetchData, options = {}) {
153
+ this.remoteFetchData = remoteFetchData;
154
+ this.options = options;
155
+ this.serviceConfigCache = new Map();
156
+ this.entityMap = new Map();
157
+ this.options.autoCreateServiceNameAlias ??= true;
158
+ if (this.options.entitiesMap) {
159
+ this.entityMap = utils_1.GraphQLUtils.extractRelationsFromGQL(this.options.entitiesMap);
160
+ }
161
+ this.buildReferences(JSON.parse(JSON.stringify(serviceConfigs), (key, value) => {
162
+ if (key === "schema") {
163
+ return;
164
+ }
165
+ return value;
166
+ }));
167
+ }
168
+ setFetchDataCallback(remoteFetchData) {
169
+ this.remoteFetchData = remoteFetchData;
170
+ }
171
+ buildReferences(serviceConfigs) {
172
+ const expandedRelationships = new Map();
173
+ for (const service of serviceConfigs) {
174
+ const service_ = service;
175
+ if (this.serviceConfigCache.has(service_.serviceName)) {
176
+ throw new Error(`Service "${service_.serviceName}" is already defined.`);
177
+ }
178
+ service_.fieldAlias ??= {};
179
+ service_.extends ??= [];
180
+ service_.relationships ??= new Map();
181
+ if (Array.isArray(service_.relationships)) {
182
+ const relationships = new Map();
183
+ for (const relationship of service_.relationships) {
184
+ relationships.set(relationship.alias, relationship);
185
+ }
186
+ service_.relationships = relationships;
187
+ }
188
+ // Precompute key arrays for all existing relationships on the service
189
+ if (service_.relationships?.size) {
190
+ for (const [, relVal] of service_.relationships.entries()) {
191
+ if (Array.isArray(relVal)) {
192
+ for (let i = 0; i < relVal.length; i++) {
193
+ const rel = relVal[i];
194
+ rel.primaryKeyArr = rel.primaryKey.split(",");
195
+ rel.foreignKeyArr = rel.foreignKey
196
+ .split(",")
197
+ .map((fk) => fk.split(".").pop());
198
+ }
199
+ }
200
+ else if (relVal) {
201
+ const rel = relVal;
202
+ rel.primaryKeyArr = rel.primaryKey.split(",");
203
+ rel.foreignKeyArr = rel.foreignKey
204
+ .split(",")
205
+ .map((fk) => fk.split(".").pop());
206
+ }
207
+ }
208
+ }
209
+ // add aliases
210
+ const isReadOnlyDefinition = !(0, utils_1.isDefined)(service_.serviceName) || service_.isReadOnlyLink;
211
+ if (!isReadOnlyDefinition) {
212
+ service_.alias ??= [];
213
+ if (!Array.isArray(service_.alias)) {
214
+ service_.alias = [service_.alias];
215
+ }
216
+ if (this.options.autoCreateServiceNameAlias) {
217
+ service_.alias.push({ name: service_.serviceName });
218
+ }
219
+ // handle alias.name as array
220
+ for (let idx = 0; idx < service_.alias.length; idx++) {
221
+ const alias = service_.alias[idx];
222
+ if (!Array.isArray(alias.name)) {
223
+ continue;
224
+ }
225
+ for (const name of alias.name) {
226
+ service_.alias.push({
227
+ name,
228
+ entity: alias.entity,
229
+ args: alias.args,
230
+ });
231
+ }
232
+ service_.alias.splice(idx, 1);
233
+ idx--;
234
+ }
235
+ // self-reference
236
+ for (const alias of service_.alias) {
237
+ if (this.serviceConfigCache.has(`alias_${alias.name}`)) {
238
+ const defined = this.serviceConfigCache.get(`alias_${alias.name}`);
239
+ if (service_.serviceName === defined?.serviceName) {
240
+ continue;
241
+ }
242
+ throw new Error(`Cannot add alias "${alias.name}" for "${service_.serviceName}". It is already defined for Service "${defined?.serviceName}".`);
243
+ }
244
+ const args = service_.args || alias.args
245
+ ? { ...service_.args, ...alias.args }
246
+ : undefined;
247
+ const aliasName = alias.name;
248
+ const rel = {
249
+ alias: aliasName,
250
+ entity: alias.entity,
251
+ foreignKey: alias.name + "_id",
252
+ primaryKey: "id",
253
+ serviceName: service_.serviceName,
254
+ args,
255
+ };
256
+ if (service_.relationships?.has(aliasName)) {
257
+ const existing = service_.relationships.get(aliasName);
258
+ const newRelation = Array.isArray(existing)
259
+ ? existing.concat(rel)
260
+ : [existing, rel];
261
+ service_.relationships?.set(aliasName, newRelation);
262
+ }
263
+ else {
264
+ service_.relationships?.set(aliasName, rel);
265
+ }
266
+ this.cacheServiceConfig(serviceConfigs, { serviceAlias: alias });
267
+ }
268
+ this.cacheServiceConfig(serviceConfigs, {
269
+ serviceName: service_.serviceName,
270
+ });
271
+ }
272
+ for (const extend of service_.extends) {
273
+ if (!expandedRelationships.has(extend.serviceName)) {
274
+ expandedRelationships.set(extend.serviceName, {
275
+ fieldAlias: {},
276
+ relationships: new Map(),
277
+ });
278
+ }
279
+ const service_ = expandedRelationships.get(extend.serviceName);
280
+ const aliasName = extend.relationship.alias;
281
+ const rel = extend.relationship;
282
+ rel.primaryKeyArr = rel.primaryKey.split(",");
283
+ rel.foreignKeyArr = rel.foreignKey
284
+ .split(",")
285
+ .map((fk) => fk.split(".").pop());
286
+ if (service_.relationships?.has(aliasName)) {
287
+ const existing = service_.relationships.get(aliasName);
288
+ const newRelation = Array.isArray(existing)
289
+ ? existing.concat(rel)
290
+ : [existing, rel];
291
+ service_.relationships?.set(aliasName, newRelation);
292
+ }
293
+ else {
294
+ service_.relationships?.set(aliasName, rel);
295
+ }
296
+ // Multiple "fieldAlias" w/ same name need the entity to handle different paths
297
+ this.mergeFieldAlias(service_, extend);
298
+ }
299
+ }
300
+ for (const [serviceName, { fieldAlias, relationships },] of expandedRelationships) {
301
+ if (!this.serviceConfigCache.has(serviceName)) {
302
+ throw new Error(`Service "${serviceName}" was not found`);
303
+ }
304
+ const service_ = this.serviceConfigCache.get(serviceName);
305
+ relationships.forEach((relationship, alias) => {
306
+ const rel = relationship;
307
+ if (service_.relationships?.has(alias)) {
308
+ const existing = service_.relationships.get(alias);
309
+ const newRelation = Array.isArray(existing)
310
+ ? existing.concat(rel)
311
+ : [existing, rel];
312
+ service_.relationships?.set(alias, newRelation);
313
+ }
314
+ else {
315
+ service_.relationships?.set(alias, rel);
316
+ }
317
+ });
318
+ Object.assign(service_.fieldAlias, fieldAlias ?? {});
319
+ if (Object.keys(service_.fieldAlias).length) {
320
+ const conflictAliases = Array.from(service_.relationships.keys()).filter((alias) => fieldAlias[alias]);
321
+ if (conflictAliases.length) {
322
+ throw new Error(`Conflict configuration for service "${serviceName}". The following aliases are already defined as relationships: ${conflictAliases.join(", ")}`);
323
+ }
324
+ }
325
+ }
326
+ return serviceConfigs;
327
+ }
328
+ mergeFieldAlias(service_, extend) {
329
+ for (const [alias, fieldAlias] of Object.entries(extend.fieldAlias ?? {})) {
330
+ const objAlias = (0, utils_1.isString)(fieldAlias)
331
+ ? { path: fieldAlias }
332
+ : fieldAlias;
333
+ if (service_.fieldAlias[alias]) {
334
+ if (!Array.isArray(service_.fieldAlias[alias])) {
335
+ service_.fieldAlias[alias] = [service_.fieldAlias[alias]];
336
+ }
337
+ if (service_.fieldAlias[alias].some((f) => f.entity === extend.entity)) {
338
+ throw new Error(`Cannot add alias "${alias}" for "${extend.serviceName}". It is already defined for Entity "${extend.entity}".`);
339
+ }
340
+ service_.fieldAlias[alias].push({
341
+ ...objAlias,
342
+ entity: extend.entity,
343
+ });
344
+ }
345
+ else {
346
+ service_.fieldAlias[alias] = {
347
+ ...objAlias,
348
+ entity: extend.entity,
349
+ };
350
+ }
351
+ }
352
+ }
353
+ getServiceConfig({ serviceName, serviceAlias, entity, }) {
354
+ if (entity) {
355
+ const name = `entity_${entity}`;
356
+ const serviceConfig = this.serviceConfigCache.get(name);
357
+ if (serviceConfig) {
358
+ return serviceConfig;
359
+ }
360
+ }
361
+ if (serviceAlias) {
362
+ const name = `alias_${serviceAlias}`;
363
+ return this.serviceConfigCache.get(name);
364
+ }
365
+ return this.serviceConfigCache.get(serviceName);
366
+ }
367
+ cacheServiceConfig(serviceConfigs, params) {
368
+ const { serviceName, serviceAlias } = params;
369
+ if (serviceAlias) {
370
+ const name = `alias_${serviceAlias.name}`;
371
+ if (!this.serviceConfigCache.has(name)) {
372
+ let aliasConfig;
373
+ const config = serviceConfigs.find((conf) => {
374
+ const aliases = conf.alias;
375
+ const hasArgs = aliases?.find((alias) => alias.name === serviceAlias.name);
376
+ aliasConfig = hasArgs;
377
+ return hasArgs;
378
+ });
379
+ if (config) {
380
+ const serviceConfig = { ...config, entity: serviceAlias.entity };
381
+ if (aliasConfig) {
382
+ serviceConfig.args = { ...config?.args, ...aliasConfig?.args };
383
+ }
384
+ this.serviceConfigCache.set(name, serviceConfig);
385
+ const entity = serviceAlias.entity;
386
+ if (entity) {
387
+ const name = `entity_${entity}`;
388
+ this.serviceConfigCache.set(name, serviceConfig);
389
+ }
390
+ }
391
+ }
392
+ return;
393
+ }
394
+ const config = serviceConfigs.find((config) => config.serviceName === serviceName);
395
+ this.serviceConfigCache.set(serviceName, config);
396
+ }
397
+ async fetchData(params) {
398
+ const { expand, pkField, ids, relationship, options } = params;
399
+ let uniqueIds;
400
+ if (ids != null) {
401
+ const isIdsUsingOperatorMap = (0, utils_1.isObject)(ids) &&
402
+ Object.keys(ids).some((key) => !!utils_1.FilterOperatorMap[key]);
403
+ uniqueIds = isIdsUsingOperatorMap ? ids : Array.isArray(ids) ? ids : [ids];
404
+ uniqueIds = Array.isArray(uniqueIds)
405
+ ? uniqueIds.filter((id) => id != null)
406
+ : uniqueIds;
407
+ }
408
+ if (uniqueIds && Array.isArray(uniqueIds)) {
409
+ const isCompositeKey = Array.isArray(uniqueIds[0]);
410
+ if (isCompositeKey) {
411
+ const seen = new Set();
412
+ uniqueIds = uniqueIds.filter((idArray) => {
413
+ const key = JSON.stringify(idArray);
414
+ const isNew = !seen.has(key);
415
+ seen.add(key);
416
+ return isNew;
417
+ });
418
+ }
419
+ else {
420
+ uniqueIds = Array.from(new Set(uniqueIds.flat()));
421
+ }
422
+ }
423
+ let pkFieldAdjusted = pkField;
424
+ if (relationship) {
425
+ pkFieldAdjusted = relationship.inverse
426
+ ? relationship.foreignKey.split(".").pop()
427
+ : relationship.primaryKey;
428
+ }
429
+ const response = await this.remoteFetchData(expand, pkFieldAdjusted, uniqueIds, relationship);
430
+ const isObj = (0, utils_1.isDefined)(response.path);
431
+ let resData = isObj ? response.data[response.path] : response.data;
432
+ resData = (0, utils_1.isDefined)(resData)
433
+ ? Array.isArray(resData)
434
+ ? resData
435
+ : [resData]
436
+ : [];
437
+ this.checkIfKeysExist({
438
+ uniqueIds,
439
+ resData,
440
+ expand,
441
+ pkField: pkFieldAdjusted,
442
+ relationship,
443
+ options,
444
+ });
445
+ const filteredDataArray = resData.map((data) => RemoteJoiner.filterFields(data, expand.fields, expand.expands));
446
+ if (isObj) {
447
+ response.data[response.path] = filteredDataArray;
448
+ }
449
+ else {
450
+ response.data = filteredDataArray;
451
+ }
452
+ return response;
453
+ }
454
+ checkIfKeysExist(params) {
455
+ const { uniqueIds, resData, expand, pkField, relationship, options } = params;
456
+ if (!((0, utils_1.isDefined)(uniqueIds) &&
457
+ ((options?.throwIfKeyNotFound && !(0, utils_1.isDefined)(relationship)) ||
458
+ (options?.throwIfRelationNotFound && (0, utils_1.isDefined)(relationship))))) {
459
+ return;
460
+ }
461
+ if ((0, utils_1.isDefined)(relationship)) {
462
+ if (Array.isArray(options?.throwIfRelationNotFound) &&
463
+ !options?.throwIfRelationNotFound.includes(relationship.serviceName)) {
464
+ return;
465
+ }
466
+ }
467
+ const notFound = new Set(uniqueIds);
468
+ resData.forEach((data) => {
469
+ notFound.delete(data[pkField]);
470
+ });
471
+ if (notFound.size > 0) {
472
+ const entityName = expand.serviceConfig.entity ??
473
+ expand.serviceConfig.args?.methodSuffix ??
474
+ expand.serviceConfig.serviceName;
475
+ throw new utils_1.AcmeKitError(utils_1.AcmeKitError.Types.NOT_FOUND, `${entityName} ${pkField} not found: ` + Array.from(notFound).join(", "));
476
+ }
477
+ }
478
+ handleFieldAliases(params) {
479
+ const { items, parsedExpands, implodeMapping } = params;
480
+ const getChildren = (item, prop) => {
481
+ if (Array.isArray(item)) {
482
+ return item.flatMap((currentItem) => currentItem[prop]);
483
+ }
484
+ else {
485
+ return item[prop];
486
+ }
487
+ };
488
+ const removeChildren = (item, prop) => {
489
+ if (Array.isArray(item)) {
490
+ for (let i = 0; i < item.length; i++) {
491
+ Object.defineProperty(item[i], prop, {
492
+ value: undefined,
493
+ enumerable: false,
494
+ });
495
+ }
496
+ }
497
+ else {
498
+ Object.defineProperty(item, prop, {
499
+ value: undefined,
500
+ enumerable: false,
501
+ });
502
+ }
503
+ };
504
+ const cleanup = [];
505
+ for (const alias of implodeMapping) {
506
+ const propPath = alias.path;
507
+ let itemsLocation = items;
508
+ for (const locationProp of alias.location) {
509
+ propPath.shift();
510
+ itemsLocation = RemoteJoiner.getNestedItems(itemsLocation, locationProp);
511
+ }
512
+ itemsLocation.forEach((locationItem) => {
513
+ if (!locationItem) {
514
+ return;
515
+ }
516
+ let currentItems = locationItem;
517
+ let parentRemoveItems = null;
518
+ const curPath = [BASE_PATH].concat(alias.location);
519
+ for (const prop of propPath) {
520
+ if (!(0, utils_1.isDefined)(currentItems)) {
521
+ break;
522
+ }
523
+ curPath.push(prop);
524
+ const config = parsedExpands.get(curPath.join("."));
525
+ if (config?.isAliasMapping && parentRemoveItems === null) {
526
+ parentRemoveItems = [currentItems, prop];
527
+ }
528
+ currentItems = getChildren(currentItems, prop);
529
+ }
530
+ if (Array.isArray(currentItems)) {
531
+ if (currentItems.length < 2 && !alias.isList) {
532
+ locationItem[alias.property] = currentItems.shift();
533
+ }
534
+ else {
535
+ locationItem[alias.property] = currentItems;
536
+ }
537
+ }
538
+ else {
539
+ locationItem[alias.property] = alias.isList
540
+ ? (0, utils_1.isDefined)(currentItems)
541
+ ? [currentItems]
542
+ : []
543
+ : currentItems;
544
+ }
545
+ if (parentRemoveItems !== null) {
546
+ cleanup.push(parentRemoveItems);
547
+ }
548
+ });
549
+ }
550
+ for (const parentRemoveItems of cleanup) {
551
+ const [remItems, path] = parentRemoveItems;
552
+ removeChildren(remItems, path);
553
+ }
554
+ }
555
+ async handleExpands(params) {
556
+ const { items, parsedExpands, implodeMapping = [], options } = params;
557
+ if (parsedExpands.size === 0) {
558
+ return;
559
+ }
560
+ const getItemsForPath = (rootItems, fullPath) => {
561
+ let nestedItems = rootItems;
562
+ const expandedPathLevels = fullPath.split(".");
563
+ for (let idx = 1; idx < expandedPathLevels.length - 1; idx++) {
564
+ nestedItems = RemoteJoiner.getNestedItems(nestedItems, expandedPathLevels[idx]);
565
+ }
566
+ return nestedItems;
567
+ };
568
+ const root = parsedExpands.get(BASE_PATH);
569
+ const executionStages = root?.executionStages;
570
+ // remove root
571
+ root?.executionStages.shift();
572
+ for (const stage of executionStages) {
573
+ const stageFetchGroups = [];
574
+ for (const { paths } of stage) {
575
+ const pathCtx = [];
576
+ for (const path of paths) {
577
+ const expand = parsedExpands.get(path);
578
+ const nestedItems = getItemsForPath(items, path);
579
+ if (!nestedItems?.length || !expand) {
580
+ continue;
581
+ }
582
+ const relationship = this.getEntityRelationship({
583
+ parentServiceConfig: expand.parentConfig,
584
+ property: expand.property,
585
+ entity: expand.entity,
586
+ });
587
+ if (!relationship) {
588
+ continue;
589
+ }
590
+ const { field, fieldsArray, idsToFetch } = this.computeIdsForRelationship(nestedItems, relationship);
591
+ pathCtx.push({
592
+ path,
593
+ expand,
594
+ relationship,
595
+ nestedItems,
596
+ field,
597
+ fieldsArray,
598
+ args: expand.args,
599
+ ids: idsToFetch,
600
+ });
601
+ }
602
+ if (!pathCtx.length) {
603
+ continue;
604
+ }
605
+ // Group by pkField
606
+ const byPkField = new Map();
607
+ for (const ctx of pathCtx) {
608
+ const key = ctx.field;
609
+ if (!byPkField.has(key)) {
610
+ byPkField.set(key, []);
611
+ }
612
+ byPkField.get(key).push(ctx);
613
+ }
614
+ for (const [pkField, ctxs] of byPkField.entries()) {
615
+ const unionIds = Array.from(new Set(ctxs.flatMap((c) => Array.from(c.ids))));
616
+ const unionFields = Array.from(new Set(ctxs.flatMap((c) => c.expand.fields ?? [])));
617
+ const unionArgs = ctxs.flatMap((c) => c.expand.args ?? []);
618
+ const base = ctxs[0].expand;
619
+ const aggExpand = {
620
+ ...base,
621
+ fields: unionFields,
622
+ };
623
+ if (unionArgs.length) {
624
+ aggExpand.args = unionArgs;
625
+ }
626
+ const relationship = ctxs[0].relationship;
627
+ const promise = this.fetchData({
628
+ expand: aggExpand,
629
+ pkField,
630
+ ids: unionIds,
631
+ relationship,
632
+ options,
633
+ });
634
+ stageFetchGroups.push({ ctxs, relationship, promise });
635
+ }
636
+ }
637
+ const stageResults = await Promise.all(stageFetchGroups.map((g) => g.promise));
638
+ for (let i = 0; i < stageFetchGroups.length; i++) {
639
+ const { ctxs, relationship } = stageFetchGroups[i];
640
+ const relatedDataArray = stageResults[i];
641
+ const joinFields = relationship.inverse
642
+ ? relationship.foreignKeyArr
643
+ : relationship.primaryKeyArr;
644
+ const relData = relatedDataArray.path
645
+ ? relatedDataArray.data[relatedDataArray.path]
646
+ : relatedDataArray.data;
647
+ const relatedDataMap = RemoteJoiner.createRelatedDataMap(relData, joinFields);
648
+ for (let ci = 0; ci < ctxs.length; ci++) {
649
+ const ctx = ctxs[ci];
650
+ this.assignRelatedToItems({
651
+ items: ctx.nestedItems,
652
+ relationship: ctx.relationship,
653
+ relatedDataMap,
654
+ field: ctx.field,
655
+ fieldsArray: ctx.fieldsArray,
656
+ });
657
+ }
658
+ }
659
+ }
660
+ if (implodeMapping.length > 0) {
661
+ this.handleFieldAliases({
662
+ items,
663
+ parsedExpands,
664
+ implodeMapping,
665
+ });
666
+ }
667
+ }
668
+ getEntityRelationship(params) {
669
+ const { parentServiceConfig, property, entity } = params;
670
+ const propEntity = entity ?? parentServiceConfig?.entity;
671
+ const rel = parentServiceConfig?.relationships?.get(property);
672
+ if (Array.isArray(rel)) {
673
+ if (!propEntity) {
674
+ return rel[0];
675
+ }
676
+ const entityRel = rel.find((r) => r.entity === propEntity);
677
+ if (entityRel) {
678
+ return entityRel;
679
+ }
680
+ // If entity is not found, return the relationship where the primary key matches
681
+ const serviceEntity = this.getServiceConfig({
682
+ entity: propEntity,
683
+ });
684
+ return rel.find((r) => serviceEntity.primaryKeys.includes(r.primaryKey));
685
+ }
686
+ return rel;
687
+ }
688
+ parseExpands(params) {
689
+ const { initialService, query, serviceConfig, expands, implodeMapping, options, initialData, initialDataOnly, } = params;
690
+ const { parsedExpands, aliasRealPathMap } = this.parseProperties({
691
+ initialService,
692
+ query,
693
+ serviceConfig,
694
+ expands,
695
+ implodeMapping,
696
+ });
697
+ if (initialData?.length && initialDataOnly) {
698
+ this.createFilterFromInitialData({
699
+ initialData: options?.initialData,
700
+ parsedExpands,
701
+ aliasRealPathMap,
702
+ });
703
+ }
704
+ const groupedExpands = this.groupExpands(parsedExpands);
705
+ this.buildQueryPlan(parsedExpands, groupedExpands);
706
+ return groupedExpands;
707
+ }
708
+ buildQueryPlan(fullParsedExpands, groupedExpands) {
709
+ const stages = [];
710
+ // Root stage
711
+ const rootExp = groupedExpands.get(BASE_PATH);
712
+ const rootService = rootExp.serviceConfig.serviceName;
713
+ stages.push([
714
+ {
715
+ service: rootService,
716
+ entity: rootExp.entity,
717
+ paths: [],
718
+ depth: 0,
719
+ },
720
+ ]);
721
+ // Build service sequence for each path
722
+ const getServiceSequence = (path) => {
723
+ const sequence = [];
724
+ let currentPath = path;
725
+ while (currentPath && currentPath !== BASE_PATH) {
726
+ const expand = fullParsedExpands.get(currentPath);
727
+ if (!expand) {
728
+ break;
729
+ }
730
+ sequence.unshift(expand.serviceConfig.serviceName);
731
+ currentPath = expand.parent;
732
+ }
733
+ return sequence;
734
+ };
735
+ // Group paths by their service sequence length and last service in sequence
736
+ const pathsBySequenceDepth = new Map();
737
+ for (const [path, expand] of groupedExpands.entries()) {
738
+ if (path === BASE_PATH) {
739
+ continue;
740
+ }
741
+ const serviceSequence = getServiceSequence(path);
742
+ const sequenceDepth = serviceSequence.length;
743
+ const lastService = expand.serviceConfig.serviceName;
744
+ if (!pathsBySequenceDepth.has(sequenceDepth)) {
745
+ pathsBySequenceDepth.set(sequenceDepth, new Map());
746
+ }
747
+ const depthMap = pathsBySequenceDepth.get(sequenceDepth);
748
+ if (!depthMap.has(lastService)) {
749
+ depthMap.set(lastService, []);
750
+ }
751
+ depthMap.get(lastService).push(path);
752
+ }
753
+ const maxDepth = Math.max(...Array.from(pathsBySequenceDepth.keys()));
754
+ for (let depth = 1; depth <= maxDepth; depth++) {
755
+ const serviceMap = pathsBySequenceDepth.get(depth);
756
+ if (!serviceMap) {
757
+ continue;
758
+ }
759
+ const stageGroups = [];
760
+ for (const [service, paths] of serviceMap.entries()) {
761
+ stageGroups.push({
762
+ service,
763
+ paths,
764
+ depth: depth,
765
+ });
766
+ }
767
+ if (stageGroups.length > 0) {
768
+ stages.push(stageGroups);
769
+ }
770
+ }
771
+ const root = groupedExpands.get(BASE_PATH);
772
+ root.executionStages = stages;
773
+ }
774
+ parseProperties(params) {
775
+ const { initialService, query, serviceConfig, expands, implodeMapping } = params;
776
+ const aliasRealPathMap = new Map();
777
+ const parsedExpands = new Map();
778
+ parsedExpands.set(BASE_PATH, initialService);
779
+ const forwardArgumentsOnPath = [];
780
+ for (const expand of expands || []) {
781
+ const properties = expand.property.split(".");
782
+ const currentPath = [];
783
+ const currentAliasPath = [];
784
+ let currentServiceConfig = serviceConfig;
785
+ for (const prop of properties) {
786
+ const fieldAlias = currentServiceConfig.fieldAlias ?? {};
787
+ if (fieldAlias[prop]) {
788
+ const aliasPath = [BASE_PATH, ...currentPath, prop].join(".");
789
+ const lastServiceConfig = this.parseAlias({
790
+ aliasPath,
791
+ aliasRealPathMap,
792
+ expands,
793
+ expand,
794
+ property: prop,
795
+ parsedExpands,
796
+ currentServiceConfig,
797
+ currentPath,
798
+ implodeMapping,
799
+ forwardArgumentsOnPath,
800
+ });
801
+ currentAliasPath.push(prop);
802
+ currentServiceConfig = lastServiceConfig;
803
+ continue;
804
+ }
805
+ const fullPath = [BASE_PATH, ...currentPath, prop].join(".");
806
+ const fullAliasPath = [BASE_PATH, ...currentAliasPath, prop].join(".");
807
+ let entity = currentServiceConfig.entity;
808
+ if (entity) {
809
+ const completePath = fullPath.split(".");
810
+ for (let i = 1; i < completePath.length; i++) {
811
+ entity = this.getEntity({ entity, prop: completePath[i] }) ?? entity;
812
+ }
813
+ }
814
+ const relationship = this.getEntityRelationship({
815
+ parentServiceConfig: currentServiceConfig,
816
+ property: prop,
817
+ entity,
818
+ });
819
+ const isCurrentProp = fullPath === BASE_PATH + "." + expand.property ||
820
+ fullAliasPath == BASE_PATH + "." + expand.property;
821
+ let fields = isCurrentProp ? expand.fields ?? [] : [];
822
+ const args = isCurrentProp ? expand.args : [];
823
+ if (relationship) {
824
+ const parentExpand = parsedExpands.get([BASE_PATH, ...currentPath].join(".")) || query;
825
+ if (parentExpand) {
826
+ const parRelField = relationship.inverse
827
+ ? relationship.primaryKey
828
+ : relationship.foreignKey.split(".").pop();
829
+ parentExpand.fields ??= [];
830
+ parentExpand.fields = parentExpand.fields
831
+ .concat(parRelField.split(","))
832
+ .filter((field) => field !== relationship.alias);
833
+ parentExpand.fields = (0, utils_1.deduplicate)(parentExpand.fields);
834
+ const relField = relationship.inverse
835
+ ? relationship.foreignKey.split(".").pop()
836
+ : relationship.primaryKey;
837
+ fields = fields.concat(relField.split(","));
838
+ }
839
+ currentServiceConfig = this.getServiceConfig({
840
+ serviceName: relationship.serviceName,
841
+ entity: relationship.entity,
842
+ });
843
+ if (!currentServiceConfig) {
844
+ throw new Error(`Target service not found: ${relationship.serviceName}`);
845
+ }
846
+ }
847
+ const isAliasMapping = expand.isAliasMapping;
848
+ if (!parsedExpands.has(fullPath)) {
849
+ let parentPath = [BASE_PATH, ...currentPath].join(".");
850
+ if (aliasRealPathMap.has(parentPath)) {
851
+ parentPath = aliasRealPathMap
852
+ .get(parentPath)
853
+ .slice(0, -1)
854
+ .join(".");
855
+ }
856
+ parsedExpands.set(fullPath, {
857
+ property: prop,
858
+ serviceConfig: currentServiceConfig,
859
+ entity: entity,
860
+ fields,
861
+ args: isAliasMapping
862
+ ? forwardArgumentsOnPath.includes(fullPath)
863
+ ? args
864
+ : undefined
865
+ : args,
866
+ isAliasMapping: isAliasMapping,
867
+ parent: parentPath,
868
+ parentConfig: parsedExpands.get(parentPath).serviceConfig,
869
+ });
870
+ }
871
+ else {
872
+ const exp = parsedExpands.get(fullPath);
873
+ if (forwardArgumentsOnPath.includes(fullPath) && args) {
874
+ exp.args = (exp.args || []).concat(args);
875
+ }
876
+ exp.isAliasMapping ??= isAliasMapping;
877
+ if (fields) {
878
+ exp.fields = (0, utils_1.deduplicate)((exp.fields ?? []).concat(fields));
879
+ }
880
+ }
881
+ currentPath.push(prop);
882
+ currentAliasPath.push(prop);
883
+ }
884
+ }
885
+ return { parsedExpands, aliasRealPathMap };
886
+ }
887
+ getEntity({ entity, prop }) {
888
+ return this.entityMap.get(entity)?.get(prop);
889
+ }
890
+ parseAlias({ aliasPath, aliasRealPathMap, expands, expand, property, parsedExpands, currentServiceConfig, currentPath, implodeMapping, forwardArgumentsOnPath, }) {
891
+ const serviceConfig = currentServiceConfig;
892
+ const fieldAlias = currentServiceConfig.fieldAlias ?? {};
893
+ let alias = fieldAlias[property];
894
+ // Handle multiple shortcuts for the same property
895
+ if (Array.isArray(alias)) {
896
+ const currentPathEntity = parsedExpands.get([BASE_PATH, ...currentPath].join("."))?.entity;
897
+ alias = alias.find((a) => a.entity == currentPathEntity);
898
+ if (!alias) {
899
+ throw new Error(`Cannot resolve alias path "${currentPath.join(".")}" that matches entity ${currentPathEntity}.`);
900
+ }
901
+ }
902
+ const path = (0, utils_1.isString)(alias) ? alias : alias.path;
903
+ const fieldAliasIsList = (0, utils_1.isString)(alias) ? false : !!alias.isList;
904
+ const fullPath = [...currentPath.concat(path.split("."))];
905
+ if (aliasRealPathMap.has(aliasPath)) {
906
+ currentPath.push(...path.split("."));
907
+ const fullPath = [BASE_PATH, ...currentPath].join(".");
908
+ return parsedExpands.get(fullPath).serviceConfig;
909
+ }
910
+ const parentPath = [BASE_PATH, ...currentPath].join(".");
911
+ const parentExpands = parsedExpands.get(parentPath);
912
+ parentExpands.fields = parentExpands.fields?.filter((field) => field !== property);
913
+ forwardArgumentsOnPath.push(...(alias?.forwardArgumentsOnPath || []).map((forPath) => BASE_PATH + "." + currentPath.concat(forPath).join(".")));
914
+ const parentFieldAlias = fullPath[Math.max(fullPath.length - 2, 0)];
915
+ implodeMapping.push({
916
+ location: [...currentPath],
917
+ property,
918
+ path: fullPath,
919
+ isList: fieldAliasIsList ||
920
+ !!serviceConfig.relationships?.get(parentFieldAlias)?.isList,
921
+ });
922
+ const extMapping = expands;
923
+ const fullAliasProp = fullPath.join(".");
924
+ const middlePath = path.split(".");
925
+ let curMiddlePath = currentPath;
926
+ for (const path of middlePath) {
927
+ curMiddlePath = curMiddlePath.concat(path);
928
+ const midProp = curMiddlePath.join(".");
929
+ const existingExpand = expands.find((exp) => exp.property === midProp);
930
+ const extraExtends = {
931
+ fields: existingExpand?.fields,
932
+ args: existingExpand?.args,
933
+ ...(midProp === fullAliasProp ? expand : {}),
934
+ property: midProp,
935
+ isAliasMapping: !existingExpand,
936
+ };
937
+ if (forwardArgumentsOnPath.includes(BASE_PATH + "." + midProp)) {
938
+ const forwarded = (existingExpand?.args ?? []).concat(expand?.args ?? []);
939
+ if (forwarded.length) {
940
+ extraExtends.args = forwarded;
941
+ }
942
+ }
943
+ extMapping.push(extraExtends);
944
+ }
945
+ const partialPath = [];
946
+ for (const partial of path.split(".")) {
947
+ const completePath = [
948
+ BASE_PATH,
949
+ ...currentPath.concat(partialPath),
950
+ partial,
951
+ ];
952
+ const parentPath = completePath.slice(0, -1).join(".");
953
+ let entity = serviceConfig.entity;
954
+ if (entity) {
955
+ for (let i = 1; i < completePath.length; i++) {
956
+ entity = this.getEntity({ entity, prop: completePath[i] }) ?? entity;
957
+ }
958
+ }
959
+ const relationship = this.getEntityRelationship({
960
+ parentServiceConfig: currentServiceConfig,
961
+ property: partial,
962
+ entity,
963
+ });
964
+ if (relationship) {
965
+ currentServiceConfig = this.getServiceConfig({
966
+ serviceName: relationship.serviceName,
967
+ entity: relationship.entity,
968
+ });
969
+ if (!currentServiceConfig) {
970
+ throw new Error(`Target service not found: ${relationship.serviceName}`);
971
+ }
972
+ }
973
+ partialPath.push(partial);
974
+ parsedExpands.set(completePath.join("."), {
975
+ property: partial,
976
+ serviceConfig: currentServiceConfig,
977
+ entity: entity,
978
+ parent: parentPath,
979
+ parentConfig: parsedExpands.get(parentPath).serviceConfig,
980
+ });
981
+ }
982
+ currentPath.push(...path.split("."));
983
+ aliasRealPathMap.set(aliasPath, [BASE_PATH, ...currentPath]);
984
+ return currentServiceConfig;
985
+ }
986
+ groupExpands(parsedExpands) {
987
+ const mergedExpands = new Map(parsedExpands);
988
+ const mergedPaths = new Map();
989
+ for (const [path, expand] of mergedExpands.entries()) {
990
+ const currentServiceName = expand.serviceConfig.serviceName;
991
+ let parentPath = expand.parent;
992
+ while (parentPath) {
993
+ const parentExpand = mergedExpands.get(parentPath) ?? mergedPaths.get(parentPath);
994
+ if (!parentExpand ||
995
+ parentExpand.serviceConfig.serviceName !== currentServiceName) {
996
+ break;
997
+ }
998
+ const nestedKeys = path.split(".").slice(parentPath.split(".").length);
999
+ let targetExpand = parentExpand;
1000
+ for (const key of nestedKeys) {
1001
+ targetExpand.expands ??= {};
1002
+ targetExpand = targetExpand.expands[key] ??= {};
1003
+ }
1004
+ const nextFields = [
1005
+ ...new Set([
1006
+ ...(targetExpand.fields ?? []),
1007
+ ...(expand.fields ?? []),
1008
+ ]),
1009
+ ];
1010
+ targetExpand.fields = nextFields;
1011
+ if (expand.args?.length) {
1012
+ const existingArgs = targetExpand.args;
1013
+ targetExpand.args = existingArgs
1014
+ ? existingArgs.concat(expand.args)
1015
+ : expand.args;
1016
+ }
1017
+ mergedExpands.delete(path);
1018
+ mergedPaths.set(path, expand);
1019
+ parentPath = parentExpand.parent;
1020
+ }
1021
+ }
1022
+ return mergedExpands;
1023
+ }
1024
+ createFilterFromInitialData({ initialData, parsedExpands, aliasRealPathMap, }) {
1025
+ if (!initialData.length) {
1026
+ return;
1027
+ }
1028
+ const getPkValues = ({ initialData, serviceConfig, relationship, }) => {
1029
+ if (!initialData.length || !relationship || !serviceConfig) {
1030
+ return {};
1031
+ }
1032
+ const primaryKeys = relationship.primaryKey
1033
+ ? relationship.primaryKey.split(",")
1034
+ : serviceConfig.primaryKeys;
1035
+ const filter = {};
1036
+ // Collect IDs for the current level, considering composed keys
1037
+ primaryKeys.forEach((key) => {
1038
+ filter[key] = Array.from(new Set(initialData.map((dt) => dt[key]).filter(utils_1.isDefined)));
1039
+ });
1040
+ return filter;
1041
+ };
1042
+ const parsedSegment = new Map();
1043
+ const aliasReversePathMap = new Map(Array.from(aliasRealPathMap).map(([path, realPath]) => [
1044
+ realPath.join("."),
1045
+ path,
1046
+ ]));
1047
+ for (let [path, expand] of parsedExpands.entries()) {
1048
+ const serviceConfig = expand.serviceConfig;
1049
+ const relationship = this.getEntityRelationship({
1050
+ parentServiceConfig: expand.parentConfig,
1051
+ property: expand.property,
1052
+ }) ?? serviceConfig.relationships?.get(serviceConfig.serviceName);
1053
+ if (!serviceConfig || !relationship) {
1054
+ continue;
1055
+ }
1056
+ let aliasToPath = null;
1057
+ if (aliasReversePathMap.has(path)) {
1058
+ aliasToPath = path;
1059
+ path = aliasReversePathMap.get(path);
1060
+ }
1061
+ const pathSegments = path.split(".");
1062
+ let relevantInitialData = initialData;
1063
+ let fullPath = [];
1064
+ for (const segment of pathSegments) {
1065
+ fullPath.push(segment);
1066
+ if (segment === BASE_PATH) {
1067
+ continue;
1068
+ }
1069
+ const pathStr = fullPath.join(".");
1070
+ if (parsedSegment.has(pathStr)) {
1071
+ relevantInitialData = parsedSegment.get(pathStr);
1072
+ continue;
1073
+ }
1074
+ relevantInitialData =
1075
+ RemoteJoiner.getNestedItems(relevantInitialData, segment) ?? [];
1076
+ parsedSegment.set(pathStr, relevantInitialData);
1077
+ if (!relevantInitialData.length) {
1078
+ break;
1079
+ }
1080
+ }
1081
+ if (!relevantInitialData.length) {
1082
+ continue;
1083
+ }
1084
+ const queryPath = expand.parent === "" ? BASE_PATH : aliasToPath ?? path;
1085
+ const filter = getPkValues({
1086
+ initialData: relevantInitialData,
1087
+ serviceConfig,
1088
+ relationship,
1089
+ });
1090
+ if (!Object.keys(filter).length) {
1091
+ continue;
1092
+ }
1093
+ const parsed = parsedExpands.get(queryPath);
1094
+ parsed.args ??= [];
1095
+ parsed.args.push({
1096
+ name: "filters",
1097
+ value: filter,
1098
+ });
1099
+ }
1100
+ }
1101
+ mergeInitialData({ items, initialData, serviceConfig, path, expands, relationship, }) {
1102
+ if (!initialData.length || !relationship) {
1103
+ return items;
1104
+ }
1105
+ const primaryKeys = relationship?.primaryKey.split(",") || [
1106
+ serviceConfig.primaryKeys[0],
1107
+ ];
1108
+ const expandKeys = Object.keys(expands ?? {});
1109
+ const initialDataIndexMap = new Map(initialData.map((dt, index) => [
1110
+ primaryKeys.map((key) => dt[key]).join(","),
1111
+ index,
1112
+ ]));
1113
+ const itemMap = new Map(items.map((item) => [primaryKeys.map((key) => item[key]).join(","), item]));
1114
+ const orderedMergedItems = new Array(initialData.length);
1115
+ for (const [key, index] of initialDataIndexMap.entries()) {
1116
+ const iniData = initialData[index];
1117
+ const item = itemMap.get(key);
1118
+ if (!item) {
1119
+ orderedMergedItems[index] = iniData;
1120
+ continue;
1121
+ }
1122
+ // Only merge properties that are not relations
1123
+ const shallowProperty = { ...iniData };
1124
+ for (const key of expandKeys) {
1125
+ const isRel = !!this.getEntityRelationship({
1126
+ parentServiceConfig: serviceConfig,
1127
+ property: key,
1128
+ });
1129
+ if (isRel) {
1130
+ Object.defineProperty(shallowProperty, key, {
1131
+ value: undefined,
1132
+ enumerable: false,
1133
+ });
1134
+ }
1135
+ }
1136
+ Object.assign(item, shallowProperty);
1137
+ orderedMergedItems[index] = item;
1138
+ }
1139
+ if (expands) {
1140
+ for (const expand of expandKeys) {
1141
+ this.mergeInitialData({
1142
+ items: items.flatMap((dt) => dt[expand] ?? []),
1143
+ initialData: initialData
1144
+ .flatMap((dt) => dt[expand] ?? [])
1145
+ .filter(utils_1.isDefined),
1146
+ serviceConfig,
1147
+ path: `${path}.${expand}`,
1148
+ expands: expands[expand]?.expands,
1149
+ relationship: this.getEntityRelationship({
1150
+ parentServiceConfig: serviceConfig,
1151
+ property: expand,
1152
+ }),
1153
+ });
1154
+ }
1155
+ }
1156
+ return orderedMergedItems;
1157
+ }
1158
+ async query(queryObj, options) {
1159
+ const serviceConfig = this.getServiceConfig({
1160
+ serviceName: queryObj.service,
1161
+ serviceAlias: queryObj.alias,
1162
+ });
1163
+ if (!serviceConfig) {
1164
+ if (queryObj.alias) {
1165
+ throw new Error(`Service with alias "${queryObj.alias}" was not found.`);
1166
+ }
1167
+ throw new Error(`Service "${queryObj.service}" was not found.`);
1168
+ }
1169
+ const iniDataArray = options?.initialData
1170
+ ? Array.isArray(options.initialData)
1171
+ ? options.initialData
1172
+ : [options.initialData]
1173
+ : [];
1174
+ const implodeMapping = [];
1175
+ const parseExpandsConfig = {
1176
+ initialService: {
1177
+ property: "",
1178
+ parent: "",
1179
+ serviceConfig,
1180
+ entity: serviceConfig.entity,
1181
+ fields: queryObj.fields,
1182
+ },
1183
+ query: queryObj,
1184
+ serviceConfig,
1185
+ expands: queryObj.expands,
1186
+ implodeMapping,
1187
+ options,
1188
+ initialData: iniDataArray,
1189
+ initialDataOnly: options?.initialDataOnly,
1190
+ };
1191
+ const parsedExpands = this.parseExpands(parseExpandsConfig);
1192
+ const root = parsedExpands.get(BASE_PATH);
1193
+ const { primaryKeyArg, otherArgs, pkName } = gerPrimaryKeysAndOtherFilters({
1194
+ serviceConfig,
1195
+ queryObj,
1196
+ });
1197
+ if (otherArgs) {
1198
+ parseExpandsConfig.initialService.args = otherArgs;
1199
+ }
1200
+ if (options?.throwIfKeyNotFound) {
1201
+ if (primaryKeyArg?.value == undefined) {
1202
+ if (!primaryKeyArg) {
1203
+ throw new utils_1.AcmeKitError(utils_1.AcmeKitError.Types.NOT_FOUND, `${serviceConfig.entity ?? serviceConfig.serviceName}: Primary key(s) [${serviceConfig.primaryKeys.join(", ")}] not found in filters`);
1204
+ }
1205
+ throw new utils_1.AcmeKitError(utils_1.AcmeKitError.Types.NOT_FOUND, `${serviceConfig.entity ?? serviceConfig.serviceName}: Value for primary key ${primaryKeyArg.name} not found in filters`);
1206
+ }
1207
+ }
1208
+ const response = await this.fetchData({
1209
+ expand: root,
1210
+ pkField: pkName,
1211
+ ids: primaryKeyArg?.value,
1212
+ options,
1213
+ });
1214
+ let data = response.path ? response.data[response.path] : response.data;
1215
+ const isDataArray = Array.isArray(data);
1216
+ data = isDataArray ? data : [data];
1217
+ if (options?.initialData) {
1218
+ data = this.mergeInitialData({
1219
+ items: data,
1220
+ initialData: iniDataArray,
1221
+ serviceConfig,
1222
+ path: BASE_PATH,
1223
+ expands: parsedExpands.get(BASE_PATH)?.expands,
1224
+ relationship: serviceConfig.relationships?.get(serviceConfig.serviceName),
1225
+ });
1226
+ delete options?.initialData;
1227
+ }
1228
+ await this.handleExpands({
1229
+ items: data,
1230
+ parsedExpands,
1231
+ implodeMapping,
1232
+ options,
1233
+ });
1234
+ const retData = isDataArray ? data : data[0];
1235
+ if (response.path) {
1236
+ response.data[response.path] = retData;
1237
+ }
1238
+ else {
1239
+ response.data = retData;
1240
+ }
1241
+ return response.data;
1242
+ }
1243
+ }
1244
+ exports.RemoteJoiner = RemoteJoiner;
1245
+ function gerPrimaryKeysAndOtherFilters({ serviceConfig, queryObj }) {
1246
+ let pkName = serviceConfig.primaryKeys[0];
1247
+ let primaryKeyArg = queryObj.args?.find((arg) => {
1248
+ const include = serviceConfig.primaryKeys.includes(arg.name);
1249
+ if (include) {
1250
+ pkName = arg.name;
1251
+ }
1252
+ return include;
1253
+ });
1254
+ let otherArgs = queryObj.args?.filter((arg) => !serviceConfig.primaryKeys.includes(arg.name));
1255
+ if (!primaryKeyArg) {
1256
+ const filters = queryObj.args?.find((arg) => arg.name === "filters")?.value ?? {};
1257
+ const primaryKeyFilter = Object.keys(filters).find((key) => {
1258
+ return serviceConfig.primaryKeys.includes(key);
1259
+ });
1260
+ if (primaryKeyFilter) {
1261
+ pkName = primaryKeyFilter;
1262
+ primaryKeyArg = {
1263
+ name: primaryKeyFilter,
1264
+ value: filters[primaryKeyFilter],
1265
+ };
1266
+ Object.defineProperty(filters, primaryKeyFilter, {
1267
+ value: undefined,
1268
+ enumerable: false,
1269
+ });
1270
+ }
1271
+ }
1272
+ otherArgs = otherArgs?.length ? otherArgs : undefined;
1273
+ return {
1274
+ primaryKeyArg,
1275
+ otherArgs,
1276
+ pkName,
1277
+ };
1278
+ }
1279
+ //# sourceMappingURL=remote-joiner.js.map