Skip to content

Commit b3b6d1e

Browse files
committed
add feat
1 parent dfffc14 commit b3b6d1e

File tree

2 files changed

+244
-5
lines changed

2 files changed

+244
-5
lines changed

spec/ParseLiveQuery.spec.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,4 +1308,32 @@ describe('ParseLiveQuery', function () {
13081308
await new Promise(resolve => setTimeout(resolve, 100));
13091309
expect(createSpy).toHaveBeenCalledTimes(1);
13101310
});
1311+
1312+
it_id('a1b7fa01-877e-46e2-9601-d312ebb9b33a')(fit)('handles query include', async done => {
1313+
await reconfigureServer({
1314+
liveQuery: {
1315+
classNames: ['Queue'],
1316+
},
1317+
startLiveQueryServer: true,
1318+
verbose: false,
1319+
silent: true,
1320+
});
1321+
1322+
const user = new Parse.User();
1323+
user.setUsername('testuser');
1324+
user.setPassword('password');
1325+
await user.signUp();
1326+
1327+
const query = new Parse.Query('Queue');
1328+
query.include('user');
1329+
const subscription = await query.subscribe();
1330+
subscription.on('create', obj => {
1331+
expect(obj.get('user').get('username')).toBe('testuser');
1332+
done();
1333+
});
1334+
1335+
const queue = new Parse.Object('Queue');
1336+
queue.set('user', user);
1337+
await queue.save();
1338+
});
13111339
});

src/LiveQuery/ParseLiveQueryServer.ts

Lines changed: 216 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import UserRouter from '../Routers/UsersRouter';
2525
import DatabaseController from '../Controllers/DatabaseController';
2626
import { isDeepStrictEqual } from 'util';
2727
import deepcopy from 'deepcopy';
28+
import RestQuery from '../RestQuery';
29+
import { master as masterAuth } from '../Auth';
2830

