Skip to content
This repository was archived by the owner on Dec 27, 2024. It is now read-only.

Commit 4e8634d

Browse files
committed
merge v0.20.1
2 parents 282c005 + 7ce303d commit 4e8634d

5 files changed

Lines changed: 139 additions & 28 deletions

File tree

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sakuraapi/core",
3-
"version": "0.20.0",
3+
"version": "0.20.1",
44
"description": "MongoDB and TypeScript MEAN Stack Framework for NodeJS",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",

src/core/@model/model-operators/to-db.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ describe('Model.toDb', () => {
239239
];
240240

241241
const result = parent.toDb();
242-
243242
expect(ObjectID.isValid(result._id)).toBeTruthy();
244243
expect(ObjectID.isValid(result.children[0]._id)).toBeTruthy();
245244
expect(ObjectID.isValid(result.children[1]._id)).toBeTruthy();

src/core/@model/model-operators/to-json.spec.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,111 @@ describe('Model.toJson', () => {
958958
expect(json.subDoc.field2).toBeUndefined();
959959
});
960960
});
961+
962+
describe('bugs', () => {
963+
describe('issue #249', () => {
964+
965+
@Model()
966+
class Test249 extends SapiModelMixin() {
967+
968+
@Id() @Json({type: 'id'})
969+
id: ObjectID = new ObjectID();
970+
971+
@Db() @Json({model: TestSubDoc})
972+
children: TestSubDoc[] = [new TestSubDoc()];
973+
974+
}
975+
976+
it('sub document includes all fields by default if parent field is projected', () => {
977+
const model = new Test249();
978+
979+
const json = model.toJson({
980+
projection: {
981+
children: 1
982+
}
983+
});
984+
985+
expect(json.id).toBeUndefined();
986+
expect(json.children).toBeDefined();
987+
expect(json.children.length).toBe(1);
988+
expect(json.children[0].subA).toBe('a');
989+
990+
});
991+
992+
it('sub document projections are independent of parent projections', () => {
993+
const model = new Test249();
994+
995+
const json = model.toJson({
996+
projection: {
997+
children: {
998+
subA: 1
999+
},
1000+
id: 0
1001+
}
1002+
});
1003+
1004+
expect(json.id).toBeUndefined();
1005+
expect(json.children).toBeDefined();
1006+
expect(json.children.length).toBe(1);
1007+
expect(json.children[0].subA).toBe('a');
1008+
expect(json.children[0].subB).toBeUndefined();
1009+
expect(json.children[0].field2).toBeUndefined();
1010+
1011+
});
1012+
1013+
it('sub document can be further projected (exclude)', () => {
1014+
const model = new Test249();
1015+
1016+
const json = model.toJson({
1017+
projection: {
1018+
children: {
1019+
subA: 0
1020+
}
1021+
}
1022+
});
1023+
1024+
expect(json.id).toBeDefined();
1025+
expect(json.children).toBeDefined();
1026+
expect(json.children.length).toBe(1);
1027+
expect(json.children[0].subA).toBeUndefined();
1028+
expect(json.children[0].subB).toBe('b');
1029+
expect(json.children[0].field2).toBe(2);
1030+
1031+
});
1032+
1033+
it('inclusion of sub document with projection of 1 rather than {}', () => {
1034+
const model = new Test249();
1035+
1036+
const json = model.toJson({
1037+
projection: {
1038+
children: 1,
1039+
id: 1
1040+
}
1041+
});
1042+
1043+
expect(json.id).toBeDefined();
1044+
expect(json.children).toBeDefined();
1045+
expect(json.children.length).toBe(1);
1046+
expect(json.children[0].subA).toBe('a');
1047+
expect(json.children[0].subB).toBe('b');
1048+
expect(json.children[0].field2).toBe(2);
1049+
});
1050+
1051+
it('inclusion of sub document with projection of 0 rather than {}', () => {
1052+
const model = new Test249();
1053+
1054+
const json = model.toJson({
1055+
projection: {
1056+
children: 0
1057+
}
1058+
});
1059+
1060+
expect(json.id).toBeDefined();
1061+
expect(json.children).toBeUndefined();
1062+
1063+
});
1064+
});
1065+
});
9611066
});
9621067

