diff --git a/CHANGELOG.md b/CHANGELOG.md index 783f8dbf9..2c0c6afe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,244 +1,117 @@ -# [4.0.0-rc.43] - 2019-07-23 - -- Fixing an async issue when loading info from offline storage - -# [4.0.0-rc.35] - 2019-07-01 - -- Don't resubscribe records that are unsubscribing on a reconnect - -# [4.0.0-rc.31] - 2019-06-27 - -- Adding an autoVersion option for indexdb -- Fixing a reconnect issue when starting client when server is down - -# [4.0.0-rc.30] - 2019-06-13 - -- Only transition the state to unsubscribing if its the last reference being removed - -# [4.0.0-rc.29] - 2019-06-13 - -- Adding the concept of context to emitter, used to fixed a bug in record where discard doesn't -delete the local emitter bindings, but can hopefully be extended elsewhere - -# [4.0.0-rc.28] - 2019-06-10 - -- Emitter is no longer a dependency and instead is owned by us, which makes typescript happier - -# [4.0.0-rc.27] - 2019-06-06 - -- Users can now delete the offline store via the record handler. This is useful for situations such -as ensuring nothing is on disk on logout. - -# [4.0.0-rc.25] - 2019-06-03 - -- Merging should transition a record to ready when first initialized -- Changing state machine to log an error instead of throwing one to stop things breaking too badly - -# [4.0.0-rc.22] - 2019-05-28 - -- Delete collections from indexdb if no longer in objectStoreList -- Fixing a race condition where ready is triggered without setting the data first -- Adding ability to ignore saving records in indexdb -- Adding the ability to save each update as it comes from the server. -- Save records to storage before they are discarded -- Changing npm to use deepstream org - -# [4.0.0-rc.12] - 2019-05-17 - -- Firing the change event on records before transitioning results in the state machine not -working as expected when doing something like our initial API usage in 2.0 days - -``` -const record = ds.record.get(name) -record.subscribe(() => { - // do something - record.discard() - // oh no! we never entered the ready state =( -}) -``` - -- Seems like URL gets injected into node somehow, which means we don't want to reference window.URL -- Removing an async state transition to avoid issues when CPU intensive apps are started -- Removing an invalid error message in merge conflicts -- Removing node-localstorage as a dependency, as its node only and doesn't play well with angular - -# [4.0.0-rc.3] - 2019-05-16 - -This release adds indexDB. However its not in a stable state. - -# [4.0.0-rc.3] - 2019-05-05 - -This release includes bulk subscriptions, which allow thousands of records to be subscribed to with a single message, -providing a huge performance boost for application startup time and data on the wire - -# [4.0.0-rc.1] - 2019-04-20 - -This release of the client includes a full rewrite in TypeScript and a move away from the old text-based protocol to the Universal Realtime Protocol (URP). This means the client is not compatible with [deepstream.io](https://github.com/deepstreamio/deepstream.io) prior to version v4.0.0. - -## Features - -### Additional events - -- **clientDataChanged**: emitted every time the server sends new the client new data after authenticating - -```javascript -client.on('clientDataChanged', clientData => { ... }) -``` - -- **reauthenticationFailure**: emitted when a client is unable to authenticate with the server after a reconnection. This will only be called for automatic reconnection attempts during connection loss or similar, if calling the `login` function authentication is as normal - -```javascript -client.on('reAuthenticationFailure', reason => { ... }) -``` - -## Improvements - -### Promisified API's - -- Presence -```javascript -client.presence.getAll() - .then(users => {}) - .catch(error => {}) - -client.presence.getAll(users) - .then(users => {}) - .catch(error => {}) -``` - -- RPC -```javascript -client.rpc.make(name, data) - .then(data => {}) - .catch(error => {}) -``` - -- Record Factory - -```javascript -client.record.snapshot(recordName) - .then(data => {}) - .catch(error => {}) - -client.record.has(recordName) - .then(exists => {}) - .catch(error => {}) - -client.record.head(recordName) - .then(version => {}) - .catch(error => {}) -``` - -- Record - -```javascript -record.whenReady() - .then(() => {}) -record.delete() - .then(() => {}) - .catch(error => {}) +# [4.0.0] - 2019-07-30 + +### Features: + +- New binary protocol support (under the hood) +- Bulk actions support (under the hood) +- Full typescript declaration files +- Promises everywhere! Long live async/await! +- Offline record support + +```JavaScript +{ + // Use indexdb to store data client side + offlineEnabled: false, + // Save each update as it comes in from the server + saveUpdatesOffline: false, + indexdb: { + // The db version, incrementing this triggers a db upgrade + dbVersion: 1, + // This auto updates the indexdb version if the objectStore names change + autoVersion: false, + // The key to index records by + primaryKey: 'id', + // The indexdb databae name + storageDatabaseName: 'deepstream', + // The default store name if not using a '/' to indicate the object store (example person/uuid) + defaultObjectStoreName: 'records', + // The object store names, required in advance due to how indexdb works + objectStoreNames: [], + // Things to not save, such search results + ignorePrefixes: [], + // The amount of time to buffer together actions before making a request + flushTimeout: 50 + } +} ``` -### Improved record error handling +- Customizable offline storage support -Permission errors of any kind are now routed to the record instance that had the error. Consider the following snippet +```typescript +export type offlineStoreWriteResponse = ((error: string | null, recordName: string) => void) -```javascript -client.on('error', (description, event, topic) => { ... }) -const record = client.record.getRecord('permission-error') -record.set('firstname', 'Homer') +export interface RecordOfflineStore { + get: (recordName: string, callback: ((recordName: string, version: number, data: RecordData) => void)) => void + set: (recordName: string, version: number, data: RecordData, callback: offlineStoreWriteResponse) => void + delete: (recordName: string, callback: offlineStoreWriteResponse) => void +} ``` -In this instance all errors are passed to the global client error handler. When dealing with multiple records, it is difficult to correlate errors to different records. We now support the following - -```javascript -const record = client.record.getRecord('permission-error') -record.on('error', (description, event, topic) => { ... }) -record.set('firstname', 'Homer') -``` - -### TypeScript - -This release includes a full rewrite of the client in TypeScript, fixing many bugs and improving the client quality. - -### Client-server binary protocol - -A full implementation of the Universal Realtime Protocol (URP) to match deepstream v4. - -## Breaking changes - -### Chaining +### Improvements -Many functions are no longer chainable (returning an instance of the client or a record) due to supporting promises. This means calls like `client.login` and will return a promise if no callback is provided. +- Separation of errors and warnings for clarity. Non critical failures (such as an ack timeout) can now be treated separated or fully muted. +- Enhanced services to reduce timeout overhead -### Module exports +### Backwards compatibility -Previously a function was exported that allowed creating an instance of the client as follows +- Only works with V4 server +- All single response APIs now return promises when not providing a callback. This means most APIs that could have been chained would now break. -```javascript -const deepstream = require('deepstream.io-client-js') -const client = deepstream('DEEPSTREAM_URL') -// constants also accessible via deepstream.C -``` +```JavaScript +const client = deepstream() +try { + await client.login() -We now support the following + const record = client.record.getRecord(name) + await record.whenReady() -```javascript -const { deepstream, C, EVENT, CONNECTION_STATE } = require('deepstream.io-client-js') + const data = await client.record.snapshot(name) + const version = await client.record.head(name) + const exists = await client.record.has(name) + const result = await client.rpc.make(name, data) + const users = await client.presence.getAll() +} catch (e) { + console.log('Error occurred', e) +} ``` -### Login - -As mentioned higher up, the callback passed to the login function now is called just once. In order to handle client reconnections the `clientDataChanged` and `reAuthenticationFailure` events should be used instead. - -### Listening +- Listening -The `isSubscribed` flag in the listener callback was removed and now the response object -has an `onStop` function which will be called when there are no more subscribers on the topic. +The listening API has been ever so slightly tweaked in order to simplify removing an active subscription. -With this change the same context is kept when a subscription is found or removed -and the listen callback now is called just once. +Before when an active provider was started you would usually need to store it in a higher scope, for example: -For those familiar with listening, this +```typescript +const listeners = new Map() -```javascript -client.record.listen('^news/.*', (match, isSubscribed, response) => { - if (isSubscribed) { - if (/* able to provide for this match */) { - response.accept() - // start providing data +client.record.listen('users/.*', (name, isSubscribed, ({ accept, reject }) => { + if (isSubscribed) { + const updateInterval = setInterval(updateRecord.bind(this, name), 1000) + listeners.set(name, updateInterval) + accept() } else { - response.reject() + clearTimeout(listeners.get(name)) + listeners.delete(name) } - return - } - // stop providing data }) ``` -turns into this: +Where now we instead do: -```javascript -client.record.listen('^news/.*', (match, response) => { - if (/* unable to provide for this match */) { - response.reject() - return - } - response.accept() - response.onStop((match) => { - // stop providing data - }) - // start providing data +```typescript +const listeners = new Map() + +client.record.listen('users/.*', (name, ({ accept, reject, onStop }) => { + const updateInterval = setInterval(updateRecord.bind(this, name), 1000) + accept() + + onStop(() => clearTimeout(updateInterval)) }) ``` -- Presence -The callback for `client.presence.getAll` is now called with an error parameter in standard JavaScript convention +### TLDR; -```javascript -client.presence.getAll((error, userList) => { ... }) -client.presence.getAll(users, (error, userMap) => { ... }) -``` +You can see the in depth side explanation of the changes [here](https://deepstream.io/releases/client-js/v4-0-0/) ## [2.3.0] - 2017-09-25 diff --git a/package-lock.json b/package-lock.json index df3b6dd03..7eb70369d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@deepstream/client", - "version": "4.0.0-rc.50", + "version": "4.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -123,9 +123,9 @@ } }, "@deepstream/protobuf": { - "version": "1.0.0-rc.7", - "resolved": "https://registry.npmjs.org/@deepstream/protobuf/-/protobuf-1.0.0-rc.7.tgz", - "integrity": "sha512-qYCRrq2r+cBk3xF3MK5xdMZT4/QcldRd49pm9sI7adWRhYkMuhs9KaWkMNNYdueenShtSshyKvY1pkX92oZG6A==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@deepstream/protobuf/-/protobuf-1.0.0.tgz", + "integrity": "sha512-wkIUwMg0jqon05Pmo0f3ydZRoDNqBgWETDjvwCqP2KPANhMbpzc6OI+YGRkgjk0MRIbj0kM62TtEMhCD7IFgHw==", "requires": { "protobufjs": "^6.8.8" } @@ -3250,9 +3250,9 @@ "dev": true }, "husky": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-3.0.1.tgz", - "integrity": "sha512-PXBv+iGKw23GHUlgELRlVX9932feFL407/wHFwtsGeArp0dDM4u+/QusSQwPKxmNgjpSL+ustbOdQ2jetgAZbA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/husky/-/husky-3.0.2.tgz", + "integrity": "sha512-WXCtaME2x0o4PJlKY4ap8BzLA+D0zlvefqAvLCPriOOu+x0dpO5uc5tlB7CY6/0SE2EESmoZsj4jW5D09KrJoA==", "dev": true, "requires": { "chalk": "^2.4.2", diff --git a/package.json b/package.json index 9304f3ad5..3d68c7fa2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@deepstream/client", - "version": "4.0.0-rc.50", + "version": "4.0.0", "description": "the javascript client for deepstream.io", "keywords": [ "deepstream", @@ -30,7 +30,7 @@ "url": "https://github.com/deepstreamIO/deepstream.io-client-js.git" }, "dependencies": { - "@deepstream/protobuf": "^1.0.0-rc.7", + "@deepstream/protobuf": "^1.0.0", "protobufjs": "^6.8.8", "ws": "^7.1.1" }, @@ -48,7 +48,7 @@ "bluebird": "^3.5.5", "chai": "^4.2.0", "coveralls": "^3.0.5", - "husky": "^3.0.1", + "husky": "^3.0.2", "mocha": "^6.2.0", "nyc": "^14.1.1", "rimraf": "^2.6.3",