@aeriajs/core 0.0.139 → 0.0.140

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.
@@ -57,7 +57,7 @@ const recurseProperty = async (_property, propertyName, description) => {
57
57
  return typeof index === 'string';
58
58
  });
59
59
  if (!indexes) {
60
- throw new Error(`neither indexes or inline are present on reference property or indexes is set on target description on ${description.$id}.${propertyName}`);
60
+ reference.indexes = Object.keys(referenceDescription.properties).slice(0, 1);
61
61
  }
62
62
  }
63
63
  }
@@ -32,7 +32,7 @@ const recurseProperty = async (_property, propertyName, description) => {
32
32
  return typeof index === "string";
33
33
  });
34
34
  if (!indexes) {
35
- throw new Error(`neither indexes or inline are present on reference property or indexes is set on target description on ${description.$id}.${propertyName}`);
35
+ reference.indexes = Object.keys(referenceDescription.properties).slice(0, 1);
36
36
  }
37
37
  }
38
38
  }
@@ -6,20 +6,25 @@ export type GetReferenceOptions = {
6
6
  };
7
7
  export type Reference = {
8
8
  isArray?: boolean;
9
+ isArrayElement?: boolean;
9
10
  isInline?: boolean;
11
+ isRecursive?: boolean;
10
12
  deepReferences?: ReferenceMap;
11
13
  referencedCollection?: string;
12
14
  populatedProperties?: string[];
13
15
  };
14
16
  export type ReferenceMap = Record<string, Reference | undefined>;
