Skip to content

Commit a7333c4

Browse files
authored
Merge branch 'alpha' into fix/parse-community#2098
2 parents 3b6684d + 8f30edf commit a7333c4

34 files changed

+537
-170
lines changed

.github/workflows/ci.yml

+19-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ jobs:
3434
- run: npm ci
3535
- name: Check Docs
3636
run: npm run docs
37+
check-lint:
38+
name: Lint
39+
timeout-minutes: 15
40+
runs-on: ubuntu-latest
41+
steps:
42+
- uses: actions/checkout@v4
43+
- name: Use Node.js
44+
uses: actions/setup-node@v4
45+
with:
46+
cache: npm
47+
- name: Install dependencies
48+
run: npm ci
49+
- name: Lint
50+
run: npm run lint
3751
build:
3852
runs-on: ubuntu-latest
3953
timeout-minutes: 30
@@ -55,12 +69,15 @@ jobs:
5569
node-version: ${{ matrix.NODE_VERSION }}
5670
cache: npm
5771
- run: npm ci
58-
- run: npm run lint
5972
- run: npm test -- --maxWorkers=4
6073
- run: npm run test:mongodb
6174
env:
6275
CI: true
63-
- run: bash <(curl -s https://codecov.io/bash)
76+
- name: Upload code coverage
77+
uses: codecov/codecov-action@v4
78+
with:
79+
fail_ci_if_error: true
80+
token: ${{ secrets.CODECOV_TOKEN }}
6481
concurrency:
6582
group: ${{ github.workflow }}-${{ github.ref }}
6683
cancel-in-progress: true

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ A library that gives you access to the powerful Parse Server backend from your J
2828

2929
- [Getting Started](#getting-started)
3030
- [Using Parse on Different Platforms](#using-parse-on-different-platforms)
31+
- [Core Manager](#core-manager)
3132
- [Compatibility](#compatibility)
3233
- [Parse Server](#parse-server)
3334
- [Node.js](#nodejs)
@@ -89,6 +90,18 @@ $ npm install @types/parse
8990

9091
Types are updated manually after every release. If a definition doesn't exist, please submit a pull request to [@types/parse][types-parse]
9192

93+
#### Core Manager
94+
95+
The SDK has a [Core Manager](src/CoreManager.js) that handles all configurations and controllers. These modules can be swapped out for customization before you initialize the SDK. For full list of all available modules take a look at the [Core Manager Documentation](src/CoreManager.js).
96+
97+
```js
98+
// Configuration example
99+
Parse.CoreManager.set('REQUEST_ATTEMPT_LIMIT', 1)
100+
101+
// Controller example
102+
Parse.CoreManager.setRESTController(MyRESTController);
103+
```
104+
92105
## Compatibility
93106

94107
### Parse Server

changelogs/CHANGELOG_alpha.md

+35
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,38 @@
1+
# [5.1.0-alpha.6](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.5...5.1.0-alpha.6) (2024-04-25)
2+
3+
4+
### Features
5+
6+
* Allow setting custom queue for handling offline operations via `Parse.EventuallyQueue` ([#2106](https://github.com/parse-community/Parse-SDK-JS/issues/2106)) ([f92e4d4](https://github.com/parse-community/Parse-SDK-JS/commit/f92e4d42afdc1e55bcfff1ba9d0658d39943f3f0))
7+
8+
# [5.1.0-alpha.5](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.4...5.1.0-alpha.5) (2024-04-22)
9+
10+
11+
### Bug Fixes
12+
13+
* Chrome browser console warning about unsafe header `access-control-expose-headers` when calling Cloud Function ([#2095](https://github.com/parse-community/Parse-SDK-JS/issues/2095)) ([7b73c03](https://github.com/parse-community/Parse-SDK-JS/commit/7b73c033eef8977c3e6c7e4af7146ffa74deed0c))
14+
15+
# [5.1.0-alpha.4](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.3...5.1.0-alpha.4) (2024-04-15)
16+
17+
18+
### Bug Fixes
19+
20+
* Live Query not working on Expo React Native ([#2109](https://github.com/parse-community/Parse-SDK-JS/issues/2109)) ([7a89665](https://github.com/parse-community/Parse-SDK-JS/commit/7a8966522f06efb3f0303b2a3c6fd08f41d8aff9))
21+
22+
# [5.1.0-alpha.3](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.2...5.1.0-alpha.3) (2024-04-14)
23+
24+
25+
### Features
26+
27+
* Lazy load `Parse.CoreManager` controllers to add support for swappable `CryptoController`, `LocalDatastoreController`, `StorageController`, `WebSocketController`, `ParseLiveQuery` ([#2100](https://github.com/parse-community/Parse-SDK-JS/issues/2100)) ([fbd0ab1](https://github.com/parse-community/Parse-SDK-JS/commit/fbd0ab1402792e241c4d9d6496b451e4cc268b8b))
28+
29+
# [5.1.0-alpha.2](https://github.com/parse-community/Parse-SDK-JS/compare/5.1.0-alpha.1...5.1.0-alpha.2) (2024-04-13)
30+
31+
32+
### Bug Fixes
33+
34+
* Local datastore throws error when `Parse.Query.notEqualTo` is set to `null` ([#2102](https://github.com/parse-community/Parse-SDK-JS/issues/2102)) ([6afd32a](https://github.com/parse-community/Parse-SDK-JS/commit/6afd32af3517c88b570505d5cb25bd5ab449f039))
35+
136
# [5.1.0-alpha.1](https://github.com/parse-community/Parse-SDK-JS/compare/5.0.0...5.1.0-alpha.1) (2024-03-31)
237

338

integration/test/ParseEventuallyQueueTest.js

+5-28
Original file line numberDiff line numberDiff line change
@@ -195,22 +195,15 @@ describe('Parse EventuallyQueue', () => {
195195
const object = new TestObject({ hash: 'saveSecret' });
196196
await new Promise((resolve) => parseServer.server.close(resolve));
197197
await object.saveEventually();
198-
let length = await Parse.EventuallyQueue.length();
198+
199+
const length = await Parse.EventuallyQueue.length();
199200
assert(Parse.EventuallyQueue.isPolling());
200201
assert.strictEqual(length, 1);
201202

202203
await reconfigureServer({});
203204
while (Parse.EventuallyQueue.isPolling()) {
204205
await sleep(100);
205206
}
206-
assert.strictEqual(Parse.EventuallyQueue.isPolling(), false);
207-
208-
while (await Parse.EventuallyQueue.length()) {
209-
await sleep(100);
210-
}
211-
length = await Parse.EventuallyQueue.length();
212-
assert.strictEqual(length, 0);
213-
214207
const query = new Parse.Query(TestObject);
215208
query.equalTo('hash', 'saveSecret');
216209
let results = await query.find();
@@ -233,10 +226,9 @@ describe('Parse EventuallyQueue', () => {
233226
object.setACL(acl);
234227

235228
await new Promise((resolve) => parseServer.server.close(resolve));
236-
237229
await object.saveEventually();
238230

239-
let length = await Parse.EventuallyQueue.length();
231+
const length = await Parse.EventuallyQueue.length();
240232
assert(Parse.EventuallyQueue.isPolling());
241233
assert.strictEqual(length, 1);
242234

@@ -245,15 +237,6 @@ describe('Parse EventuallyQueue', () => {
245237
while (Parse.EventuallyQueue.isPolling()) {
246238
await sleep(100);
247239
}
248-
assert.strictEqual(Parse.EventuallyQueue.isPolling(), false);
249-
250-
length = await Parse.EventuallyQueue.length();
251-
while (length) {
252-
await sleep(100);
253-
}
254-
length = await Parse.EventuallyQueue.length();
255-
assert.strictEqual(length, 0);
256-
257240
const query = new Parse.Query('TestObject');
258241
query.equalTo('hash', 'saveSecret');
259242
let results = await query.find();
@@ -269,21 +252,15 @@ describe('Parse EventuallyQueue', () => {
269252
await object.save();
270253
await new Promise((resolve) => parseServer.server.close(resolve));
271254
await object.destroyEventually();
272-
let length = await Parse.EventuallyQueue.length();
255+
const length = await Parse.EventuallyQueue.length();
256+
273257
assert(Parse.EventuallyQueue.isPolling());
274258
assert.strictEqual(length, 1);
275259

276260
await reconfigureServer({});
277261
while (Parse.EventuallyQueue.isPolling()) {
278262
await sleep(100);
279263
}
280-
assert.strictEqual(Parse.EventuallyQueue.isPolling(), false);
281-
while (await Parse.EventuallyQueue.length()) {
282-
await sleep(100);
283-
}
284-
length = await Parse.EventuallyQueue.length();
285-
assert.strictEqual(length, 0);
286-
287264
const query = new Parse.Query(TestObject);
288265
query.equalTo('hash', 'deleteSecret');
289266
let results = await query.find();

integration/test/ParseLiveQueryTest.js

+38
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const assert = require('assert');
44
const Parse = require('../../node');
55
const sleep = require('./sleep');
66
const { resolvingPromise } = require('../../lib/node/promiseUtils');
7+
const { EventEmitter } = require('events');
78

89
describe('Parse LiveQuery', () => {
910
beforeEach(() => {
@@ -367,4 +368,41 @@ describe('Parse LiveQuery', () => {
367368
client.state = 'closed';
368369
await client.close();
369370
});
371+
372+
it('can subscribe to query with EventEmitter private fields', async () => {
373+
class CustomEmitter {
374+
#privateEmitter;
375+
376+
constructor() {
377+
this.#privateEmitter = new EventEmitter();
378+
}
379+
on(event, listener) {
380+
this.#privateEmitter.on(event, listener);
381+
}
382+
emit(event, ...args) {
383+
this.#privateEmitter.emit(event, ...args);
384+
}
385+
}
386+
387+
const EV = Parse.CoreManager.getEventEmitter();
388+
389+
Parse.CoreManager.setEventEmitter(CustomEmitter);
390+
const object = new TestObject();
391+
await object.save();
392+
const installationId = await Parse.CoreManager.getInstallationController().currentInstallationId();
393+
394+
const query = new Parse.Query(TestObject);
395+
query.equalTo('objectId', object.id);
396+
const subscription = await query.subscribe();
397+
const promise = resolvingPromise();
398+
subscription.on('update', (object, original, response) => {
399+
assert.equal(object.get('foo'), 'bar');
400+
assert.equal(response.installationId, installationId);
401+
promise.resolve();
402+
});
403+
object.set({ foo: 'bar' });
404+
await object.save();
405+
await promise;
406+
Parse.CoreManager.setEventEmitter(EV);
407+
});
370408
});

integration/test/ParseLocalDatastoreTest.js

+10
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,16 @@ function runTest(controller) {
13761376
assert.equal(results.length, 9);
13771377
});
13781378

1379+
it(`${controller.name} can perform notEqualTo null queries`, async () => {
1380+
const nullObject = new Parse.Object({ className: 'BoxedNumber', number: null });
1381+
await nullObject.save();
1382+
const query = new Parse.Query('BoxedNumber');
1383+
query.notEqualTo('number', null);
1384+
query.fromLocalDatastore();
1385+
const results = await query.find();
1386+
assert.equal(results.length, 10);
1387+
});
1388+
13791389
it(`${controller.name} can perform containedIn queries`, async () => {
13801390
const query = new Parse.Query('BoxedNumber');
13811391
query.containedIn('number', [3, 5, 7, 9, 11]);
+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use strict';
2+
3+
const Parse = require('../../react-native');
4+
const { resolvingPromise } = require('../../lib/react-native/promiseUtils');
5+
const CryptoController = require('../../lib/react-native/CryptoController');
6+
const LocalDatastoreController = require('../../lib/react-native/LocalDatastoreController.default');
7+
const StorageController = require('../../lib/react-native/StorageController.default');
8+
const RESTController = require('../../lib/react-native/RESTController');
9+
10+
RESTController._setXHR(require('xmlhttprequest').XMLHttpRequest);
11+
12+
describe('Parse React Native', () => {
13+
beforeEach(() => {
14+
// Set up missing controllers and configurations
15+
Parse.CoreManager.setWebSocketController(require('ws'));
16+
Parse.CoreManager.setEventEmitter(require('events').EventEmitter);
17+
Parse.CoreManager.setLocalDatastoreController(LocalDatastoreController);
18+
Parse.CoreManager.setStorageController(StorageController);
19+
Parse.CoreManager.setRESTController(RESTController);
20+
Parse.CoreManager.setCryptoController(CryptoController);
21+
22+
Parse.initialize('integration');
23+
Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse');
24+
Parse.CoreManager.set('MASTER_KEY', 'notsosecret');
25+
Parse.enableLocalDatastore();
26+
});
27+
28+
afterEach(async () => {
29+
await Parse.User.logOut();
30+
Parse.Storage._clear();
31+
});
32+
33+
it('can log in a user', async () => {
34+
// Handle Storage Controller
35+
await Parse.User.signUp('asdf', 'zxcv')
36+
const user = await Parse.User.logIn('asdf', 'zxcv');
37+
expect(user.get('username')).toBe('asdf');
38+
expect(user.existed()).toBe(true);
39+
});
40+
41+
it('can encrypt user', async () => {
42+
// Handle Crypto Controller
43+
Parse.User.enableUnsafeCurrentUser();
44+
Parse.enableEncryptedUser();
45+
Parse.secret = 'My Secret Key';
46+
const user = new Parse.User();
47+
user.setUsername('usernameENC');
48+
user.setPassword('passwordENC');
49+
await user.signUp();
50+
51+
const path = Parse.Storage.generatePath('currentUser');
52+
const encryptedUser = Parse.Storage.getItem(path);
53+
54+
const crypto = Parse.CoreManager.getCryptoController();
55+
56+
const decryptedUser = crypto.decrypt(encryptedUser, Parse.CoreManager.get('ENCRYPTED_KEY'));
57+
expect(JSON.parse(decryptedUser).objectId).toBe(user.id);
58+
59+
const currentUser = Parse.User.current();
60+
expect(currentUser).toEqual(user);
61+
62+
const currentUserAsync = await Parse.User.currentAsync();
63+
expect(currentUserAsync).toEqual(user);
64+
await Parse.User.logOut();
65+
Parse.CoreManager.set('ENCRYPTED_USER', false);
66+
Parse.CoreManager.set('ENCRYPTED_KEY', null);
67+
});
68+
69+
it('can pin saved object LDS', async () => {
70+
// Handle LocalDatastore Controller
71+
function LDS_KEY(object) {
72+
return Parse.LocalDatastore.getKeyForObject(object);
73+
}
74+
const object = new Parse.Object('TestObject');
75+
object.set('field', 'test');
76+
await object.save();
77+
await object.pin();
78+
const localDatastore = await Parse.LocalDatastore._getAllContents();
79+
const cachedObject = localDatastore[LDS_KEY(object)][0];
80+
expect(Object.keys(localDatastore).length).toBe(2);
81+
expect(cachedObject.objectId).toBe(object.id);
82+
expect(cachedObject.field).toBe('test');
83+
});
84+
85+
it('can subscribe to query', async () => {
86+
// Handle WebSocket Controller
87+
const object = new Parse.Object('TestObject');
88+
await object.save();
89+
const installationId = await Parse.CoreManager.getInstallationController().currentInstallationId();
90+
91+
const query = new Parse.Query('TestObject');
92+
query.equalTo('objectId', object.id);
93+
const subscription = await query.subscribe();
94+
const promise = resolvingPromise();
95+
subscription.on('update', (object, _, response) => {
96+
expect(object.get('foo')).toBe('bar');
97+
expect(response.installationId).toBe(installationId);
98+
promise.resolve();
99+
});
100+
object.set({ foo: 'bar' });
101+
await object.save();
102+
await promise;
103+
});
104+
});

integration/test/helper.js

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const defaultConfiguration = {
8484
revokeSessionOnPasswordReset: false,
8585
allowCustomObjectId: false,
8686
allowClientClassCreation: true,
87+
encodeParseObjectInCloudFunction: true,
8788
emailAdapter: MockEmailAdapterWithOptions({
8889
fromAddress: '[email protected]',
8990
apiKey: 'k',

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse",
3-
"version": "5.1.0-alpha.1",
3+
"version": "5.1.0-alpha.6",
44
"description": "Parse JavaScript SDK",
55
"homepage": "https://parseplatform.org",
66
"keywords": [

0 commit comments

Comments
 (0)