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