15
- export type BuildLookupOptions = {
16
- properties: FixedObjectProperty['properties'];
17
- parent?: string;
18
- depth?: number;
19
- maxDepth?: number;
17
+ export type PipelineStage = {};
18
+ export type BuildLookupPipelineOptions = {
19
+ rootPipeline?: PipelineStage[];
20
+ path?: string[];
21
+ tempNames?: string[];
20
22
  memoize?: string;
21
23
  project?: string[];
22
24
  };
23
25
  export declare const getReferences: (properties: FixedObjectProperty["properties"], options?: GetReferenceOptions) => Promise<{}>;
24
- export declare const buildLookupPipeline: (referenceMap: ReferenceMap | {}, options: BuildLookupOptions) => Promise<any[]>;
25
- export declare const getLookupPipeline: (description: Description, _options?: Omit<BuildLookupOptions, "properties">) => Promise<any[]>;
26
+ export declare const recurseSetStage: (reference: Reference, path: string[], parentElem: {}, options?: {
27
+ noCond: boolean;
28
+ }) => PipelineStage;
29
+ export declare const buildLookupPipeline: (refMap: ReferenceMap, options?: BuildLookupPipelineOptions) => PipelineStage[];
30
+ export declare const getLookupPipeline: (description: Description, options: BuildLookupPipelineOptions) => Promise<PipelineStage[]>;
@@ -1,79 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getLookupPipeline = exports.buildLookupPipeline = exports.getReferences = void 0;
3
+ exports.getLookupPipeline = exports.buildLookupPipeline = exports.recurseSetStage = exports.getReferences = void 0;
4
4
  const common_1 = require("@aeriajs/common");
5
5
  const assets_js_1 = require("../assets.js");
6
6
  const database_js_1 = require("../database.js");
7
+ const getTempName = (path) => {
8
+ return `_${path.join('_')}`;
9
+ };
7
10
  const referenceMemo = {};
8
11
  const lookupMemo = {};
9
- const narrowLookupPipelineProjection = (pipeline, projection) => {
10
- const hasAny = (propName) => {
11
- return propName.includes('.') || projection.includes(propName);
12
- };
13
- return pipeline.filter((stage) => {
14
- if (stage.$lookup) {
15
- return hasAny(stage.$lookup.as);
16
- }
17
- if (stage.$unwind) {
18
- return hasAny(stage.$unwind.path.slice(1));
19
- }
20
- return true;
21
- });
22
- };
23
- const buildGroupStages = (referenceMap, properties) => {
24
- const $group = Object.keys(properties).reduce((a, propName) => {
25
- const refMap = referenceMap[propName] || {};
26
- const groupType = !refMap.referencedCollection && refMap.isArray
27
- ? 'push'
28
- : 'first';
29
- return {
30
- ...a,
31
- [propName]: {
32
- [`$${groupType}`]: `$${propName}`,
33
- },
34
- };
35
- }, {
36
- _id: '$_id',
37
- });
38
- return {
39
- $group,
40
- };
41
- };
42
- const buildArrayCleanupStages = (referenceMap) => {
43
- const $set = Object.entries(referenceMap).reduce((a, [refName, refMap]) => {
44
- if (!refMap.isArray || refMap.referencedCollection) {
45
- return a;
46
- }
47
- return {
48
- ...a,
49
- [refName]: {
50
- $filter: {
51
- input: `$${refName}`,
52
- as: `${refName}_elem`,
53
- cond: {
54
- $ne: [
55
- `$$${refName}_elem`,
56
- {},
57
- ],
58
- },
59
- },
60
- },
61
- };
62
- }, {});
63
- return Object.keys($set).length > 0
64
- ? {
65
- $set,
66
- }
67
- : null;
68
- };
69
- const getReferences = async (properties, options) => {
70
- const { depth = 0, maxDepth = 3, memoize, } = options || {};
12
+ const getReferences = async (properties, options = {}) => {
13
+ const { depth = 0, maxDepth = 3, memoize, } = options;
71
14
  if (memoize) {
72
15
  if (referenceMemo[memoize]) {
73
16
  return referenceMemo[memoize];
74
17
  }
75
18
  }
76
- const references = {};
19
+ const refMap = {};
77
20
  for (const [propName, property] of Object.entries(properties)) {
78
21
  const refProperty = (0, common_1.getReferenceProperty)(property);
79
22
  const reference = {};
@@ -120,6 +63,9 @@ const getReferences = async (properties, options) => {
120
63
  if ('items' in property) {
121
64
  reference.isArray = true;
122
65
  }
66
+ if (depth > 0) {
67
+ reference.isRecursive = true;
68
+ }
123
69
  if (refProperty) {
124
70
  if (refProperty.$ref) {
125
71
  reference.referencedCollection = refProperty.$ref;
@@ -128,185 +74,262 @@ const getReferences = async (properties, options) => {
128
74
  reference.isInline = true;
129
75
  }
130
76
  }
131
- references[propName] = reference;
77
+ refMap[propName] = reference;
132
78
  }
133
79
  if (memoize) {
134
- referenceMemo[memoize] = references;
80
+ referenceMemo[memoize] = refMap;
135
81
  }
136
- return references;
82
+ return refMap;
137
83
  };
138
84
  exports.getReferences = getReferences;
139
- const buildLookupStages = async (reference, propName, options) => {
140
- const { parent, properties, depth = 0, maxDepth = 3, } = options;
141
- const stages = [];
142
- let refHasDeepReferences = false;
143
- const withParent = (propName) => {
144
- return parent
145
- ? `${parent}.${propName}`
146
- : propName;
147
- };
148
- if (reference.referencedCollection) {
149
- if (!reference.populatedProperties) {
150
- stages.push({
151
- $lookup: {
152
- from: (0, database_js_1.prepareCollectionName)(reference.referencedCollection),
153
- foreignField: '_id',
154
- localField: withParent(propName),
155
- as: withParent(propName),
156
- },
157
- });
158
- }
159
- else {
160
- const subPipeline = [];
161
- if (reference.deepReferences) {
162
- const subProperties = (0, common_1.throwIfError)(await (0, assets_js_1.getCollectionAsset)(reference.referencedCollection, 'description')).properties;
163
- subPipeline.push(...await (0, exports.buildLookupPipeline)(reference.deepReferences, {
164
- project: reference.populatedProperties,
165
- properties: subProperties,
166
- }));
167
- }
168
- if (reference.populatedProperties.length > 0) {
169
- subPipeline.push({
170
- $project: Object.fromEntries(reference.populatedProperties.map((index) => [
171
- index,
172
- 1,
173
- ])),
174
- });
175
- }
176
- stages.push({
177
- $lookup: {
178
- from: (0, database_js_1.prepareCollectionName)(reference.referencedCollection),
179
- let: {
180
- 'ids': !reference.isArray
181
- ? `$${withParent(propName)}`
182
- : {
183
- $ifNull: [
184
- `$${withParent(propName)}`,
185
- [],
186
- ],
187
- },
188
- },
189
- as: withParent(propName),
190
- pipeline: [
85
+ const recurseSetStage = (reference, path, parentElem, options = {
86
+ noCond: false,
87
+ }) => {
88
+ const refName = path.at(-1);
89
+ let indexOfArray;
90
+ if (reference.isRecursive && !reference.isArrayElement) {
91
+ indexOfArray = {
92
+ $indexOfArray: [
93
+ `$${getTempName(path)}._id`,
94
+ {
95
+ $arrayElemAt: [
96
+ `$${getTempName(path.slice(0, -1))}.${refName}`,
191
97
  {
192
- $match: {
193
- $expr: {
194
- [reference.isArray
195
- ? '$in'
196
- : '$eq']: [
197
- '$_id',
198
- '$$ids',
199
- ],
200
- },
201
- },
98
+ $indexOfArray: [
99
+ `$${getTempName(path.slice(0, -1))}._id`,
100
+ parentElem,
101
+ ],
202
102
  },
203
- ...subPipeline,
204
103
  ],
205
104
  },
206
- });
105
+ ],
106
+ };
107
+ }
108
+ else {
109
+ indexOfArray = {
110
+ $indexOfArray: [
111
+ `$${getTempName(path)}._id`,
112
+ parentElem,
113
+ ],
114
+ };
115
+ }
116
+ if (reference.isArray) {
117
+ const newElemName = `${refName}__elem`;
118
+ let mapIn;
119
+ if (reference.referencedCollection) {
120
+ mapIn = (0, exports.recurseSetStage)({
121
+ ...reference,
122
+ isArray: false,
123
+ isArrayElement: true,
124
+ }, path, `$$${newElemName}`);
207
125
  }
208
- if (!reference.isArray) {
209
- stages.push({
210
- $unwind: {
211
- path: `$${withParent(propName)}`,
212
- preserveNullAndEmptyArrays: true,
213
- },
214
- });
126
+ else {
127
+ mapIn = {
128
+ $mergeObjects: [
129
+ `$$${newElemName}`,
130
+ (0, exports.recurseSetStage)({
131
+ ...reference,
132
+ isArray: false,
133
+ isArrayElement: true,
134
+ }, path, `$$${newElemName}`),
135
+ ],
136
+ };
215
137
  }
216
- }
217
- else if (reference.deepReferences && depth <= maxDepth) {
218
- refHasDeepReferences = true;
219
- if (reference.isArray) {
220
- stages.push({
221
- $unwind: {
222
- path: `$${withParent(propName)}`,
223
- preserveNullAndEmptyArrays: true,
224
- },
225
- });
138
+ let mapInput = parentElem;
139
+ if (reference.isRecursive) {
140
+ mapInput = {
141
+ $arrayElemAt: [
142
+ `$${getTempName(path.slice(0, -1))}.${refName}`,
143
+ indexOfArray,
144
+ ],
145
+ };
226
146
  }
227
- for (const [refName, refMap] of Object.entries(reference.deepReferences)) {
228
- if (!refMap) {
229
- continue;
230
- }
231
- if (refMap.referencedCollection) {
232
- const description = (0, common_1.throwIfError)(await (0, assets_js_1.getCollectionAsset)(refMap.referencedCollection, 'description'));
233
- const { stages: result } = await buildLookupStages(refMap, refName, {
234
- depth: depth + 1,
235
- parent: withParent(propName),
236
- properties: description.properties,
237
- });
238
- stages.push(...result);
147
+ return {
148
+ $filter: {
149
+ input: {
150
+ $map: {
151
+ input: mapInput,
152
+ as: newElemName,
153
+ in: mapIn,
154
+ },
155
+ },
156
+ as: 'elem',
157
+ cond: {
158
+ $ne: [
159
+ '$$elem',
160
+ null,
161
+ ],
162
+ },
163
+ },
164
+ };
165
+ }
166
+ if (reference.deepReferences) {
167
+ const stages = [];
168
+ for (const [subRefName, subReference] of Object.entries(reference.deepReferences)) {
169
+ if (!subReference) {
239
170
  continue;
240
171
  }
241
- const refProperties = properties[propName];
242
- if ('properties' in refProperties) {
243
- const { stages: refStages } = await buildLookupStages(refMap, refName, {
244
- depth: depth + 1,
245
- parent: withParent(propName),
246
- properties: refProperties.properties,
247
- });
248
- stages.push(...refStages);
249
- }
250
- else if ('items' in refProperties) {
251
- if (!('properties' in refProperties.items)) {
252
- throw new Error();
253
- }
254
- const { stages: refStages } = await buildLookupStages(refMap, refName, {
255
- depth: depth + 1,
256
- parent: withParent(propName),
257
- properties: refProperties.items.properties,
258
- });
259
- stages.push(...refStages);
172
+ let newElem;
173
+ if (reference.isRecursive) {
174
+ newElem = {
175
+ $arrayElemAt: [
176
+ `$${getTempName(path.slice(0, -1))}.${refName}`,
177
+ indexOfArray,
178
+ ],
179
+ };
260
180
  }
261
181
  else {
262
- throw new Error();
182
+ newElem = reference.referencedCollection
183
+ ? parentElem
184
+ : `${parentElem}.${subRefName}`;
263
185
  }
186
+ const result = (0, exports.recurseSetStage)(subReference, path.concat(subRefName), newElem);
187
+ stages.push([
188
+ subRefName,
189
+ result,
190
+ ]);
264
191
  }
192
+ if (reference.referencedCollection) {
193
+ return {
194
+ $cond: [
195
+ {
196
+ $ne: [
197
+ indexOfArray,
198
+ -1,
199
+ ],
200
+ },
201
+ {
202
+ $mergeObjects: [
203
+ (0, exports.recurseSetStage)({
204
+ ...reference,
205
+ deepReferences: undefined,
206
+ }, path, parentElem, {
207
+ noCond: true,
208
+ }),
209
+ Object.fromEntries(stages),
210
+ ],
211
+ },
212
+ null,
213
+ ],
214
+ };
215
+ }
216
+ return Object.fromEntries(stages);
217
+ }
218
+ const arrayElemAt = {
219
+ $arrayElemAt: [
220
+ `$${getTempName(path)}`,
221
+ indexOfArray,
222
+ ],
223
+ };
224
+ if (options.noCond) {
225
+ return arrayElemAt;
265
226
  }
266
227
  return {
267
- stages,
268
- refHasDeepReferences,
228
+ $cond: [
229
+ {
230
+ $ne: [
231
+ indexOfArray,
232
+ -1,
233
+ ],
234
+ },
235
+ arrayElemAt,
236
+ null,
237
+ ],
269
238
  };
270
239
  };
271
- const buildLookupPipeline = async (referenceMap, options) => {
272
- const { properties, memoize: memoizeId, project = [], } = options;
273
- const memoize = `${memoizeId}-${project.sort().join('-')}`;
274
- if (memoizeId && lookupMemo[memoize]) {
275
- const result = lookupMemo[memoize];
276
- return project.length > 0
277
- ? narrowLookupPipelineProjection(result, project)
278
- : result;
240
+ exports.recurseSetStage = recurseSetStage;
241
+ const buildLookupPipeline = (refMap, options = {}) => {
242
+ const { rootPipeline = [], path = [], tempNames = [], project, memoize: memoizeId, } = options;
243
+ const memoize = project
244
+ ? `${memoizeId}-${project.sort().join('-')}`
245
+ : memoizeId;
246
+ if (memoize) {
247
+ if (lookupMemo[memoize]) {
248
+ return lookupMemo[memoize];
249
+ }
279
250
  }
280
- let hasDeepReferences = false;
281
251
  const pipeline = [];
282
- for (const [propName, reference] of Object.entries(referenceMap)) {
252
+ const setProperties = [];
253
+ for (const [refName, reference] of Object.entries(refMap)) {
283
254
  if (!reference) {
284
255
  continue;
285
256
  }
286
- const { stages, refHasDeepReferences, } = await buildLookupStages(reference, propName, options);
287
- hasDeepReferences = hasDeepReferences || refHasDeepReferences;
288
- pipeline.push(...stages);
289
- }
290
- if (hasDeepReferences) {
291
- pipeline.push(buildGroupStages(referenceMap, properties));
292
- const arrayCleanupStages = buildArrayCleanupStages(referenceMap);
293
- if (arrayCleanupStages) {
294
- pipeline.push(arrayCleanupStages);
257
+ if (project) {
258
+ if (!project.includes(refName)) {
259
+ continue;
260
+ }
261
+ }
262
+ if (reference.deepReferences) {
263
+ (0, exports.buildLookupPipeline)(reference.deepReferences, {
264
+ rootPipeline,
265
+ tempNames,
266
+ path: path.concat(refName),
267
+ });
268
+ const result = (0, exports.recurseSetStage)(reference, path.concat(refName), `$${refName}`);
269
+ setProperties.push([
270
+ refName,
271
+ result,
272
+ ]);
273
+ }
274
+ if (reference.referencedCollection) {
275
+ const tempName = getTempName(path.concat(refName));
276
+ const lookupPipeline = [];
277
+ tempNames.unshift(tempName);
278
+ if (reference.populatedProperties && reference.populatedProperties.length > 0) {
279
+ const lookupPopulate = reference.populatedProperties;
280
+ if (reference.deepReferences) {
281
+ lookupPopulate.push(...Object.keys(reference.deepReferences));
282
+ }
283
+ lookupPipeline.push({
284
+ $project: Object.fromEntries(reference.populatedProperties.map((index) => [
285
+ index,
286
+ 1,
287
+ ])),
288
+ });
289
+ }
290
+ const localField = reference.isRecursive
291
+ ? `${getTempName(path)}.${refName}`
292
+ : path.concat(refName).join('.');
293
+ rootPipeline.unshift({
294
+ $lookup: {
295
+ from: (0, database_js_1.prepareCollectionName)(reference.referencedCollection),
296
+ foreignField: '_id',
297
+ localField,
298
+ as: tempName,
299
+ pipeline: lookupPipeline,
300
+ },
301
+ });
302
+ if (!reference.deepReferences) {
303
+ const result = (0, exports.recurseSetStage)(reference, path.concat(refName), `$${refName}`);
304
+ setProperties.push([
305
+ refName,
306
+ result,
307
+ ]);
308
+ }
295
309
  }
296
310
  }
297
- if (memoizeId) {
298
- lookupMemo[memoize] = pipeline;
311
+ if (path.length === 0) {
312
+ if (setProperties.length > 0) {
313
+ pipeline.push({
314
+ $set: Object.fromEntries(setProperties),
315
+ });
316
+ }
317
+ if (tempNames.length > 0) {
318
+ pipeline.push({
319
+ $unset: tempNames,
320
+ });
321
+ }
322
+ const finalPipeline = rootPipeline.concat(pipeline);
323
+ if (memoize) {
324
+ lookupMemo[memoize] = finalPipeline;
325
+ }
326
+ return finalPipeline;
299
327
  }
300
- return project.length > 0
301
- ? narrowLookupPipelineProjection(pipeline, project)
302
- : pipeline;
328
+ return pipeline;
303
329
  };
304
330
  exports.buildLookupPipeline = buildLookupPipeline;
305
- const getLookupPipeline = (description, _options) => {
306
- const options = Object.assign(_options || {}, {
307
- properties: description.properties,
308
- });
309
- const references = (0, exports.getReferences)(description.properties);
310
- return (0, exports.buildLookupPipeline)(references, options);
331
+ const getLookupPipeline = async (description, options) => {
332
+ const refMap = await (0, exports.getReferences)(description.properties);
333
+ return (0, exports.buildLookupPipeline)(refMap, options);
311
334
  };
312
335
  exports.getLookupPipeline = getLookupPipeline;