9631068
describe('array of sub documents', () => {

src/core/@model/model-operators/to-json.ts

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { dbSymbols, IDbOptions } from '../db';
33
import { IJsonOptions, jsonSymbols } from '../json';
44
import { modelSymbols } from '../model';
55
import { privateSymbols } from '../private';
6+
import { SapiModelMixin } from '../sapi-model-mixin';
67
import { formatToJsonSymbols, ToJsonHandler } from '../to-json';
78
import { debug } from './index';
89

@@ -62,42 +63,43 @@ function isInclusiveProjection(projection: IProjection) {
6263
if (projection) {
6364
const keys = Object.keys(projection);
6465
for (const key of keys) {
65-
if (projection[key] === true || projection[key] > 0) {
66+
if (!Array.isArray(projection[key]) && typeof projection[key] === 'object') {
67+
continue;
68+
} else if (projection[key] === true || projection[key] > 0) {
6669
return true;
6770
}
6871
}
6972
return false;
7073
}
71-
7274
return true;
7375
}
7476

7577
function keyMapper(key, value, options: IJsonOptions, optionsStar: IJsonOptions) {
7678
return options.field || optionsStar.field || key;
7779
}
7880

79-
function mapModelToJson(ctx: IContext, source, projection: IProjection) {
81+
function mapModelToJson(ctx: IContext, model: InstanceType<ReturnType<typeof SapiModelMixin>>, projection: IProjection) {
8082

8183
let jsonObj = {};
8284

83-
if (!source) {
84-
return source;
85+
if (!model) {
86+
return model;
8587
}
8688

87-
const dbOptionsByPropertyName: Map<string, IDbOptions> = Reflect.getMetadata(dbSymbols.dbByPropertyName, source);
89+
const dbOptionsByPropertyName: Map<string, IDbOptions> = Reflect.getMetadata(dbSymbols.dbByPropertyName, model);
8890

8991
const privateFields: Map<string, boolean> = Reflect
90-
.getMetadata(privateSymbols.sakuraApiPrivatePropertyToFieldNames, source) || new Map<string, boolean>();
92+
.getMetadata(privateSymbols.sakuraApiPrivatePropertyToFieldNames, model) || new Map<string, boolean>();
9193

9294
const inclusiveProjection = isInclusiveProjection(projection);
9395

9496
// iterate over each property
95-
const keys = Object.keys(source);
97+
const keys = Object.keys(model);
9698
for (const key of keys) {
9799

98100
const dbOptions = (dbOptionsByPropertyName) ? dbOptionsByPropertyName.get(key) || {} : {};
99-
const jsonOptions = getJsonOptions(source, key, ctx);
100-
const jsonOptionsStar = getJsonOptions(source, key, '*');
101+
const jsonOptions = getJsonOptions(model, key, ctx);
102+
const jsonOptionsStar = getJsonOptions(model, key, '*');
101103

102104
if ((ctx.context !== jsonOptions.context) && (jsonOptionsStar.context !== '*')) {
103105
continue;
@@ -118,73 +120,73 @@ function mapModelToJson(ctx: IContext, source, projection: IProjection) {
118120
continue;
119121
}
120122

121-
const model = dbOptions.model || jsonOptions.model || jsonOptionsStar.model || null;
122-
const newKey = keyMapper(key, source[key], jsonOptions, jsonOptionsStar);
123+
const subModel = dbOptions.model || jsonOptions.model || jsonOptionsStar.model || null;
124+
const newKey = keyMapper(key, model[key], jsonOptions, jsonOptionsStar);
123125

124126
if (skipProjection(inclusiveProjection, projection, newKey)) {
125127
continue;
126128
}
127129

128130
let value;
129-
if (model || shouldRecurse(source[key])) {
131+
if (subModel || shouldRecurse(model[key])) {
130132

131133
const subProjection = (projection)
132134
? (typeof projection[newKey] === 'object') ? projection[newKey] as IProjection : undefined
133135
: undefined;
134136

135-
if (Array.isArray(source[key])) {
137+
if (Array.isArray(model[key])) {
136138

137139
const values = [];
138-
for (const src of source[key]) {
140+
for (const src of model[key]) {
139141
values.push(mapModelToJson(ctx, src, subProjection));
140142
}
141143
value = values;
142144

143145
} else if (newKey !== undefined) {
144146

145-
value = mapModelToJson(ctx, source[key], subProjection);
147+
value = mapModelToJson(ctx, model[key], subProjection);
146148

147149
}
148150

149151
} else if (newKey !== undefined) {
150152

151-
value = source[key];
153+
value = model[key];
152154

153155
}
154156

155157
// check for @json({toJson})
156158
if (jsonOptions.toJson) {
157-
value = jsonOptions.toJson.call(source, value, key, ctx);
159+
value = jsonOptions.toJson.call(model, value, key, ctx);
158160
}
159161
if (jsonOptionsStar.toJson) {
160-
value = jsonOptionsStar.toJson.call(source, value, key, ctx);
162+
value = jsonOptionsStar.toJson.call(model, value, key, ctx);
161163
}
162164

163165
// check for @json({encrypt})
164166
if (jsonOptions.encrypt) {
165167
value = (jsonOptions.encryptor)
166-
? jsonOptions.encryptor.call(source, value, key, jsonOptions.key, ctx)
167-
: encrypt(value, jsonOptions.key || source[modelSymbols.cipherKey]);
168+
? jsonOptions.encryptor.call(model, value, key, jsonOptions.key, ctx)
169+
: encrypt(value, jsonOptions.key || model[modelSymbols.cipherKey]);
168170
}
169171
if (jsonOptionsStar.encrypt) {
170172
value = (jsonOptionsStar.encryptor)
171-
? jsonOptionsStar.encryptor.call(source, value, key, jsonOptions.key, ctx)
172-
: encrypt(value, jsonOptionsStar.key || source[modelSymbols.cipherKey]);
173+
? jsonOptionsStar.encryptor.call(model, value, key, jsonOptions.key, ctx)
174+
: encrypt(value, jsonOptionsStar.key || model[modelSymbols.cipherKey]);
173175
}
174176

175177
jsonObj[newKey] = value;
176178

177179
}
178180

179181
// @ToJson
180-
const formatToJson = Reflect.getMetadata(formatToJsonSymbols.functionMap, source);
182+
const formatToJson = Reflect.getMetadata(formatToJsonSymbols.functionMap, model);
181183
if (formatToJson) {
182184
const formatters: ToJsonHandler[] = [
183185
...formatToJson.get(ctx.context) || [],
184186
...formatToJson.get('*') || []
185187
];
186188
for (const formatter of formatters) {
187-
jsonObj = formatter.call(source, jsonObj, source, ctx);
189+
jsonObj = formatter.call(model, jsonObj, model, ctx);
188190
}
189191
}
190192

@@ -196,6 +198,11 @@ function skipProjection(isInclusive: boolean, projection: IProjection, newKey: s
196198
return false;
197199
}
198200

201+
// is projection of sub-document
202+
if (!Array.isArray(projection[newKey]) && typeof projection[newKey] === 'object') {
203+
return false;
204+
}
205+
199206
return (isInclusive)
200207
? projection[newKey] == null || projection[newKey] === false || projection[newKey] < 1
201208
: projection[newKey] === false || projection[newKey] < 1;

0 commit comments

Comments
 (0)