diff --git a/integration/test/ParseLiveQueryTest.js b/integration/test/ParseLiveQueryTest.js index dd7d607df..c33f21a00 100644 --- a/integration/test/ParseLiveQueryTest.js +++ b/integration/test/ParseLiveQueryTest.js @@ -76,7 +76,7 @@ describe('Parse LiveQuery', () => { query.equalTo('objectId', object.id); const subscription = await client.subscribe(query); const promise = resolvingPromise(); - subscription.on('update', async (object) => { + subscription.on('update', async object => { assert.equal(object.get('foo'), 'bar'); await client.close(); promise.resolve(); @@ -206,7 +206,7 @@ describe('Parse LiveQuery', () => { subscription.on('update', async object => { assert.equal(object.get('foo'), 'bar'); await Parse.User.logOut(); - promise.resolve() + promise.resolve(); }); await object.save({ foo: 'bar' }); await promise; @@ -276,6 +276,44 @@ describe('Parse LiveQuery', () => { await promise; }); + it('can subscribe to query with watch', async () => { + const query = new Parse.Query(TestObject); + query.watch('yolo'); + const subscription = await query.subscribe(); + const spy = { + create(obj) { + if (!obj.get('yolo')) { + fail('create should not have been called'); + } + }, + update(object, original) { + if (object.get('yolo') === original.get('yolo')) { + fail('create should not have been called'); + } + }, + }; + const createSpy = spyOn(spy, 'create').and.callThrough(); + const updateSpy = spyOn(spy, 'update').and.callThrough(); + subscription.on('create', spy.create); + subscription.on('update', spy.update); + const obj = new TestObject(); + obj.set('foo', 'bar'); + await obj.save(); + obj.set('foo', 'xyz'); + obj.set('yolo', 'xyz'); + await obj.save(); + const obj2 = new TestObject(); + obj2.set('foo', 'bar'); + obj2.set('yolo', 'bar'); + await obj2.save(); + obj2.set('foo', 'bart'); + await obj2.save(); + await sleep(1000); + await subscription.unsubscribe(); + expect(createSpy).toHaveBeenCalledTimes(1); + expect(updateSpy).toHaveBeenCalledTimes(1); + }); + it('live query can handle beforeConnect and beforeSubscribe errors', async () => { await reconfigureServer({ cloud({ Cloud }) { diff --git a/src/LiveQueryClient.js b/src/LiveQueryClient.js index 893ba365e..6baf67fb5 100644 --- a/src/LiveQueryClient.js +++ b/src/LiveQueryClient.js @@ -191,7 +191,8 @@ class LiveQueryClient extends EventEmitter { const className = query.className; const queryJSON = query.toJSON(); const where = queryJSON.where; - const fields = queryJSON.keys ? queryJSON.keys.split(',') : undefined; + const fields = queryJSON.keys?.split(','); + const watch = queryJSON.watch?.split(','); const subscribeRequest = { op: OP_TYPES.SUBSCRIBE, requestId: this.requestId, @@ -199,6 +200,7 @@ class LiveQueryClient extends EventEmitter { className, where, fields, + watch, }, }; diff --git a/src/ParseQuery.js b/src/ParseQuery.js index 7c7ce8bc7..f2e18b4d0 100644 --- a/src/ParseQuery.js +++ b/src/ParseQuery.js @@ -22,6 +22,7 @@ export type WhereClause = { export type QueryJSON = { where: WhereClause, + watch?: string, include?: string, excludeKeys?: string, keys?: string, @@ -224,6 +225,7 @@ class ParseQuery { */ className: string; _where: any; + _watch: Array; _include: Array; _exclude: Array; _select: Array; @@ -265,6 +267,7 @@ class ParseQuery { } this._where = {}; + this._watch = []; this._include = []; this._exclude = []; this._count = false; @@ -426,6 +429,9 @@ class ParseQuery { where: this._where, }; + if (this._watch.length) { + params.watch = this._watch.join(','); + } if (this._include.length) { params.include = this._include.join(','); } @@ -495,6 +501,10 @@ class ParseQuery { this._where = json.where; } + if (json.watch) { + this._watch = json.watch.split(','); + } + if (json.include) { this._include = json.include.split(','); } @@ -1921,6 +1931,25 @@ class ParseQuery { return this; } + /** + * Restricts live query to trigger only for watched fields. + * + * Requires Parse Server 6.0.0+ + * + * @param {...string|Array} keys The name(s) of the key(s) to watch. + * @returns {Parse.Query} Returns the query, so you can chain this call. + */ + watch(...keys: Array>): ParseQuery { + keys.forEach(key => { + if (Array.isArray(key)) { + this._watch = this._watch.concat(key); + } else { + this._watch.push(key); + } + }); + return this; + } + /** * Changes the read preference that the backend will use when performing the query to the database. * diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js index 622f9bb51..4499b27fa 100644 --- a/src/__tests__/ParseQuery-test.js +++ b/src/__tests__/ParseQuery-test.js @@ -1078,6 +1078,32 @@ describe('ParseQuery', () => { expect(q2._exclude).toEqual(['foo', 'bar']); }); + it('can watch keys', () => { + const q = new ParseQuery('Item'); + q.watch('foo'); + const json = q.toJSON(); + expect(json).toEqual({ + where: {}, + watch: 'foo', + }); + const q2 = new ParseQuery('Item'); + q2.withJSON(json); + expect(q2._watch).toEqual(['foo']); + }); + + it('can watch multiple keys', () => { + const q = new ParseQuery('Item'); + q.watch(['foo', 'bar']); + const json = q.toJSON(); + expect(json).toEqual({ + where: {}, + watch: 'foo,bar', + }); + const q2 = new ParseQuery('Item'); + q2.withJSON(json); + expect(q2._watch).toEqual(['foo', 'bar']); + }); + it('can use extraOptions', () => { const q = new ParseQuery('Item'); q._extraOptions.randomOption = 'test'; @@ -2334,8 +2360,7 @@ describe('ParseQuery', () => { const q = new ParseQuery('Thing'); let testObject; - q - .find() + q.find() .then(results => { testObject = results[0]; @@ -2460,8 +2485,7 @@ describe('ParseQuery', () => { const q = new ParseQuery('Thing'); let testObject; - q - .first() + q.first() .then(result => { testObject = result; @@ -2875,8 +2899,7 @@ describe('ParseQuery', () => { const q = new ParseQuery('Thing'); q.select('other', 'tbd', 'subObject.key1'); let testObject; - q - .find() + q.find() .then(results => { testObject = results[0]; @@ -2926,8 +2949,7 @@ describe('ParseQuery', () => { const q = new ParseQuery('Thing'); let testObject; - q - .find() + q.find() .then(results => { testObject = results[0];