2931
class ParseLiveQueryServer {
3032
server: any;
@@ -241,6 +243,7 @@ class ParseLiveQueryServer {
241243
}
242244
if (res.object && typeof res.object.toJSON === 'function') {
243245
deletedParseObject = toJSONwithObjects(res.object, res.object.className || className);
246+
deletedParseObject = await this._applyInclude(client, requestId, deletedParseObject);
244247
}
245248
await this._filterSensitiveData(
246249
classLevelPermissions,
@@ -393,12 +396,11 @@ class ParseLiveQueryServer {
393396
}
394397
if (res.object && typeof res.object.toJSON === 'function') {
395398
currentParseObject = toJSONwithObjects(res.object, res.object.className || className);
399+
currentParseObject = await this._applyInclude(client, requestId, currentParseObject);
396400
}
397401
if (res.original && typeof res.original.toJSON === 'function') {
398-
originalParseObject = toJSONwithObjects(
399-
res.original,
400-
res.original.className || className
401-
);
402+
originalParseObject = toJSONwithObjects(res.original, res.original.className || className);
403+
originalParseObject = await this._applyInclude(client, requestId, originalParseObject);
402404
}
403405
await this._filterSensitiveData(
404406
classLevelPermissions,
@@ -553,7 +555,7 @@ class ParseLiveQueryServer {
553555
}
554556
}
555557

556-
getAuthForSessionToken(sessionToken?: string): Promise<{ auth?: Auth, userId?: string }> {
558+
getAuthForSessionToken(sessionToken?: string): Promise<{ auth?: Auth; userId?: string }> {
557559
if (!sessionToken) {
558560
return Promise.resolve({});
559561
}
@@ -674,6 +676,24 @@ class ParseLiveQueryServer {
674676
res.original = filter(res.original);
675677
}
676678

679+
async _applyInclude(client: any, requestId: number, object: any) {
680+
const subscriptionInfo = client.getSubscriptionInfo(requestId);
681+
if (!object || !subscriptionInfo) {
682+
return object;
683+
}
684+
const include = subscriptionInfo.include;
685+
if (!include || include.length === 0) {
686+
return object;
687+
}
688+
const restOptions: any = {};
689+
if (subscriptionInfo.keys) {
690+
restOptions.keys = Array.isArray(subscriptionInfo.keys)
691+
? subscriptionInfo.keys.join(',')
692+
: subscriptionInfo.keys;
693+
}
694+
return includeObject(this.config, object, include, {}, restOptions, masterAuth(this.config));
695+
}
696+
677697
_getCLPOperation(query: any) {
678698
return typeof query === 'object' &&
679699
Object.keys(query).length == 1 &&
@@ -933,6 +953,11 @@ class ParseLiveQueryServer {
933953
? request.query.keys
934954
: request.query.keys.split(',');
935955
}
956+
if (request.query.include) {
957+
subscriptionInfo.include = Array.isArray(request.query.include)
958+
? request.query.include
959+
: request.query.include.split(',');
960+
}
936961
if (request.query.watch) {
937962
subscriptionInfo.watch = request.query.watch;
938963
}
@@ -1056,6 +1081,192 @@ class ParseLiveQueryServer {
10561081
`Delete client: ${parseWebsocket.clientId} | subscription: ${request.requestId}`
10571082
);
10581083
}
1084+
1085+
async includePath(
1086+
config: any,
1087+
auth: any,
1088+
response: any,
1089+
path: Array<string>,
1090+
context: any,
1091+
restOptions: any = {},
1092+
) {
1093+
const pointers = this.findPointers(response.results, path);
1094+
if (pointers.length === 0) {
1095+
return response;
1096+
}
1097+
const pointersHash: any = {};
1098+
for (const pointer of pointers) {
1099+
if (!pointer) {
1100+
continue;
1101+
}
1102+
const className = pointer.className;
1103+
if (className) {
1104+
pointersHash[className] = pointersHash[className] || new Set();
1105+
pointersHash[className].add(pointer.objectId);
1106+
}
1107+
}
1108+
const includeRestOptions: any = {};
1109+
if (restOptions.keys) {
1110+
const keys = new Set(restOptions.keys.split(','));
1111+
const keySet = Array.from(keys).reduce((set, key) => {
1112+
const keyPath = key.split('.');
1113+
let i = 0;
1114+
for (; i < path.length; i++) {
1115+
if (path[i] != keyPath[i]) {
1116+
return set;
1117+
}
1118+
}
1119+
if (i < keyPath.length) {
1120+
set.add(keyPath[i]);
1121+
}
1122+
return set;
1123+
}, new Set<string>());
1124+
if (keySet.size > 0) {
1125+
includeRestOptions.keys = Array.from(keySet).join(',');
1126+
}
1127+
}
1128+
1129+
if (restOptions.excludeKeys) {
1130+
const excludeKeys = new Set(restOptions.excludeKeys.split(','));
1131+
const excludeKeySet = Array.from(excludeKeys).reduce((set, key) => {
1132+
const keyPath = key.split('.');
1133+
let i = 0;
1134+
for (; i < path.length; i++) {
1135+
if (path[i] != keyPath[i]) {
1136+
return set;
1137+
}
1138+
}
1139+
if (i == keyPath.length - 1) {
1140+
set.add(keyPath[i]);
1141+
}
1142+
return set;
1143+
}, new Set<string>());
1144+
if (excludeKeySet.size > 0) {
1145+
includeRestOptions.excludeKeys = Array.from(excludeKeySet).join(',');
1146+
}
1147+
}
1148+
1149+
if (restOptions.includeReadPreference) {
1150+
includeRestOptions.readPreference = restOptions.includeReadPreference;
1151+
includeRestOptions.includeReadPreference = restOptions.includeReadPreference;
1152+
} else if (restOptions.readPreference) {
1153+
includeRestOptions.readPreference = restOptions.readPreference;
1154+
}
1155+
1156+
const queryPromises = Object.keys(pointersHash).map(async className => {
1157+
const objectIds = Array.from(pointersHash[className]);
1158+
let where;
1159+
if (objectIds.length === 1) {
1160+
where = { objectId: objectIds[0] };
1161+
} else {
1162+
where = { objectId: { $in: objectIds } };
1163+
}
1164+
const query = await RestQuery({
1165+
method: objectIds.length === 1 ? RestQuery.Method.get : RestQuery.Method.find,
1166+
config,
1167+
auth,
1168+
className,
1169+
restWhere: where,
1170+
restOptions: includeRestOptions,
1171+
context: context,
1172+
});
1173+
return query.execute({ op: 'get' }).then(results => {
1174+
results.className = className;
1175+
return Promise.resolve(results);
1176+
});
1177+
});
1178+
1179+
const responses = await Promise.all(queryPromises);
1180+
const replace = responses.reduce((acc, includeResponse) => {
1181+
for (const obj of includeResponse.results) {
1182+
obj.__type = 'Object';
1183+
obj.className = includeResponse.className;
1184+
if (obj.className === '_User' && !auth.isMaster) {
1185+
delete obj.sessionToken;
1186+
delete obj.authData;
1187+
}
1188+
acc[obj.objectId] = obj;
1189+
}
1190+
return acc;
1191+
}, {} as any);
1192+
1193+
const resp: any = {
1194+
results: this.replacePointers(response.results, path, replace),
1195+
};
1196+
if (response.count) {
1197+
resp.count = response.count;
1198+
}
1199+
return resp;
1200+
}
1201+
1202+
findPointers(object: any, path: Array<string>): any[] {
1203+
if (object instanceof Array) {
1204+
return object.map(x => this.findPointers(x, path)).flat();
1205+
}
1206+
if (typeof object !== 'object' || !object) {
1207+
return [];
1208+
}
1209+
if (path.length === 0) {
1210+
if (object === null || object.__type === 'Pointer') {
1211+
return [object];
1212+
}
1213+
return [];
1214+
}
1215+
const subObject = object[path[0]];
1216+
if (!subObject) {
1217+
return [];
1218+
}
1219+
return this.findPointers(subObject, path.slice(1));
1220+
}
1221+
1222+
replacePointers(object: any, path: Array<string>, replace: any): any {
1223+
if (object instanceof Array) {
1224+
return object
1225+
.map(obj => this.replacePointers(obj, path, replace))
1226+
.filter(obj => typeof obj !== 'undefined');
1227+
}
1228+
if (typeof object !== 'object' || !object) {
1229+
return object;
1230+
}
1231+
if (path.length === 0) {
1232+
if (object && object.__type === 'Pointer') {
1233+
return replace[object.objectId];
1234+
}
1235+
return object;
1236+
}
1237+
const subObject = object[path[0]];
1238+
if (!subObject) {
1239+
return object;
1240+
}
1241+
const newSub = this.replacePointers(subObject, path.slice(1), replace);
1242+
const answer: any = {};
1243+
for (const key in object) {
1244+
if (key === path[0]) {
1245+
answer[key] = newSub;
1246+
} else {
1247+
answer[key] = object[key];
1248+
}
1249+
}
1250+
return answer;
1251+
}
1252+
1253+
async includeObject(
1254+
config: any,
1255+
object: any,
1256+
include: Array<string>,
1257+
context: any,
1258+
restOptions: any,
1259+
auth: any
1260+
) {
1261+
if (!include || include.length === 0) {
1262+
return object;
1263+
}
1264+
let response = { results: [object] } as any;
1265+
for (const path of include) {
1266+
response = await this.includePath(config, auth, response, path.split('.'), context, restOptions);
1267+
}
1268+
return response.results[0];
1269+
}
10591270
}
10601271

10611272
export { ParseLiveQueryServer };

0 commit comments

Comments
 (0)