From 3159b385e31febeaa56f823a0273fa3f18d4ec07 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 27 Nov 2019 20:58:43 +0200 Subject: [PATCH 01/70] Add notion of discrete client requests --- client-js/main/client/client-request.js | 126 ++++++++++++++ client-js/main/client/client.js | 203 +--------------------- client-js/main/client/composite-client.js | 84 ++++----- client-js/main/client/firebase-client.js | 2 +- 4 files changed, 172 insertions(+), 243 deletions(-) create mode 100644 client-js/main/client/client-request.js diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js new file mode 100644 index 000000000..b96b406a5 --- /dev/null +++ b/client-js/main/client/client-request.js @@ -0,0 +1,126 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +"use strict"; + +import {Message} from 'google-protobuf'; +import {Observable} from 'rxjs'; + +class FilteringRequest { + + /** + * @param {!Class} targetType + * @param {!QueryingClient | SubscribingClient} client + */ + constructor(targetType, client) { + this._targetType = targetType; + this._client = client; + this._requestFactory = client.requestFactory; + this._builder = null; + } + + targetType() { + return this._targetType; + } + + client() { + return this._client; + } + + builder() { + if (!this._builder) { + this._builder = this.builderFn().apply(this._requestFactory); + } + return this._builder; + } + + /** + * @abstract + * + * @return {Function} + */ + builderFn() { + throw new Error('Not implemented in abstract base.'); + } +} + +export class CommandRequest { + + /** + * @param {!Message} commandMessage + * @param {!CommandingClient} client + */ + constructor(commandMessage, client) { + this._commandMessage = commandMessage; + this._client = client; + } + + /** + * @return {Promise | Observable>} + * + * @template a Protobuf type of entities being the target of a query + */ + post() { + // TODO:2019-11-27:dmytro.kuzmin:WIP Extend with event-subscribing logic. + this._client.sendCommand(this._commandMessage); + } +} + +export class QueryRequest extends FilteringRequest { + + constructor(targetType, client) { + super(targetType, client) + } + + /** + * @return {Promise<[]>} + * + * @template a Protobuf type of entities being the target of a query + */ + run() { + const query = this.builder().build(); + return this.client().execute(query); + } + + builderFn() { + return requestFactory => requestFactory.query().select(this.targetType()); + } +} + +export class SubscriptionRequest extends FilteringRequest { + + constructor(targetType, client) { + super(targetType, client) + } + + /** + * @return {Promise>} + * + * @template a Protobuf type of entities being the target of a subscription + */ + post() { + const topic = this.builder().build(); + return this.client().subscribe(topic); + } + + builderFn() { + return requestFactory => requestFactory.topic().select(this.targetType()); + } +} diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index d1b978f22..ae9856900 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -22,8 +22,6 @@ import {Message} from 'google-protobuf'; import {Observable} from 'rxjs'; -import {Query} from '../proto/spine/client/query_pb'; -import {Topic} from '../proto/spine/client/subscription_pb'; /** * The callback that doesn't accept any parameters. @@ -82,209 +80,26 @@ import {Topic} from '../proto/spine/client/subscription_pb'; export class Client { /** - * Creates a new {@link QueryFactory} for creating `Query` instances specifying - * the data to be retrieved from Spine server. - * - * @example - * // Build a query for `Task` domain entity, specifying particular IDs. - * newQuery().select(Task) - * .byIds([taskId1, taskId2]) - * .build() - * - * @example - * // Build a query for `Task` domain entity, selecting the instances which assigned to the - * // particular user. - * newQuery().select(Task) - * .where([Filters.eq('assignee', userId)]) - * .build() - * - * To execute the resulting `Query` instance pass it to the {@link Client#execute()}. - * - * @return {QueryFactory} a factory for creating queries to the Spine server - * - * @see QueryFactory - * @see QueryBuilder - * @see AbstractTargetBuilder + * @param {!Message} command a Protobuf type of the query target entities + * @return {CommandRequest} */ - newQuery() { + command(command) { throw new Error('Not implemented in abstract base.'); } /** - * Executes the given `Query` instance specifying the data to be retrieved from - * Spine server fulfilling a returned promise with an array of received objects. - * - * @param {!Query} query a query instance to be executed - * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf - * messages of a given type or with an empty list if no entities matching given query - * were found; rejected with a `SpineError` if error occurs; - * - * @template a Protobuf type of entities being the target of a query + * @param {!Class} entityType a Protobuf type of the query target entities + * @return {QueryRequest} */ - execute(query) { + select(entityType) { throw new Error('Not implemented in abstract base.'); } /** - * Creates a new {@link TopicFactory} for building subscription topics specifying - * the state changes to be observed from Spine server. - * - * @example - * // Build a subscription topic for `UserTasks` domain entity. - * newTopic().select(Task) - * .build() - * - * @example - * // Build a subscription topic for `UserTasks` domain entity, selecting the instances - * // which task count is greater than 3. - * newTopic().select(UserTasks) - * .where(Filters.gt('tasksCount', 3)) - * .build() - * - * To turn the resulting `Topic` instance into a subscription pass it - * to the {@link Client#subscribeTo()}. - * - * @return {TopicFactory} a factory for creating subscription topics to the Spine server - * - * @see TopicFactory - * @see TopicBuilder - * @see AbstractTargetBuilder + * @param {!Class} type a Protobuf type of the target entities or events + * @return {SubscriptionRequest} */ - newTopic() { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Creates a subscription to the topic which is updated with backend changes. Fulfills - * a returning promise with the created subscription. - * - * @param {!Topic} topic a topic to subscribe - * @return {Promise>} a promise to be resolved with an object - * representing a result of the subscription to entities state changes; rejected with - * a `SpineError` if error occurs; - * - * @template a Protobuf type of entities being the target of a subscription - */ - subscribeTo(topic) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Sends the provided command to the server. - * - * After sending the command to the server the following scenarios are possible: - * - * - the `acknowledgedCallback` is called if the command is acknowledged for further processing - * - the `errorCallback` is called if sending of the command failed - * - * Invocation of the `acknowledgedCallback` and the `errorCallback` are mutually exclusive. - * - * If the command sending fails, the respective error is passed to the `errorCallback`. This error - * is always the type of `CommandHandlingError`. Its cause can be retrieved by `getCause()` method - * and can be represented with the following types of errors: - * - * - `ConnectionError` – if the connection error occurs; - * - `ClientError` – if the server responds with `4xx` HTTP status code; - * - `ServerError` – if the server responds with `5xx` HTTP status code; - * - `spine.base.Error` – if the command message can't be processed by the server; - * - `SpineError` – if parsing of the response fails; - * - * If the command sending fails due to a command validation error, an error passed to the - * `errorCallback` is the type of `CommandValidationError` (inherited from - * `CommandHandlingError`). The validation error can be retrieved by `validationError()` method. - * - * The occurrence of an error does not guarantee that the command is not accepted by the server - * for further processing. To verify this, call the error `assuresCommandNeglected()` method. - * - * @param {!Message} commandMessage a Protobuf message representing the command - * @param {!parameterlessCallback} acknowledgedCallback - * a no-argument callback invoked if the command is acknowledged - * @param {?consumerCallback} errorCallback - * a callback receiving the errors executed if an error occurred when sending command - * @param {?consumerCallback} rejectionCallback - * a callback executed if the command was rejected by Spine server - * @see CommandHandlingError - * @see CommandValidationError - */ - sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Fetches entities of the given class type from the Spine backend. - * - * Optionally accepts entity identifiers targeting the objects to fetch. If no identifiers are - * passed, returns all entities of the selected type. - * - * The returned promise is fulfilled an array of objects, each representing an entity. - * - * This API call is a shortcut for {@link Client#newQuery()} followed by {@link Client#execute()}. - * It covers the most common use cases. If a more advanced fetch behaviour is required, the - * `Query` instance should be created and parameterized via {@link Client#newQuery()}. - * - * @example - * // Fetch all `Task` domain entities. Returning promise resolves with a list of entities - * // or with an empty list if no records of the specified type were found. - * fetch({entity: Task}).then(tasks => { ... }) - * - * @example - * // Fetch a single `Task` domain entity by ID. Returning promise resolves with a list containing - * // the target entity or with an empty list if no record with the specified ID was found. - * fetch({entity: Task, byIds: taskId}).then(task => { ... }) - * - * @example - * // Fetch several `Task` domain entities by IDs. Returning promise resolves with a list of - * // entities or with an empty list if no records with the specified IDs were found. - * fetch({entity: Task, byIds: [taskId1, taskId2]}).then(tasks => { ... }) - * - * @param {SimpleTarget} object representing a set of parameters for building a query by target - * entities class type and IDs - * @return {Promise} a promise to be fulfilled with a list of Protobuf messages - * of a given type or with an empty list if no entities matching given class or IDs were - * found; rejected with a `SpineError` if error occurs; - * - * @template a Protobuf type of entities being the fetch target - */ - fetch({entity: cls, byIds: ids}) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Creates a subscription to changes of entities of given class type from the Spine backend. - * - * Optionally accepts entity identifiers targeting the objects. If no identifiers are - * passed, subscribes to changes of all entities of the selected type. - * - * Fulfills a returning promise with the created subscription. - * - * The entities that already exist will be initially passed to the `itemAdded` observer. - * - * This API call is a shortcut for {@link Client#newTopic()} followed by - * {@link Client#subscribeTo()}. It covers the most common use cases. If a more advanced - * subscription behaviour is required, the `Topic` instance should be created and parameterized - * via {@link Client#newTopic()}. - * - * @example - * // Subscribe to changes of all `UserTasks` domain entities. Returning promise resolves with - * // an object representing a result of the subscription. - * subscribe({entity: UserTasks}).then(subscriptionObject => { ... }) - * - * @example - * // Subscribe to changes of a single `UserTasks` domain entity by ID. - * subscribe({entity: Task, byIds: taskId}).then(subscriptionObject => { ... }) - * - * @example - * // Subscribe to changes of several `UserTasks` domain entities by IDs. - * subscribe({entity: Task, byIds: [taskId1, taskId2]}).then(subscriptionObject => { ... }) - * - * @param {SimpleTarget} object representing a set of parameters for building a subscription - * topic by target entities class type and IDs - * @return {Promise>} a promise of means to observe the changes - * and unsubscribe from the updated - * - * @template a Protobuf type of entities being the subscription target - */ - subscribe({entity: cls, byIds: ids}) { + subscribeTo(type) { throw new Error('Not implemented in abstract base.'); } } diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index 1f1c72c8c..b29a9dfb8 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -20,10 +20,12 @@ "use strict"; +import {Message} from 'google-protobuf'; import ObjectToProto from "./object-to-proto"; import {CommandHandlingError, CommandValidationError, SpineError} from "./errors"; import {Status} from '../proto/spine/core/response_pb'; import {Client} from "./client"; +import {QueryRequest, SubscriptionRequest} from "./client-request"; /** * A {@link Client} that delegates requests to case-specific client implementations. @@ -59,35 +61,25 @@ export class CompositeClient extends Client { this._commanding = commanding; } - newQuery() { - return this._querying.newQuery(); - } - - execute(query) { - return this._querying.execute(query); - } - - fetch({entity: cls, byIds: ids}) { - return this._querying.fetch(cls, ids); - } - - newTopic() { - return this._subscribing.newTopic(); - } - - subscribeTo(topic) { - return this._subscribing.subscribeTo(topic); + /** + * @override + */ + command(command) { + this._commanding.command(command); } - subscribe({entity: cls, byIds: ids}) { - return this._subscribing.subscribe(cls, ids); + /** + * @override + */ + select(entityType) { + this._querying.select(entityType); } - sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { - return this._commanding.sendCommand(commandMessage, - acknowledgedCallback, - errorCallback, - rejectionCallback); + /** + * @override + */ + subscribeTo(type) { + this._subscribing.subscribeTo(type); } } @@ -103,7 +95,7 @@ export class QueryingClient { * a request factory to build requests to Spine server */ constructor(actorRequestFactory) { - this._requestFactory = actorRequestFactory; + this.requestFactory = actorRequestFactory; } /** @@ -121,14 +113,12 @@ export class QueryingClient { throw new Error('Not implemented in abstract base.'); } - newQuery() { - return this._requestFactory.query(); - } - - fetch(entityClass, ids) { - const queryBuilder = this.newQuery().select(entityClass); - const query = _buildTarget(queryBuilder, ids); - return this.execute(query); + /** + * @param {!Class} entityType a Protobuf type of the query target entities + * @return {QueryRequest} + */ + select(entityType) { + return new QueryRequest(entityType, this); } } @@ -144,22 +134,20 @@ export class SubscribingClient { * a request factory to build requests to Spine server */ constructor(actorRequestFactory) { - this._requestFactory = actorRequestFactory; + this.requestFactory = actorRequestFactory; } - subscribeTo(topic) { + /** + * @return {Promise>} + * + * @template a Protobuf type of entities being the target of a subscription + */ + subscribe(topic) { throw new Error('Not implemented in abstract base.'); } - newTopic() { - return this._requestFactory.topic(); - } - - subscribe(entityClass, ids) { - const topicBuilder = this.newTopic().select(entityClass); - const topic = _buildTarget(topicBuilder, ids); - - return this.subscribeTo(topic); + subscribeTo(type) { + return new SubscriptionRequest(type, this); } } @@ -177,7 +165,7 @@ export class NoOpSubscribingClient extends SubscribingClient { * * @override */ - subscribeTo(topic) { + subscribe(topic) { throw new Error('Entity subscription is not supported.'); } } @@ -192,12 +180,12 @@ const _statusType = Status.typeUrl(); export class CommandingClient { constructor(endpoint, requestFactory) { - this._requestFactory = requestFactory; + this.requestFactory = requestFactory; this._endpoint = endpoint; } sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { - const command = this._requestFactory.command().create(commandMessage); + const command = this.requestFactory.command().create(commandMessage); this._endpoint.command(command) .then(ack => this._onAck(ack, acknowledgedCallback, errorCallback, rejectionCallback)) .catch(error => { diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 0cfaa7166..620eeb836 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -139,7 +139,7 @@ class FirebaseSubscribingClient extends SubscribingClient { /** * @inheritDoc */ - subscribeTo(topic) { + subscribe(topic) { return new Promise((resolve, reject) => { const typeUrl = topic.getTarget().getType(); this._endpoint.subscribeTo(topic) From 42c587fce43d1f78c319462f7ab5a1731a6b6b3b Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Fri, 29 Nov 2019 14:43:18 +0200 Subject: [PATCH 02/70] Add filtering logic to client requests --- client-js/main/client/client-request.js | 75 +++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index b96b406a5..182a6add0 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -22,6 +22,8 @@ import {Message} from 'google-protobuf'; import {Observable} from 'rxjs'; +import {CompositeFilter, Filter} from '../proto/spine/client/filters_pb'; +import {OrderBy} from '../proto/spine/client/query_pb'; class FilteringRequest { @@ -36,6 +38,36 @@ class FilteringRequest { this._builder = null; } + /** + * + * @param ids {![]|Number[]|String[]} + * @return {QueryRequest | SubscriptionRequest} self + * + * @template a Protobuf type of IDs + */ + byId(ids) { + this.builder().byIds(ids); + return this.self(); + } + + /** + * @param {!Filter[]|CompositeFilter[]} predicates + * @return {QueryRequest | SubscriptionRequest} self + */ + where(predicates) { + this.builder().where(predicates); + return this.self(); + } + + /** + * @param {!String[]} fieldNames + * @return {QueryRequest | SubscriptionRequest} self + */ + withMask(fieldNames) { + this.builder().withMask(fieldNames); + return this.self(); + } + targetType() { return this._targetType; } @@ -59,6 +91,15 @@ class FilteringRequest { builderFn() { throw new Error('Not implemented in abstract base.'); } + + /** + * @abstract + * + * @return {QueryRequest | SubscriptionRequest} + */ + self() { + throw new Error('Not implemented in abstract base.'); + } } export class CommandRequest { @@ -89,6 +130,32 @@ export class QueryRequest extends FilteringRequest { super(targetType, client) } + // TODO:2019-11-27:dmytro.kuzmin:WIP See what we can do about it. + // noinspection JSValidateJSDoc unresolved nested type which actually exists + /** + * + * @param {!String} column + * @param {!OrderBy.Direction} direction + * @return {QueryRequest} self + */ + orderBy(column, direction) { + if (direction === OrderBy.Direction.ASCENDING) { + this.builder().orderAscendingBy(column); + } else { + this.builder().orderDescendingBy(column); + } + return this.self(); + } + + /** + * @param {number} count the max number of response entities + * @return {QueryRequest} self + */ + limit(count) { + this.builder().limit(count); + return this.self(); + } + /** * @return {Promise<[]>} * @@ -102,6 +169,10 @@ export class QueryRequest extends FilteringRequest { builderFn() { return requestFactory => requestFactory.query().select(this.targetType()); } + + self() { + return this; + } } export class SubscriptionRequest extends FilteringRequest { @@ -123,4 +194,8 @@ export class SubscriptionRequest extends FilteringRequest { builderFn() { return requestFactory => requestFactory.topic().select(this.targetType()); } + + self() { + return this; + } } From b80b1812d70ca04be4c8a2bced1b87b3024838e9 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Fri, 29 Nov 2019 15:20:19 +0200 Subject: [PATCH 03/70] Implement missing methods --- client-js/main/client/composite-client.js | 43 +++++++++++++++-------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index b29a9dfb8..b51113a77 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -25,7 +25,7 @@ import ObjectToProto from "./object-to-proto"; import {CommandHandlingError, CommandValidationError, SpineError} from "./errors"; import {Status} from '../proto/spine/core/response_pb'; import {Client} from "./client"; -import {QueryRequest, SubscriptionRequest} from "./client-request"; +import {CommandRequest, QueryRequest, SubscriptionRequest} from "./client-request"; /** * A {@link Client} that delegates requests to case-specific client implementations. @@ -98,6 +98,14 @@ export class QueryingClient { this.requestFactory = actorRequestFactory; } + /** + * @param {!Class} entityType a Protobuf type of the query target entities + * @return {QueryRequest} + */ + select(entityType) { + return new QueryRequest(entityType, this); + } + /** * Executes the given `Query` instance specifying the data to be retrieved from * Spine server fulfilling a returned promise with an array of received objects. @@ -112,14 +120,6 @@ export class QueryingClient { execute(query) { throw new Error('Not implemented in abstract base.'); } - - /** - * @param {!Class} entityType a Protobuf type of the query target entities - * @return {QueryRequest} - */ - select(entityType) { - return new QueryRequest(entityType, this); - } } /** @@ -137,6 +137,10 @@ export class SubscribingClient { this.requestFactory = actorRequestFactory; } + subscribeTo(type) { + return new SubscriptionRequest(type, this); + } + /** * @return {Promise>} * @@ -145,12 +149,10 @@ export class SubscribingClient { subscribe(topic) { throw new Error('Not implemented in abstract base.'); } - - subscribeTo(type) { - return new SubscriptionRequest(type, this); - } } +const SUBSCRIPTIONS_NOT_SUPPORTED = 'Subscriptions are not supported.'; + /** * A {@link SubscribingClient} which does not create subscriptions. */ @@ -160,13 +162,22 @@ export class NoOpSubscribingClient extends SubscribingClient { super(actorRequestFactory) } + /** + * Always throws an error. + * + * @override + */ + subscribeTo(type) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } + /** * Always throws an error. * * @override */ subscribe(topic) { - throw new Error('Entity subscription is not supported.'); + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); } } @@ -184,6 +195,10 @@ export class CommandingClient { this._endpoint = endpoint; } + command(commandMessage) { + return new CommandRequest(commandMessage, this); + } + sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { const command = this.requestFactory.command().create(commandMessage); this._endpoint.command(command) From 8804f8b225e270a23f2488c617fe3b2b0ab7823c Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Sat, 30 Nov 2019 15:51:05 +0200 Subject: [PATCH 04/70] Add notion of observed types to command request --- client-js/main/client/client-request.js | 91 +++++++++++++++++------ client-js/main/client/client.js | 16 ++-- client-js/main/client/composite-client.js | 67 +++++++---------- client-js/main/client/firebase-client.js | 2 +- 4 files changed, 104 insertions(+), 72 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 182a6add0..7025f3f8b 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -21,7 +21,6 @@ "use strict"; import {Message} from 'google-protobuf'; -import {Observable} from 'rxjs'; import {CompositeFilter, Filter} from '../proto/spine/client/filters_pb'; import {OrderBy} from '../proto/spine/client/query_pb'; @@ -102,28 +101,6 @@ class FilteringRequest { } } -export class CommandRequest { - - /** - * @param {!Message} commandMessage - * @param {!CommandingClient} client - */ - constructor(commandMessage, client) { - this._commandMessage = commandMessage; - this._client = client; - } - - /** - * @return {Promise | Observable>} - * - * @template a Protobuf type of entities being the target of a query - */ - post() { - // TODO:2019-11-27:dmytro.kuzmin:WIP Extend with event-subscribing logic. - this._client.sendCommand(this._commandMessage); - } -} - export class QueryRequest extends FilteringRequest { constructor(targetType, client) { @@ -199,3 +176,71 @@ export class SubscriptionRequest extends FilteringRequest { return this; } } + +const NOOP_CALLBACK = () => {}; + +export class CommandRequest { + + /** + * @param {!Message} commandMessage + * @param {!CommandingClient} client + */ + constructor(commandMessage, client) { + this._commandMessage = commandMessage; + this._client = client; + this._onAck = NOOP_CALLBACK; + this._onError = NOOP_CALLBACK; + this._onRejection = NOOP_CALLBACK; + this._observedTypes = []; + } + + /** + * @param {!parameterlessCallback} callback + * + * @return {CommandRequest} self + */ + onAck(callback) { + this._onAck = callback; + return this; + } + + /** + * @param {!consumerCallback} callback + * + * @return {CommandRequest} self + */ + onError(callback) { + this._onError = callback; + return this; + } + + /** + * @param {!consumerCallback} callback + * + * @return {CommandRequest} self + */ + onRejection(callback) { + this._onRejection = callback; + return this; + } + + /** + * @param {!Class} eventType a Protobuf type of the observed events + * + * @return {CommandRequest} self + */ + observe(eventType) { + this._observedTypes.push(eventType); + return this; + } + + /** + * @return {Promise, EntitySubscriptionObject> | EntitySubscriptionObject>} + */ + post() { + // TODO:2019-11-27:dmytro.kuzmin:WIP Extend with event-subscribing logic. + const ackCallback = + {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; + this._client.sendCommand(this._commandMessage, ackCallback, this._observedTypes); + } +} diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index ae9856900..1fae602c7 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -79,14 +79,6 @@ import {Observable} from 'rxjs'; */ export class Client { - /** - * @param {!Message} command a Protobuf type of the query target entities - * @return {CommandRequest} - */ - command(command) { - throw new Error('Not implemented in abstract base.'); - } - /** * @param {!Class} entityType a Protobuf type of the query target entities * @return {QueryRequest} @@ -102,4 +94,12 @@ export class Client { subscribeTo(type) { throw new Error('Not implemented in abstract base.'); } + + /** + * @param {!Message} command a Protobuf type of the query target entities + * @return {CommandRequest} + */ + command(command) { + throw new Error('Not implemented in abstract base.'); + } } diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index b51113a77..b310a0225 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -64,22 +64,22 @@ export class CompositeClient extends Client { /** * @override */ - command(command) { - this._commanding.command(command); + select(entityType) { + this._querying.select(entityType); } /** * @override */ - select(entityType) { - this._querying.select(entityType); + subscribeTo(type) { + this._subscribing.subscribeTo(type); } /** * @override */ - subscribeTo(type) { - this._subscribing.subscribeTo(type); + command(command) { + this._commanding.command(command); } } @@ -183,6 +183,14 @@ export class NoOpSubscribingClient extends SubscribingClient { const _statusType = Status.typeUrl(); +/** + * @typedef AckCallback + * + * @property {!parameterlessCallback} onOk + * @property {!consumerCallback} onError + * @property {!consumerCallback} onRejection + */ + /** * A client which posts commands. * @@ -199,64 +207,43 @@ export class CommandingClient { return new CommandRequest(commandMessage, this); } - sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { + /** + * @param {!Message} commandMessage + * @param {!AckCallback} ackCallback + * @param {!Array>} observedTypes + */ + sendCommand(commandMessage, ackCallback, observedTypes) { const command = this.requestFactory.command().create(commandMessage); this._endpoint.command(command) - .then(ack => this._onAck(ack, acknowledgedCallback, errorCallback, rejectionCallback)) + .then(ack => this._onAck(ack, ackCallback)) .catch(error => { - errorCallback(new CommandHandlingError(error.message, error)); + ackCallback.onError(new CommandHandlingError(error.message, error)); }); } - _onAck(ack, acknowledgedCallback, errorCallback, rejectionCallback) { + _onAck(ack, ackCallback) { const responseStatus = ack.status; const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); const responseStatusCase = responseStatusProto.getStatusCase(); switch (responseStatusCase) { case Status.StatusCase.OK: - acknowledgedCallback(); + ackCallback.onOk(); break; case Status.StatusCase.ERROR: const error = responseStatusProto.getError(); const message = error.getMessage(); - errorCallback(error.hasValidationError() + ackCallback.onError(error.hasValidationError() ? new CommandValidationError(message, error) : new CommandHandlingError(message, error)); break; case Status.StatusCase.REJECTION: - rejectionCallback(responseStatusProto.getRejection()); + ackCallback.onRejection(responseStatusProto.getRejection()); break; default: - errorCallback( + ackCallback.onError( new SpineError(`Unknown response status case ${responseStatusCase}`) ); } } } - -/** - * Builds target from the given target builder specifying the set - * of target entities. - * - * The resulting target points to: - * - the particular entities with the given IDs; - * - the all entities if no IDs specified. - * - * @param {AbstractTargetBuilder} targetBuilder - * a builder for creating `Query` or `Topic` instances. - * @param {?[] | | Number[] | Number | String[] | String} ids - * a list of target entities IDs or an ID of a single target entity - * @return {Query|Topic} the built target - * - * @template a class of identifiers, corresponding to the query/subscription targets - * @private - */ -function _buildTarget(targetBuilder, ids) { - if (Array.isArray(ids)) { - targetBuilder.byIds(ids); - } else if (!!ids) { - targetBuilder.byIds([ids]); - } - return targetBuilder.build(); -} diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 620eeb836..afb577c0c 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -79,7 +79,7 @@ class EntitySubscription extends Subscription { * @return {EntitySubscriptionObject} a plain object with observables and unsubscribe method */ toObject() { - return Object.assign({}, this._observables, {unsubscribe: () => this.unsubscribe()}) + return Object.assign({}, this._observables, {unsubscribe: () => this.unsubscribe()}); } } From 7c0778af3867a5511b8f027c8bc84dbd4917adac Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Sat, 30 Nov 2019 20:25:59 +0200 Subject: [PATCH 05/70] Add observed types implementation to the command request --- .../main/client/actor-request-factory.js | 4 +- client-js/main/client/client-request.js | 123 ++++++++++++------ client-js/main/client/client.js | 43 ++++++ client-js/main/client/composite-client.js | 51 +++++--- client-js/main/client/direct-client.js | 2 +- client-js/main/client/firebase-client.js | 2 +- 6 files changed, 161 insertions(+), 64 deletions(-) diff --git a/client-js/main/client/actor-request-factory.js b/client-js/main/client/actor-request-factory.js index f168e5818..d7ab18ea2 100644 --- a/client-js/main/client/actor-request-factory.js +++ b/client-js/main/client/actor-request-factory.js @@ -814,7 +814,7 @@ class CommandFactory { * Creates a `Command` from the given command message. * * @param {!Message} message a command message - * @return {TypedMessage} a typed representation of the Spine Command + * @return {Command} a typed representation of the Spine Command */ create(message) { const id = CommandFactory._newCommandId(); @@ -825,7 +825,7 @@ class CommandFactory { result.setId(id); result.setMessage(messageAny); result.setContext(context); - return TypedMessage.of(result); + return result; } _commandContext() { diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 7025f3f8b..9cca8cf0c 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -23,24 +23,37 @@ import {Message} from 'google-protobuf'; import {CompositeFilter, Filter} from '../proto/spine/client/filters_pb'; import {OrderBy} from '../proto/spine/client/query_pb'; +import {MessageId, Origin} from '../proto/spine/core/diagnostics_pb'; +import {AnyPacker} from "./any-packer"; +import {Filters} from "./actor-request-factory"; -class FilteringRequest { +class ClientRequest { + + /** + * @param {!Client} client + * @param {!ActorRequestFactory} actorRequestFactory + */ + constructor(client, actorRequestFactory) { + this.client = client; + this.actorRequestFactory = actorRequestFactory; + } +} + +class FilteringRequest extends ClientRequest { /** * @param {!Class} targetType - * @param {!QueryingClient | SubscribingClient} client + * @param {!Client} client + * @param {!ActorRequestFactory} actorRequestFactory */ - constructor(targetType, client) { - this._targetType = targetType; - this._client = client; - this._requestFactory = client.requestFactory; - this._builder = null; + constructor(targetType, client, actorRequestFactory) { + super(client, actorRequestFactory); + this.targetType = targetType; } /** - * * @param ids {![]|Number[]|String[]} - * @return {QueryRequest | SubscriptionRequest} self + * @return {FilteringRequest} self * * @template a Protobuf type of IDs */ @@ -51,7 +64,7 @@ class FilteringRequest { /** * @param {!Filter[]|CompositeFilter[]} predicates - * @return {QueryRequest | SubscriptionRequest} self + * @return {FilteringRequest} self */ where(predicates) { this.builder().where(predicates); @@ -60,24 +73,18 @@ class FilteringRequest { /** * @param {!String[]} fieldNames - * @return {QueryRequest | SubscriptionRequest} self + * @return {FilteringRequest} self */ withMask(fieldNames) { this.builder().withMask(fieldNames); return this.self(); } - targetType() { - return this._targetType; - } - - client() { - return this._client; - } - builder() { + // TODO:2019-11-27:dmytro.kuzmin:WIP Check that setting to some initial value is + // unnecessary. if (!this._builder) { - this._builder = this.builderFn().apply(this._requestFactory); + this._builder = this.newBuilderFn().apply(this.actorRequestFactory); } return this._builder; } @@ -87,14 +94,14 @@ class FilteringRequest { * * @return {Function} */ - builderFn() { + newBuilderFn() { throw new Error('Not implemented in abstract base.'); } /** * @abstract * - * @return {QueryRequest | SubscriptionRequest} + * @return {FilteringRequest} self */ self() { throw new Error('Not implemented in abstract base.'); @@ -103,8 +110,8 @@ class FilteringRequest { export class QueryRequest extends FilteringRequest { - constructor(targetType, client) { - super(targetType, client) + constructor(targetType, client, actorRequestFactory) { + super(targetType, client, actorRequestFactory) } // TODO:2019-11-27:dmytro.kuzmin:WIP See what we can do about it. @@ -140,11 +147,11 @@ export class QueryRequest extends FilteringRequest { */ run() { const query = this.builder().build(); - return this.client().execute(query); + return this.client.read(query); } - builderFn() { - return requestFactory => requestFactory.query().select(this.targetType()); + newBuilderFn() { + return requestFactory => requestFactory.query().select(this.targetType); } self() { @@ -154,8 +161,8 @@ export class QueryRequest extends FilteringRequest { export class SubscriptionRequest extends FilteringRequest { - constructor(targetType, client) { - super(targetType, client) + constructor(targetType, client, actorRequestFactory) { + super(targetType, client, actorRequestFactory) } /** @@ -165,11 +172,11 @@ export class SubscriptionRequest extends FilteringRequest { */ post() { const topic = this.builder().build(); - return this.client().subscribe(topic); + return this.client.subscribe(topic); } - builderFn() { - return requestFactory => requestFactory.topic().select(this.targetType()); + newBuilderFn() { + return requestFactory => requestFactory.topic().select(this.targetType); } self() { @@ -179,15 +186,16 @@ export class SubscriptionRequest extends FilteringRequest { const NOOP_CALLBACK = () => {}; -export class CommandRequest { +export class CommandRequest extends ClientRequest{ /** * @param {!Message} commandMessage - * @param {!CommandingClient} client + * @param {!Client} client + * @param {!ActorRequestFactory} actorRequestFactory */ - constructor(commandMessage, client) { + constructor(commandMessage, client, actorRequestFactory) { + super(client, actorRequestFactory); this._commandMessage = commandMessage; - this._client = client; this._onAck = NOOP_CALLBACK; this._onError = NOOP_CALLBACK; this._onRejection = NOOP_CALLBACK; @@ -235,12 +243,49 @@ export class CommandRequest { } /** - * @return {Promise, EntitySubscriptionObject> | EntitySubscriptionObject>} + * @return {Promise} */ post() { - // TODO:2019-11-27:dmytro.kuzmin:WIP Extend with event-subscribing logic. + const command = this.actorRequestFactory.command().create(this._commandMessage); const ackCallback = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; - this._client.sendCommand(this._commandMessage, ackCallback, this._observedTypes); + this.client.post(command, ackCallback); + const promises = []; + this._observedTypes.forEach(type => { + const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); + const promise = this.client.subscribeTo(type) + .where([originFilter]) + .post(); + promises.push(promise); + }); + if (promises.length === 1) { + return promises[0]; + } + return Promise.all(promises); + } + + /** + * @param {!Command} command + * + * @return {Origin} + * + * @private + */ + _asOrigin(command) { + const result = new Origin(); + + const messageId = new MessageId(); + const packedId = AnyPacker.pack(command.getId()); + messageId.setId(packedId); + const typeUrl = command.getMessage().getTypeUrl(); + messageId.setTypeUrl(typeUrl); + result.setMessage(messageId); + + const grandOrigin = command.getContext().getOrigin(); + result.setGrandOrigin(grandOrigin); + + const actorContext = command.getContext().getActorContext(); + result.setActorContext(actorContext); + return result; } } diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index 1fae602c7..248e5caec 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -71,6 +71,14 @@ import {Observable} from 'rxjs'; * @template a class of a query or subscription target entities identifiers */ +/** + * @typedef AckCallback + * + * @property {!parameterlessCallback} onOk + * @property {!consumerCallback} onError + * @property {!consumerCallback} onRejection + */ + /** * An abstract client for Spine application backend. This is a single channel for client-server * communication in a Spine-based browser application. @@ -87,6 +95,21 @@ export class Client { throw new Error('Not implemented in abstract base.'); } + /** + * Executes the given `Query` instance specifying the data to be retrieved from + * Spine server fulfilling a returned promise with an array of received objects. + * + * @param {!spine.client.Query} query a query instance to be executed + * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf + * messages of a given type or with an empty list if no entities matching given query + * were found; rejected with a `SpineError` if error occurs + * + * @template a Protobuf type of entities being the target of a query + */ + read(query) { + throw new Error('Not implemented in abstract base.'); + } + /** * @param {!Class} type a Protobuf type of the target entities or events * @return {SubscriptionRequest} @@ -95,6 +118,17 @@ export class Client { throw new Error('Not implemented in abstract base.'); } + /** + * @param {!spine.client.Topic} topic + * + * @return {Promise>} + * + * @template a Protobuf type of entities being the target of a subscription + */ + subscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } + /** * @param {!Message} command a Protobuf type of the query target entities * @return {CommandRequest} @@ -102,4 +136,13 @@ export class Client { command(command) { throw new Error('Not implemented in abstract base.'); } + + /** + * @param {!Command} command a Command send to Spine server + * @param {!AckCallback} ackCallback + * @param {!Array>} observedTypes + */ + post(command, ackCallback) { + throw new Error('Not implemented in abstract base.'); + } } diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index b310a0225..66e326fbd 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -26,6 +26,7 @@ import {CommandHandlingError, CommandValidationError, SpineError} from "./errors import {Status} from '../proto/spine/core/response_pb'; import {Client} from "./client"; import {CommandRequest, QueryRequest, SubscriptionRequest} from "./client-request"; +import {TypedMessage} from "./typed-message"; /** * A {@link Client} that delegates requests to case-specific client implementations. @@ -65,21 +66,42 @@ export class CompositeClient extends Client { * @override */ select(entityType) { - this._querying.select(entityType); + return this._querying.select(entityType); + } + + /** + * @override + */ + read(query) { + return this._querying.read(query); } /** * @override */ subscribeTo(type) { - this._subscribing.subscribeTo(type); + return this._subscribing.subscribeTo(type); + } + + /** + * @override + */ + subscribe(topic) { + return this._subscribing.subscribe(topic) } /** * @override */ command(command) { - this._commanding.command(command); + return this._commanding.command(command); + } + + /** + * @override + */ + post(command, ackCallback) { + this._commanding.post(command, ackCallback); } } @@ -110,14 +132,14 @@ export class QueryingClient { * Executes the given `Query` instance specifying the data to be retrieved from * Spine server fulfilling a returned promise with an array of received objects. * - * @param {!Query} query a query instance to be executed + * @param {!spine.client.Query} query a query instance to be executed * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf * messages of a given type or with an empty list if no entities matching given query * were found; rejected with a `SpineError` if error occurs * * @template a Protobuf type of entities being the target of a query */ - execute(query) { + read(query) { throw new Error('Not implemented in abstract base.'); } } @@ -183,14 +205,6 @@ export class NoOpSubscribingClient extends SubscribingClient { const _statusType = Status.typeUrl(); -/** - * @typedef AckCallback - * - * @property {!parameterlessCallback} onOk - * @property {!consumerCallback} onError - * @property {!consumerCallback} onRejection - */ - /** * A client which posts commands. * @@ -207,14 +221,9 @@ export class CommandingClient { return new CommandRequest(commandMessage, this); } - /** - * @param {!Message} commandMessage - * @param {!AckCallback} ackCallback - * @param {!Array>} observedTypes - */ - sendCommand(commandMessage, ackCallback, observedTypes) { - const command = this.requestFactory.command().create(commandMessage); - this._endpoint.command(command) + post(command, ackCallback) { + const cmd = TypedMessage.of(command); + this._endpoint.command(cmd) .then(ack => this._onAck(ack, ackCallback)) .catch(error => { ackCallback.onError(new CommandHandlingError(error.message, error)); diff --git a/client-js/main/client/direct-client.js b/client-js/main/client/direct-client.js index 27221e8af..434ad8f75 100644 --- a/client-js/main/client/direct-client.js +++ b/client-js/main/client/direct-client.js @@ -102,7 +102,7 @@ class DirectQueryingClient extends QueryingClient { this._endpoint = endpoint; } - execute(query) { + read(query) { const typeUrl = query.getTarget().getType(); const targetClass = KnownTypes.classFor(typeUrl); const targetType = Type.of(targetClass, typeUrl); diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index afb577c0c..9e5e9f46b 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -98,7 +98,7 @@ class FirebaseQueryingClient extends QueryingClient { this._firebase = firebaseDatabase; } - execute(query) { + read(query) { return new Promise((resolve, reject) => { this._endpoint.query(query) .then(({path}) => this._firebase.getValues(path, values => { From 5fa19f3621eec94742c0b6a7287392eee22f3b56 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Sun, 1 Dec 2019 18:43:50 +0200 Subject: [PATCH 06/70] Resolve issues with the wrong type of client specified to requests --- client-js/main/client/composite-client.js | 24 ++++------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index 66e326fbd..d5f82465a 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -66,7 +66,7 @@ export class CompositeClient extends Client { * @override */ select(entityType) { - return this._querying.select(entityType); + return new QueryRequest(entityType, this, this._querying.requestFactory); } /** @@ -80,7 +80,7 @@ export class CompositeClient extends Client { * @override */ subscribeTo(type) { - return this._subscribing.subscribeTo(type); + return new SubscriptionRequest(type, this, this._subscribing.requestFactory); } /** @@ -93,8 +93,8 @@ export class CompositeClient extends Client { /** * @override */ - command(command) { - return this._commanding.command(command); + command(commandMessage) { + return new CommandRequest(commandMessage, this, this._commanding.requestFactory); } /** @@ -120,14 +120,6 @@ export class QueryingClient { this.requestFactory = actorRequestFactory; } - /** - * @param {!Class} entityType a Protobuf type of the query target entities - * @return {QueryRequest} - */ - select(entityType) { - return new QueryRequest(entityType, this); - } - /** * Executes the given `Query` instance specifying the data to be retrieved from * Spine server fulfilling a returned promise with an array of received objects. @@ -159,10 +151,6 @@ export class SubscribingClient { this.requestFactory = actorRequestFactory; } - subscribeTo(type) { - return new SubscriptionRequest(type, this); - } - /** * @return {Promise>} * @@ -217,10 +205,6 @@ export class CommandingClient { this._endpoint = endpoint; } - command(commandMessage) { - return new CommandRequest(commandMessage, this); - } - post(command, ackCallback) { const cmd = TypedMessage.of(command); this._endpoint.command(cmd) From e919478cf6a57ad5ebe5cacbbf2e5de58b4d389d Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Sun, 1 Dec 2019 18:44:08 +0200 Subject: [PATCH 07/70] Delete `SimpleTarget` type which is now unused --- client-js/main/client/client.js | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index 248e5caec..397d59b9f 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -52,25 +52,6 @@ import {Observable} from 'rxjs'; * @template a type of the subscription target entities */ -/** - * @typedef {Object} SimpleTarget - * - * An object representing a set of parameters for building a query or a subscription - * topic specifying only a type and identifiers of the target entities. - * - * Target built from this object point either: - * - a single entity of a given type with a given ID; - * - several entities of a given type with given IDs; - * - all entities of a given type if no IDs specified; - * - * @property {!Class} entity a class of target entities - * @property {?[] | | Number[] | Number | String[] | String} byIds - * a list of target entities IDs or an ID of a single target entity - * - * @template a class of a query or subscription target entities - * @template a class of a query or subscription target entities identifiers - */ - /** * @typedef AckCallback * @@ -130,17 +111,16 @@ export class Client { } /** - * @param {!Message} command a Protobuf type of the query target entities + * @param {!Message} commandMessage a Protobuf type of the query target entities * @return {CommandRequest} */ - command(command) { + command(commandMessage) { throw new Error('Not implemented in abstract base.'); } /** - * @param {!Command} command a Command send to Spine server + * @param {!spine.core.Command} command a Command send to Spine server * @param {!AckCallback} ackCallback - * @param {!Array>} observedTypes */ post(command, ackCallback) { throw new Error('Not implemented in abstract base.'); From c2e61ba62625671c51ea3341ff665b8a7098a452 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Sun, 1 Dec 2019 19:45:03 +0200 Subject: [PATCH 08/70] Move request creation to the corresponding clients --- client-js/main/client/composite-client.js | 24 +++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index d5f82465a..fd3326980 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -66,7 +66,7 @@ export class CompositeClient extends Client { * @override */ select(entityType) { - return new QueryRequest(entityType, this, this._querying.requestFactory); + return this._querying.select(entityType, this); } /** @@ -80,7 +80,7 @@ export class CompositeClient extends Client { * @override */ subscribeTo(type) { - return new SubscriptionRequest(type, this, this._subscribing.requestFactory); + return this._subscribing.subscribeTo(type, this); } /** @@ -94,7 +94,7 @@ export class CompositeClient extends Client { * @override */ command(commandMessage) { - return new CommandRequest(commandMessage, this, this._commanding.requestFactory); + return this._commanding.command(commandMessage, this); } /** @@ -117,7 +117,11 @@ export class QueryingClient { * a request factory to build requests to Spine server */ constructor(actorRequestFactory) { - this.requestFactory = actorRequestFactory; + this._requestFactory = actorRequestFactory; + } + + select(entityType, client) { + return new QueryRequest(entityType, client, this._requestFactory); } /** @@ -148,7 +152,11 @@ export class SubscribingClient { * a request factory to build requests to Spine server */ constructor(actorRequestFactory) { - this.requestFactory = actorRequestFactory; + this._requestFactory = actorRequestFactory; + } + + subscribeTo(type, client) { + return new SubscriptionRequest(type, client, this._requestFactory); } /** @@ -201,10 +209,14 @@ const _statusType = Status.typeUrl(); export class CommandingClient { constructor(endpoint, requestFactory) { - this.requestFactory = requestFactory; + this._requestFactory = requestFactory; this._endpoint = endpoint; } + command(commandMessage, client) { + return new CommandRequest(commandMessage, client, this._requestFactory); + } + post(command, ackCallback) { const cmd = TypedMessage.of(command); this._endpoint.command(cmd) From 98043cfe12e498d45c3f9413e0a4c94b1f78fcba Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 15:17:48 +0200 Subject: [PATCH 09/70] Split entity and event subscriptions Event subscription now returns an `EventSubscriptionObject`. The `EventSubscriptionObject` does not have redundant `itemChanged` and `itemRemoved` callbacks and instead holds a single observable which is named `eventEmitted` and is of `Observable` type. The `SubscriptionBridge` now writes `Event` instances to the Firebase database instead of event messages to allow this behavior. --- client-js/main/client/client-request.js | 49 +++++- client-js/main/client/client.js | 28 ++- client-js/main/client/composite-client.js | 54 +++++- client-js/main/client/firebase-client.js | 164 +++++++++++++----- .../client/firebase-subscription-service.js | 7 +- .../firebase/subscription/UpdatePayload.java | 2 +- 6 files changed, 243 insertions(+), 61 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 9cca8cf0c..8a1293d4b 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -27,6 +27,9 @@ import {MessageId, Origin} from '../proto/spine/core/diagnostics_pb'; import {AnyPacker} from "./any-packer"; import {Filters} from "./actor-request-factory"; +/** + * @abstract + */ class ClientRequest { /** @@ -39,6 +42,9 @@ class ClientRequest { } } +/** + * @abstract + */ class FilteringRequest extends ClientRequest { /** @@ -159,11 +165,10 @@ export class QueryRequest extends FilteringRequest { } } -export class SubscriptionRequest extends FilteringRequest { - - constructor(targetType, client, actorRequestFactory) { - super(targetType, client, actorRequestFactory) - } +/** + * @abstract + */ +class SubscribingRequest extends FilteringRequest { /** * @return {Promise>} @@ -172,7 +177,7 @@ export class SubscriptionRequest extends FilteringRequest { */ post() { const topic = this.builder().build(); - return this.client.subscribe(topic); + return this.doSubscribe(topic); } newBuilderFn() { @@ -182,6 +187,38 @@ export class SubscriptionRequest extends FilteringRequest { self() { return this; } + + doSubscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } +} + +export class SubscriptionRequest extends SubscribingRequest { + + constructor(entityType, client, actorRequestFactory) { + super(entityType, client, actorRequestFactory) + } + + /** + * @override + */ + doSubscribe(topic) { + return this.client.subscribe(topic); + } +} + +export class EventSubscriptionRequest extends SubscribingRequest { + + constructor(eventType, client, actorRequestFactory) { + super(eventType, client, actorRequestFactory) + } + + /** + * @override + */ + doSubscribe(topic) { + return this.client.subscribeToEvents(topic); + } } const NOOP_CALLBACK = () => {}; diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index 397d59b9f..f0f97e1aa 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -52,6 +52,13 @@ import {Observable} from 'rxjs'; * @template a type of the subscription target entities */ +/** + * @typedef {Object} EventSubscriptionObject + * + * @property > eventEmitted + * @property {!parameterlessCallback} unsubscribe + */ + /** * @typedef AckCallback * @@ -92,10 +99,18 @@ export class Client { } /** - * @param {!Class} type a Protobuf type of the target entities or events + * @param {!Class} entityType a Protobuf type of the target entities * @return {SubscriptionRequest} */ - subscribeTo(type) { + subscribeTo(entityType) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * @param {!Class} eventType a Protobuf type of the target events + * @return {EventSubscriptionRequest} + */ + subscribeToEvent(eventType) { throw new Error('Not implemented in abstract base.'); } @@ -110,6 +125,15 @@ export class Client { throw new Error('Not implemented in abstract base.'); } + /** + * @param {!spine.client.Topic} topic + * + * @return {Promise} + */ + subscribeToEvents(topic) { + throw new Error('Not implemented in abstract base.'); + } + /** * @param {!Message} commandMessage a Protobuf type of the query target entities * @return {CommandRequest} diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index fd3326980..9ce0e5f1c 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -25,7 +25,12 @@ import ObjectToProto from "./object-to-proto"; import {CommandHandlingError, CommandValidationError, SpineError} from "./errors"; import {Status} from '../proto/spine/core/response_pb'; import {Client} from "./client"; -import {CommandRequest, QueryRequest, SubscriptionRequest} from "./client-request"; +import { + CommandRequest, + EventSubscriptionRequest, + QueryRequest, + SubscriptionRequest +} from "./client-request"; import {TypedMessage} from "./typed-message"; /** @@ -79,8 +84,15 @@ export class CompositeClient extends Client { /** * @override */ - subscribeTo(type) { - return this._subscribing.subscribeTo(type, this); + subscribeTo(entityType) { + return this._subscribing.subscribeTo(entityType, this); + } + + /** + * @override + */ + subscribeToEvent(eventType) { + return this._subscribing.subscribeToEvent(eventType, this); } /** @@ -90,6 +102,13 @@ export class CompositeClient extends Client { return this._subscribing.subscribe(topic) } + /** + * @override + */ + subscribeToEvents(topic) { + return this._subscribing.subscribeToEvents(topic); + } + /** * @override */ @@ -159,6 +178,10 @@ export class SubscribingClient { return new SubscriptionRequest(type, client, this._requestFactory); } + subscribeToEvent(type, client) { + return new EventSubscriptionRequest(type, client, this._requestFactory); + } + /** * @return {Promise>} * @@ -167,6 +190,13 @@ export class SubscribingClient { subscribe(topic) { throw new Error('Not implemented in abstract base.'); } + + /** + * @return {Promise} + */ + subscribeToEvents(topic) { + throw new Error('Not implemented in abstract base.'); + } } const SUBSCRIPTIONS_NOT_SUPPORTED = 'Subscriptions are not supported.'; @@ -189,6 +219,15 @@ export class NoOpSubscribingClient extends SubscribingClient { throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); } + /** + * Always throws an error. + * + * @override + */ + subscribeToEvent(type, client) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } + /** * Always throws an error. * @@ -197,6 +236,15 @@ export class NoOpSubscribingClient extends SubscribingClient { subscribe(topic) { throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); } + + /** + * Always throws an error. + * + * @override + */ + subscribeToEvents(topic) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } } const _statusType = Status.typeUrl(); diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 9e5e9f46b..42fb6074f 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -22,7 +22,7 @@ import {Observable, Subject, Subscription} from 'rxjs'; import { - Subscription as SpineSubscription, + Subscription as SubscriptionObject, SubscriptionId } from '../proto/spine/client/subscription_pb'; import {AbstractClientFactory} from './client-factory'; @@ -40,29 +40,19 @@ import { } from "./composite-client"; /** - * A subscription to entity changes on application backend. + * @abstract */ -class EntitySubscription extends Subscription { +class SpineSubscription extends Subscription { - /** - * @param {Function} unsubscribe - * @param {{itemAdded: Observable, itemChanged: Observable, itemRemoved: Observable}} observables - * @param {SpineSubscription} subscription - */ - constructor({ - unsubscribedBy: unsubscribe, - withObservables: observables, - forInternal: subscription - }) { + constructor(unsubscribe, subscription) { super(unsubscribe); - this._observables = observables; this._subscription = subscription; } /** * An internal Spine subscription which includes the topic the updates are received for. * - * @return {SpineSubscription} a `spine.client.Subscription` instance + * @return {SubscriptionObject} a `spine.client.Subscription` instance */ internal() { return this._subscription; @@ -74,6 +64,26 @@ class EntitySubscription extends Subscription { id() { return this.internal().getId().getValue(); } +} + +/** + * A subscription to entity changes on application backend. + */ +class EntitySubscription extends SpineSubscription { + + /** + * @param {Function} unsubscribe + * @param {{itemAdded: Observable, itemChanged: Observable, itemRemoved: Observable}} observables + * @param {SubscriptionObject} subscription + */ + constructor({ + unsubscribedBy: unsubscribe, + withObservables: observables, + forInternal: subscription + }) { + super(unsubscribe, subscription); + this._observables = observables; + } /** * @return {EntitySubscriptionObject} a plain object with observables and unsubscribe method @@ -83,6 +93,32 @@ class EntitySubscription extends Subscription { } } +class EventSubscription extends SpineSubscription { + + /** + * @param {Function} unsubscribe + * @param {Observable} eventEmitted + * @param {SubscriptionObject} subscription + */ + constructor({ + unsubscribedBy: unsubscribe, + withObservable: observable, + forInternal: subscription + }) { + super(unsubscribe, subscription); + this._observable = observable; + } + + /** + * @return {EventSubscriptionObject} a plain object with observables and unsubscribe method + */ + toObject() { + return Object.assign( + {}, {eventEmitted: this._observable}, {unsubscribe: () => this.unsubscribe()} + ); + } +} + class FirebaseQueryingClient extends QueryingClient { /** @@ -111,6 +147,8 @@ class FirebaseQueryingClient extends QueryingClient { } } +const EVENT_TYPE_URL = 'type.spine.io/spine.core.Event'; + /** * A {@link SubscribingClient} which receives entity state updates and events from * a Firebase Realtime Database. @@ -140,45 +178,79 @@ class FirebaseSubscribingClient extends SubscribingClient { * @inheritDoc */ subscribe(topic) { + return this._doSubscribe(topic, this._entitySubscription); + } + + /** + * @inheritDoc + */ + subscribeToEvents(topic) { + return this._doSubscribe(topic, this._eventSubscription); + } + + /** + * @private + */ + _doSubscribe(topic, createSubscriptionFn) { return new Promise((resolve, reject) => { - const typeUrl = topic.getTarget().getType(); this._endpoint.subscribeTo(topic) .then(response => { const path = response.nodePath.value; - - const itemAdded = new Subject(); - const itemChanged = new Subject(); - const itemRemoved = new Subject(); - - const pathSubscriptions = [ - this._firebase - .onChildAdded(path, itemAdded.next.bind(itemAdded)), - this._firebase - .onChildChanged(path, itemChanged.next.bind(itemChanged)), - this._firebase - .onChildRemoved(path, itemRemoved.next.bind(itemRemoved)) - ]; - const internalSubscription = FirebaseSubscribingClient.internalSubscription(path, topic); - const entitySubscription = new EntitySubscription({ - unsubscribedBy: () => { - FirebaseSubscribingClient._unsubscribe(pathSubscriptions); - }, - withObservables: { - itemAdded: ObjectToProto.map(itemAdded.asObservable(), typeUrl), - itemChanged: ObjectToProto.map(itemChanged.asObservable(), typeUrl), - itemRemoved: ObjectToProto.map(itemRemoved.asObservable(), typeUrl) - }, - forInternal: internalSubscription - }); - resolve(entitySubscription.toObject()); - this._subscriptionService.add(entitySubscription); + + const subscription = createSubscriptionFn.apply(path, internalSubscription); + + resolve(subscription.toObject()); + this._subscriptionService.add(subscription); }) .catch(reject); }); } + _entitySubscription(path, subscription) { + const itemAdded = new Subject(); + const itemChanged = new Subject(); + const itemRemoved = new Subject(); + + const pathSubscriptions = [ + this._firebase + .onChildAdded(path, itemAdded.next.bind(itemAdded)), + this._firebase + .onChildChanged(path, itemChanged.next.bind(itemChanged)), + this._firebase + .onChildRemoved(path, itemRemoved.next.bind(itemRemoved)) + ]; + + const typeUrl = subscription.getTopic().getTarget().getType(); + + return new EntitySubscription({ + unsubscribedBy: () => { + FirebaseSubscribingClient._unsubscribe(pathSubscriptions); + }, + withObservables: { + itemAdded: ObjectToProto.map(itemAdded.asObservable(), typeUrl), + itemChanged: ObjectToProto.map(itemChanged.asObservable(), typeUrl), + itemRemoved: ObjectToProto.map(itemRemoved.asObservable(), typeUrl) + }, + forInternal: subscription + }); + } + + _eventSubscription(path, subscription) { + const itemAdded = new Subject(); + const pathSubscription = + this._firebase.onChildAdded(path, itemAdded.next.bind(itemAdded)); + + return new EventSubscription({ + unsubscribedBy: () => { + FirebaseSubscribingClient._unsubscribe([pathSubscription]); + }, + withObservable: ObjectToProto.map(itemAdded.asObservable(), EVENT_TYPE_URL), + forInternal: subscription + }); + } + /** * Unsubscribes the provided Firebase subscriptions. * @@ -194,14 +266,14 @@ class FirebaseSubscribingClient extends SubscribingClient { } /** - * Creates a `SpineSubscription` instance to communicate with Spine server. + * Creates a `SubscriptionObject` instance to communicate with Spine server. * * @param {!String} path a path to object which gets updated in Firebase * @param {!spine.client.Topic} topic a topic for which the Subscription gets updates - * @return {SpineSubscription} a `SpineSubscription` instance to communicate with Spine server + * @return {SubscriptionObject} a `SubscriptionObject` instance to communicate with Spine server */ static internalSubscription(path, topic) { - const subscription = new SpineSubscription(); + const subscription = new SubscriptionObject(); const id = new SubscriptionId(); id.setValue(path); subscription.setId(id); diff --git a/client-js/main/client/firebase-subscription-service.js b/client-js/main/client/firebase-subscription-service.js index aabc4fc3e..df3b19b5e 100644 --- a/client-js/main/client/firebase-subscription-service.js +++ b/client-js/main/client/firebase-subscription-service.js @@ -25,7 +25,8 @@ import {Duration} from './time-utils'; const SUBSCRIPTION_KEEP_UP_INTERVAL = new Duration({minutes: 2}); /** - * A service that manages the subscriptions periodically sending requests to keep them running. + * A service that manages the active subscriptions periodically sending requests to keep them + * running. */ export class FirebaseSubscriptionService { /** @@ -33,7 +34,7 @@ export class FirebaseSubscriptionService { */ constructor(endpoint) { /** - * @type {EntitySubscription[]} + * @type {SpineSubscription[]} * @private */ this._subscriptions = []; @@ -48,7 +49,7 @@ export class FirebaseSubscriptionService { * Add a subscription to the service to handle the keep-up requests and cancel in * case of unsubscribe. * - * @param {EntitySubscription} subscription an entity subscription to keep running + * @param {SpineSubscription} subscription an active subscription to keep running */ add(subscription) { if (this._isRegistered(subscription)) { diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java index 60881d774..623fae127 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java @@ -99,7 +99,7 @@ private static UpdatePayload eventUpdates(SubscriptionUpdate update) { .getEventUpdates() .getEventList() .stream() - .collect(toHashTable(Event::id, Event::enclosedMessage)); + .collect(toHashTable(Event::id, event -> event)); return new UpdatePayload(messages); } From 8c6b0e84d84157a2de0375e8ae801b8749834b66 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 15:34:47 +0200 Subject: [PATCH 10/70] Return `EventSubscriptionObject` from command request --- client-js/main/client/client-request.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 8a1293d4b..ccee2fe18 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -280,7 +280,7 @@ export class CommandRequest extends ClientRequest{ } /** - * @return {Promise} + * @return {Promise} */ post() { const command = this.actorRequestFactory.command().create(this._commandMessage); @@ -290,7 +290,7 @@ export class CommandRequest extends ClientRequest{ const promises = []; this._observedTypes.forEach(type => { const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); - const promise = this.client.subscribeTo(type) + const promise = this.client.subscribeToEvent(type) .where([originFilter]) .post(); promises.push(promise); From 15079bbf5eaf56e637be543c88d940617cff2fa5 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 16:19:21 +0200 Subject: [PATCH 11/70] Update doc --- client-js/main/client/actor-request-factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-js/main/client/actor-request-factory.js b/client-js/main/client/actor-request-factory.js index d7ab18ea2..9f955c95d 100644 --- a/client-js/main/client/actor-request-factory.js +++ b/client-js/main/client/actor-request-factory.js @@ -814,7 +814,7 @@ class CommandFactory { * Creates a `Command` from the given command message. * * @param {!Message} message a command message - * @return {Command} a typed representation of the Spine Command + * @return {Command} a Spine Command */ create(message) { const id = CommandFactory._newCommandId(); From 35ef6be78e086bca7ff89c311d6cf327793e66a7 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 18:18:44 +0200 Subject: [PATCH 12/70] Improve doc of the methods and classes --- client-js/main/client/client-request.js | 64 ++++++++++++++++++++---- client-js/main/client/client.js | 2 +- client-js/main/client/firebase-client.js | 6 +++ client-js/main/client/http-endpoint.js | 6 +-- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index ccee2fe18..f2c36118a 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -23,6 +23,7 @@ import {Message} from 'google-protobuf'; import {CompositeFilter, Filter} from '../proto/spine/client/filters_pb'; import {OrderBy} from '../proto/spine/client/query_pb'; +import {Command} from '../proto/spine/core/command_pb'; import {MessageId, Origin} from '../proto/spine/core/diagnostics_pb'; import {AnyPacker} from "./any-packer"; import {Filters} from "./actor-request-factory"; @@ -86,6 +87,9 @@ class FilteringRequest extends ClientRequest { return this.self(); } + /** + * @return {AbstractTargetBuilder} + */ builder() { // TODO:2019-11-27:dmytro.kuzmin:WIP Check that setting to some initial value is // unnecessary. @@ -98,7 +102,9 @@ class FilteringRequest extends ClientRequest { /** * @abstract * - * @return {Function} + * @return {Function} + * + * @template */ newBuilderFn() { throw new Error('Not implemented in abstract base.'); @@ -120,8 +126,6 @@ export class QueryRequest extends FilteringRequest { super(targetType, client, actorRequestFactory) } - // TODO:2019-11-27:dmytro.kuzmin:WIP See what we can do about it. - // noinspection JSValidateJSDoc unresolved nested type which actually exists /** * * @param {!String} column @@ -156,10 +160,20 @@ export class QueryRequest extends FilteringRequest { return this.client.read(query); } + /** + * @inheritDoc + * + * @return {Function} + */ newBuilderFn() { return requestFactory => requestFactory.query().select(this.targetType); } + /** + * @inheritDoc + * + * @return {QueryRequest} + */ self() { return this; } @@ -171,7 +185,7 @@ export class QueryRequest extends FilteringRequest { class SubscribingRequest extends FilteringRequest { /** - * @return {Promise>} + * @return {Promise | EventSubscriptionObject>} * * @template a Protobuf type of entities being the target of a subscription */ @@ -180,14 +194,20 @@ class SubscribingRequest extends FilteringRequest { return this.doSubscribe(topic); } + /** + * @inheritDoc + * + * @return {Function} + */ newBuilderFn() { return requestFactory => requestFactory.topic().select(this.targetType); } - self() { - return this; - } - + /** + * @return {Promise | EventSubscriptionObject>} + * + * @template a Protobuf type of entities being the target of a subscription + */ doSubscribe(topic) { throw new Error('Not implemented in abstract base.'); } @@ -200,11 +220,24 @@ export class SubscriptionRequest extends SubscribingRequest { } /** - * @override + * @inheritDoc + * + * @return {Promise>} + * + * @template */ doSubscribe(topic) { return this.client.subscribe(topic); } + + /** + * @inheritDoc + * + * @return {SubscriptionRequest} + */ + self() { + return this; + } } export class EventSubscriptionRequest extends SubscribingRequest { @@ -214,11 +247,22 @@ export class EventSubscriptionRequest extends SubscribingRequest { } /** - * @override + * @inheritDoc + * + * @return {Promise} */ doSubscribe(topic) { return this.client.subscribeToEvents(topic); } + + /** + * @inheritDoc + * + * @return {EventSubscriptionRequest} + */ + self() { + return this; + } } const NOOP_CALLBACK = () => {}; diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index f0f97e1aa..a51835035 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -143,7 +143,7 @@ export class Client { } /** - * @param {!spine.core.Command} command a Command send to Spine server + * @param {!spine.core.Command} command a Command sent to Spine server * @param {!AckCallback} ackCallback */ post(command, ackCallback) { diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 42fb6074f..3f915b624 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -208,6 +208,9 @@ class FirebaseSubscribingClient extends SubscribingClient { }); } + /** + * @private + */ _entitySubscription(path, subscription) { const itemAdded = new Subject(); const itemChanged = new Subject(); @@ -237,6 +240,9 @@ class FirebaseSubscribingClient extends SubscribingClient { }); } + /** + * @private + */ _eventSubscription(path, subscription) { const itemAdded = new Subject(); const pathSubscription = diff --git a/client-js/main/client/http-endpoint.js b/client-js/main/client/http-endpoint.js index 0d08f404a..733b66c8d 100644 --- a/client-js/main/client/http-endpoint.js +++ b/client-js/main/client/http-endpoint.js @@ -50,7 +50,7 @@ class Endpoint { /** * Sends off a command to the endpoint. * - * @param {!TypedMessage} command a Command send to Spine server + * @param {!TypedMessage} command a Command sent to Spine server * @return {Promise} a promise of a successful server response, rejected if * an error occurs */ @@ -110,7 +110,7 @@ class Endpoint { /** - * @param {!TypedMessage} command a Command send to Spine server + * @param {!TypedMessage} command a Command sent to Spine server * @return {Promise} a promise of a successful server response, rejected if * an error occurs * @protected @@ -184,7 +184,7 @@ export class HttpEndpoint extends Endpoint { /** * Sends off a command to the endpoint. * - * @param {!TypedMessage} command a Command send to Spine server + * @param {!TypedMessage} command a Command sent to Spine server * @return {Promise} a promise of a successful server response JSON data, rejected if * the client response is not 2xx or a connection error occurs * @protected From b5c76fafb2eb2c8d672fae4b56f1ed325ec03b0f Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 19:03:02 +0200 Subject: [PATCH 13/70] Fix declared return types of the methods --- client-js/main/client/client-request.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index f2c36118a..9a662e4fc 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -60,7 +60,7 @@ class FilteringRequest extends ClientRequest { /** * @param ids {![]|Number[]|String[]} - * @return {FilteringRequest} self + * @return {this} self for method chaining * * @template a Protobuf type of IDs */ @@ -71,7 +71,7 @@ class FilteringRequest extends ClientRequest { /** * @param {!Filter[]|CompositeFilter[]} predicates - * @return {FilteringRequest} self + * @return {this} self for method chaining */ where(predicates) { this.builder().where(predicates); @@ -80,7 +80,7 @@ class FilteringRequest extends ClientRequest { /** * @param {!String[]} fieldNames - * @return {FilteringRequest} self + * @return {this} self for method chaining */ withMask(fieldNames) { this.builder().withMask(fieldNames); @@ -88,7 +88,9 @@ class FilteringRequest extends ClientRequest { } /** - * @return {AbstractTargetBuilder} + * @return {AbstractTargetBuilder} + * + * @template */ builder() { // TODO:2019-11-27:dmytro.kuzmin:WIP Check that setting to some initial value is @@ -113,7 +115,7 @@ class FilteringRequest extends ClientRequest { /** * @abstract * - * @return {FilteringRequest} self + * @return {this} */ self() { throw new Error('Not implemented in abstract base.'); From 4d588e1748215550689e4cf37a8f379649944593 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 19:29:18 +0200 Subject: [PATCH 14/70] Limit method visibility to `@protected` where appropriate --- client-js/main/client/client-request.js | 70 ++++++++++++++---------- client-js/main/client/firebase-client.js | 3 + 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 9a662e4fc..d2fd2ce47 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -36,6 +36,8 @@ class ClientRequest { /** * @param {!Client} client * @param {!ActorRequestFactory} actorRequestFactory + * + * @protected */ constructor(client, actorRequestFactory) { this.client = client; @@ -52,6 +54,8 @@ class FilteringRequest extends ClientRequest { * @param {!Class} targetType * @param {!Client} client * @param {!ActorRequestFactory} actorRequestFactory + * + * @protected */ constructor(targetType, client, actorRequestFactory) { super(client, actorRequestFactory); @@ -65,8 +69,8 @@ class FilteringRequest extends ClientRequest { * @template a Protobuf type of IDs */ byId(ids) { - this.builder().byIds(ids); - return this.self(); + this._builder().byIds(ids); + return this._self(); } /** @@ -74,8 +78,8 @@ class FilteringRequest extends ClientRequest { * @return {this} self for method chaining */ where(predicates) { - this.builder().where(predicates); - return this.self(); + this._builder().where(predicates); + return this._self(); } /** @@ -83,22 +87,24 @@ class FilteringRequest extends ClientRequest { * @return {this} self for method chaining */ withMask(fieldNames) { - this.builder().withMask(fieldNames); - return this.self(); + this._builder().withMask(fieldNames); + return this._self(); } /** * @return {AbstractTargetBuilder} * * @template + * + * @protected */ - builder() { + _builder() { // TODO:2019-11-27:dmytro.kuzmin:WIP Check that setting to some initial value is // unnecessary. - if (!this._builder) { - this._builder = this.newBuilderFn().apply(this.actorRequestFactory); + if (!this._builderInstance) { + this._builderInstance = this._newBuilderFn().apply(this.actorRequestFactory); } - return this._builder; + return this._builderInstance; } /** @@ -107,8 +113,10 @@ class FilteringRequest extends ClientRequest { * @return {Function} * * @template + * + * @protected */ - newBuilderFn() { + _newBuilderFn() { throw new Error('Not implemented in abstract base.'); } @@ -116,8 +124,10 @@ class FilteringRequest extends ClientRequest { * @abstract * * @return {this} + * + * @protected */ - self() { + _self() { throw new Error('Not implemented in abstract base.'); } } @@ -136,11 +146,11 @@ export class QueryRequest extends FilteringRequest { */ orderBy(column, direction) { if (direction === OrderBy.Direction.ASCENDING) { - this.builder().orderAscendingBy(column); + this._builder().orderAscendingBy(column); } else { - this.builder().orderDescendingBy(column); + this._builder().orderDescendingBy(column); } - return this.self(); + return this._self(); } /** @@ -148,8 +158,8 @@ export class QueryRequest extends FilteringRequest { * @return {QueryRequest} self */ limit(count) { - this.builder().limit(count); - return this.self(); + this._builder().limit(count); + return this._self(); } /** @@ -158,7 +168,7 @@ export class QueryRequest extends FilteringRequest { * @template a Protobuf type of entities being the target of a query */ run() { - const query = this.builder().build(); + const query = this._builder().build(); return this.client.read(query); } @@ -167,7 +177,7 @@ export class QueryRequest extends FilteringRequest { * * @return {Function} */ - newBuilderFn() { + _newBuilderFn() { return requestFactory => requestFactory.query().select(this.targetType); } @@ -176,7 +186,7 @@ export class QueryRequest extends FilteringRequest { * * @return {QueryRequest} */ - self() { + _self() { return this; } } @@ -192,8 +202,8 @@ class SubscribingRequest extends FilteringRequest { * @template a Protobuf type of entities being the target of a subscription */ post() { - const topic = this.builder().build(); - return this.doSubscribe(topic); + const topic = this._builder().build(); + return this._subscribe(topic); } /** @@ -201,16 +211,20 @@ class SubscribingRequest extends FilteringRequest { * * @return {Function} */ - newBuilderFn() { + _newBuilderFn() { return requestFactory => requestFactory.topic().select(this.targetType); } /** + * @abstract + * * @return {Promise | EventSubscriptionObject>} * * @template a Protobuf type of entities being the target of a subscription + * + * @protected */ - doSubscribe(topic) { + _subscribe(topic) { throw new Error('Not implemented in abstract base.'); } } @@ -228,7 +242,7 @@ export class SubscriptionRequest extends SubscribingRequest { * * @template */ - doSubscribe(topic) { + _subscribe(topic) { return this.client.subscribe(topic); } @@ -237,7 +251,7 @@ export class SubscriptionRequest extends SubscribingRequest { * * @return {SubscriptionRequest} */ - self() { + _self() { return this; } } @@ -253,7 +267,7 @@ export class EventSubscriptionRequest extends SubscribingRequest { * * @return {Promise} */ - doSubscribe(topic) { + _subscribe(topic) { return this.client.subscribeToEvents(topic); } @@ -262,7 +276,7 @@ export class EventSubscriptionRequest extends SubscribingRequest { * * @return {EventSubscriptionRequest} */ - self() { + _self() { return this; } } diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 3f915b624..483598279 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -44,6 +44,9 @@ import { */ class SpineSubscription extends Subscription { + /** + * @protected + */ constructor(unsubscribe, subscription) { super(unsubscribe); this._subscription = subscription; From 6d4912b3b54b1b81abf071847b5f8f03dc209373 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 19:35:29 +0200 Subject: [PATCH 15/70] Extract method for identity conversion function --- .../web/firebase/subscription/UpdatePayload.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java index 623fae127..a5b5d07e9 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java @@ -99,7 +99,7 @@ private static UpdatePayload eventUpdates(SubscriptionUpdate update) { .getEventUpdates() .getEventList() .stream() - .collect(toHashTable(Event::id, event -> event)); + .collect(toHashTable(Event::id, identity())); return new UpdatePayload(messages); } @@ -139,4 +139,14 @@ NodeValue asNodeValue() { messages.forEach((id, message) -> node.addChild(id, StoredJson.encodeOrNull(message))); return node; } + + /** + * An identity function for {@link Event}-to-{@link Message} conversion. + * + *

The standard {@link Function#identity()} cannot be applied because of the type arguments + * mismatch. + */ + private static Function identity() { + return event -> event; + } } From 5c01e6d3e5255f7dc9eac84e5be7e619de6def6c Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 19:42:29 +0200 Subject: [PATCH 16/70] Fix fields visibility --- client-js/main/client/client-request.js | 30 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index d2fd2ce47..19104ea6f 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -35,13 +35,21 @@ class ClientRequest { /** * @param {!Client} client - * @param {!ActorRequestFactory} actorRequestFactory + * @param {!ActorRequestFactory} requestFactory * * @protected */ - constructor(client, actorRequestFactory) { - this.client = client; - this.actorRequestFactory = actorRequestFactory; + constructor(client, requestFactory) { + + /** + * @protected + */ + this._client = client; + + /** + * @protected + */ + this._requestFactory = requestFactory; } } @@ -102,7 +110,7 @@ class FilteringRequest extends ClientRequest { // TODO:2019-11-27:dmytro.kuzmin:WIP Check that setting to some initial value is // unnecessary. if (!this._builderInstance) { - this._builderInstance = this._newBuilderFn().apply(this.actorRequestFactory); + this._builderInstance = this._newBuilderFn().apply(this._requestFactory); } return this._builderInstance; } @@ -169,7 +177,7 @@ export class QueryRequest extends FilteringRequest { */ run() { const query = this._builder().build(); - return this.client.read(query); + return this._client.read(query); } /** @@ -243,7 +251,7 @@ export class SubscriptionRequest extends SubscribingRequest { * @template */ _subscribe(topic) { - return this.client.subscribe(topic); + return this._client.subscribe(topic); } /** @@ -268,7 +276,7 @@ export class EventSubscriptionRequest extends SubscribingRequest { * @return {Promise} */ _subscribe(topic) { - return this.client.subscribeToEvents(topic); + return this._client.subscribeToEvents(topic); } /** @@ -343,14 +351,14 @@ export class CommandRequest extends ClientRequest{ * @return {Promise} */ post() { - const command = this.actorRequestFactory.command().create(this._commandMessage); + const command = this._requestFactory.command().create(this._commandMessage); const ackCallback = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; - this.client.post(command, ackCallback); + this._client.post(command, ackCallback); const promises = []; this._observedTypes.forEach(type => { const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); - const promise = this.client.subscribeToEvent(type) + const promise = this._client.subscribeToEvent(type) .where([originFilter]) .post(); promises.push(promise); From 9abcd80b79df8ba83211e6afa27dabcb31177724 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 20:30:12 +0200 Subject: [PATCH 17/70] Remove `FirebaseClient` from the exported types --- client-js/main/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client-js/main/index.js b/client-js/main/index.js index c4b922177..cf65268b5 100644 --- a/client-js/main/index.js +++ b/client-js/main/index.js @@ -26,5 +26,4 @@ export {FirebaseDatabaseClient} from './client/firebase-database-client'; export {HttpClient} from './client/http-client'; export {Client} from './client/client'; export {init} from './client/spine'; -export {FirebaseClient} from './client/firebase-client' export * from './client/errors'; From 59d443f3eee3ff2686a580007c20bb0597912c2b Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 20:57:07 +0200 Subject: [PATCH 18/70] Handle non-`OK` server response on subscription keep-up --- .../client/firebase-subscription-service.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/client-js/main/client/firebase-subscription-service.js b/client-js/main/client/firebase-subscription-service.js index df3b19b5e..1f332b03c 100644 --- a/client-js/main/client/firebase-subscription-service.js +++ b/client-js/main/client/firebase-subscription-service.js @@ -21,6 +21,9 @@ "use strict"; import {Duration} from './time-utils'; +import ObjectToProto from "./object-to-proto"; +import {Status} from '../proto/spine/core/response_pb'; + const SUBSCRIPTION_KEEP_UP_INTERVAL = new Duration({minutes: 2}); @@ -71,6 +74,15 @@ export class FirebaseSubscriptionService { }, SUBSCRIPTION_KEEP_UP_INTERVAL.inMs()); } + /** + * Sends the "keep-up" request for all active subscriptions. + * + * The non-`OK` response status on this request means the subscription has already been canceled + * by the server, most likely due to a timeout. So, in such case, the subscription is removed + * from the list of active subscriptions. + * + * @private + */ _keepUpSubscriptions() { this._subscriptions.forEach(subscription => { const spineSubscription = subscription.internal(); @@ -79,7 +91,13 @@ export class FirebaseSubscriptionService { this._removeSubscription(subscription); }); } else { - this._endpoint.keepUpSubscription(spineSubscription); + this._endpoint.keepUpSubscription(spineSubscription).then(response => { + const responseStatus = response.status; + const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); + if (responseStatusProto.getStatusCase() !== Status.StatusCase.OK) { + this._removeSubscription(subscription) + } + }); } }); } From 82f11b76d43734af0b2c9541dfc4b185415e8fd9 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 2 Dec 2019 20:59:15 +0200 Subject: [PATCH 19/70] Reword doc --- client-js/main/client/firebase-subscription-service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client-js/main/client/firebase-subscription-service.js b/client-js/main/client/firebase-subscription-service.js index 1f332b03c..815a6e711 100644 --- a/client-js/main/client/firebase-subscription-service.js +++ b/client-js/main/client/firebase-subscription-service.js @@ -77,9 +77,9 @@ export class FirebaseSubscriptionService { /** * Sends the "keep-up" request for all active subscriptions. * - * The non-`OK` response status on this request means the subscription has already been canceled - * by the server, most likely due to a timeout. So, in such case, the subscription is removed - * from the list of active subscriptions. + * The non-`OK` response status means the subscription has already been canceled by the server, + * most likely due to a timeout. So, in such case, the subscription is removed from the list of + * active ones. * * @private */ From 07b18c98ee6ababcce253b51ec1b32bb1ac2308c Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 3 Dec 2019 13:15:47 +0200 Subject: [PATCH 20/70] Update Spine version to `1.2.6` --- client-js/package.json | 2 +- integration-tests/js-tests/package.json | 2 +- license-report.md | 350 +++++++++++------------- pom.xml | 14 +- version.gradle | 6 +- 5 files changed, 166 insertions(+), 208 deletions(-) diff --git a/client-js/package.json b/client-js/package.json index b3a3a8b13..2e483ded7 100644 --- a/client-js/package.json +++ b/client-js/package.json @@ -1,6 +1,6 @@ { "name": "spine-web", - "version": "1.2.1", + "version": "1.2.6", "license": "Apache-2.0", "description": "A JS client for interacting with Spine applications.", "homepage": "https://spine.io", diff --git a/integration-tests/js-tests/package.json b/integration-tests/js-tests/package.json index 0f238828f..381006284 100644 --- a/integration-tests/js-tests/package.json +++ b/integration-tests/js-tests/package.json @@ -1,6 +1,6 @@ { "name": "client-js-tests", - "version": "1.2.1", + "version": "1.2.6", "license": "Apache-2.0", "description": "Tests of a `spine-web` JS library against the Spine-based application.", "scripts": { diff --git a/license-report.md b/license-report.md index bcd1dc6dc..4c80aefef 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client-js:1.2.1` +# Dependencies of `io.spine:spine-client-js:1.2.6` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -376,10 +376,10 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Tue Dec 03 12:59:56 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -#NPM dependencies of `spine-web@1.2.1` +#NPM dependencies of `spine-web@1.2.6` ## `Production` dependencies: @@ -389,7 +389,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **encoding@0.1.12** * Licenses: MIT * Repository: [https://github.com/andris9/encoding](https://github.com/andris9/encoding) -1. **google-protobuf@3.10.0** +1. **google-protobuf@3.9.1** * Licenses: BSD-3-Clause * Repository: [https://github.com/protocolbuffers/protobuf/tree/master/js](https://github.com/protocolbuffers/protobuf/tree/master/js) 1. **iconv-lite@0.4.24** @@ -404,19 +404,19 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **node-fetch@1.7.3** * Licenses: MIT * Repository: [https://github.com/bitinn/node-fetch](https://github.com/bitinn/node-fetch) -1. **rxjs@6.5.3** +1. **rxjs@6.5.2** * Licenses: Apache-2.0 * Repository: [https://github.com/reactivex/rxjs](https://github.com/reactivex/rxjs) 1. **safer-buffer@2.1.2** * Licenses: MIT * Repository: [https://github.com/ChALkeR/safer-buffer](https://github.com/ChALkeR/safer-buffer) -1. **spine-web@1.2.1** +1. **spine-web@1.2.6** * Licenses: Apache-2.0 * Repository: [https://github.com/SpineEventEngine/web](https://github.com/SpineEventEngine/web) 1. **tslib@1.10.0** * Licenses: Apache-2.0 * Repository: [https://github.com/Microsoft/tslib](https://github.com/Microsoft/tslib) -1. **uuid@3.3.3** +1. **uuid@3.3.2** * Licenses: MIT * Repository: [https://github.com/kelektiv/node-uuid](https://github.com/kelektiv/node-uuid) 1. **whatwg-fetch@3.0.0** @@ -427,55 +427,52 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice ## `Development` dependencies: -1. **@babel/cli@7.7.0** +1. **@babel/cli@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-cli](https://github.com/babel/babel/tree/master/packages/babel-cli) 1. **@babel/code-frame@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-code-frame](https://github.com/babel/babel/tree/master/packages/babel-code-frame) -1. **@babel/core@7.7.2** +1. **@babel/core@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-core](https://github.com/babel/babel/tree/master/packages/babel-core) -1. **@babel/generator@7.7.2** +1. **@babel/generator@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-generator](https://github.com/babel/babel/tree/master/packages/babel-generator) -1. **@babel/helper-annotate-as-pure@7.7.0** +1. **@babel/helper-annotate-as-pure@7.0.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-annotate-as-pure](https://github.com/babel/babel/tree/master/packages/babel-helper-annotate-as-pure) -1. **@babel/helper-builder-binary-assignment-operator-visitor@7.7.0** +1. **@babel/helper-builder-binary-assignment-operator-visitor@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-builder-binary-assignment-operator-visitor](https://github.com/babel/babel/tree/master/packages/babel-helper-builder-binary-assignment-operator-visitor) -1. **@babel/helper-call-delegate@7.7.0** +1. **@babel/helper-call-delegate@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-call-delegate](https://github.com/babel/babel/tree/master/packages/babel-helper-call-delegate) -1. **@babel/helper-create-regexp-features-plugin@7.7.2** - * Licenses: MIT - * Repository: [https://github.com/babel/babel](https://github.com/babel/babel) -1. **@babel/helper-define-map@7.7.0** +1. **@babel/helper-define-map@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-define-map](https://github.com/babel/babel/tree/master/packages/babel-helper-define-map) -1. **@babel/helper-explode-assignable-expression@7.7.0** +1. **@babel/helper-explode-assignable-expression@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-explode-assignable-expression](https://github.com/babel/babel/tree/master/packages/babel-helper-explode-assignable-expression) -1. **@babel/helper-function-name@7.7.0** +1. **@babel/helper-function-name@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-function-name](https://github.com/babel/babel/tree/master/packages/babel-helper-function-name) -1. **@babel/helper-get-function-arity@7.7.0** +1. **@babel/helper-get-function-arity@7.0.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-get-function-arity](https://github.com/babel/babel/tree/master/packages/babel-helper-get-function-arity) -1. **@babel/helper-hoist-variables@7.7.0** +1. **@babel/helper-hoist-variables@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-hoist-variables](https://github.com/babel/babel/tree/master/packages/babel-helper-hoist-variables) -1. **@babel/helper-member-expression-to-functions@7.7.0** +1. **@babel/helper-member-expression-to-functions@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-member-expression-to-functions](https://github.com/babel/babel/tree/master/packages/babel-helper-member-expression-to-functions) -1. **@babel/helper-module-imports@7.7.0** +1. **@babel/helper-module-imports@7.0.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-module-imports](https://github.com/babel/babel/tree/master/packages/babel-helper-module-imports) -1. **@babel/helper-module-transforms@7.7.0** +1. **@babel/helper-module-transforms@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-module-transforms](https://github.com/babel/babel/tree/master/packages/babel-helper-module-transforms) -1. **@babel/helper-optimise-call-expression@7.7.0** +1. **@babel/helper-optimise-call-expression@7.0.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-optimise-call-expression](https://github.com/babel/babel/tree/master/packages/babel-helper-optimise-call-expression) 1. **@babel/helper-plugin-utils@7.0.0** @@ -484,46 +481,46 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/helper-regex@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-regex](https://github.com/babel/babel/tree/master/packages/babel-helper-regex) -1. **@babel/helper-remap-async-to-generator@7.7.0** +1. **@babel/helper-remap-async-to-generator@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-remap-async-to-generator](https://github.com/babel/babel/tree/master/packages/babel-helper-remap-async-to-generator) -1. **@babel/helper-replace-supers@7.7.0** +1. **@babel/helper-replace-supers@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-replace-supers](https://github.com/babel/babel/tree/master/packages/babel-helper-replace-supers) -1. **@babel/helper-simple-access@7.7.0** +1. **@babel/helper-simple-access@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-simple-access](https://github.com/babel/babel/tree/master/packages/babel-helper-simple-access) -1. **@babel/helper-split-export-declaration@7.7.0** +1. **@babel/helper-split-export-declaration@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-split-export-declaration](https://github.com/babel/babel/tree/master/packages/babel-helper-split-export-declaration) -1. **@babel/helper-wrap-function@7.7.0** +1. **@babel/helper-wrap-function@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-wrap-function](https://github.com/babel/babel/tree/master/packages/babel-helper-wrap-function) -1. **@babel/helpers@7.7.0** +1. **@babel/helpers@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helpers](https://github.com/babel/babel/tree/master/packages/babel-helpers) 1. **@babel/highlight@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-highlight](https://github.com/babel/babel/tree/master/packages/babel-highlight) -1. **@babel/parser@7.7.3** +1. **@babel/parser@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-parser](https://github.com/babel/babel/tree/master/packages/babel-parser) -1. **@babel/plugin-proposal-async-generator-functions@7.7.0** +1. **@babel/plugin-proposal-async-generator-functions@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-async-generator-functions](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-async-generator-functions) -1. **@babel/plugin-proposal-dynamic-import@7.7.0** +1. **@babel/plugin-proposal-dynamic-import@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-dynamic-import](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-dynamic-import) 1. **@babel/plugin-proposal-json-strings@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-json-strings](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-json-strings) -1. **@babel/plugin-proposal-object-rest-spread@7.6.2** +1. **@babel/plugin-proposal-object-rest-spread@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-object-rest-spread](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-object-rest-spread) 1. **@babel/plugin-proposal-optional-catch-binding@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-optional-catch-binding](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-optional-catch-binding) -1. **@babel/plugin-proposal-unicode-property-regex@7.7.0** +1. **@babel/plugin-proposal-unicode-property-regex@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-unicode-property-regex](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-unicode-property-regex) 1. **@babel/plugin-syntax-async-generators@7.2.0** @@ -541,31 +538,28 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-syntax-optional-catch-binding@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-optional-catch-binding](https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-optional-catch-binding) -1. **@babel/plugin-syntax-top-level-await@7.7.0** - * Licenses: MIT - * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-top-level-await](https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-top-level-await) 1. **@babel/plugin-transform-arrow-functions@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-arrow-functions](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-arrow-functions) -1. **@babel/plugin-transform-async-to-generator@7.7.0** +1. **@babel/plugin-transform-async-to-generator@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-to-generator](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-to-generator) 1. **@babel/plugin-transform-block-scoped-functions@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-block-scoped-functions](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-block-scoped-functions) -1. **@babel/plugin-transform-block-scoping@7.6.3** +1. **@babel/plugin-transform-block-scoping@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-block-scoping](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-block-scoping) -1. **@babel/plugin-transform-classes@7.7.0** +1. **@babel/plugin-transform-classes@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-classes](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-classes) 1. **@babel/plugin-transform-computed-properties@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-computed-properties](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-computed-properties) -1. **@babel/plugin-transform-destructuring@7.6.0** +1. **@babel/plugin-transform-destructuring@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-destructuring](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-destructuring) -1. **@babel/plugin-transform-dotall-regex@7.7.0** +1. **@babel/plugin-transform-dotall-regex@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-dotall-regex](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-dotall-regex) 1. **@babel/plugin-transform-duplicate-keys@7.5.0** @@ -577,7 +571,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-for-of@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-for-of](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-for-of) -1. **@babel/plugin-transform-function-name@7.7.0** +1. **@babel/plugin-transform-function-name@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-function-name](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-function-name) 1. **@babel/plugin-transform-literals@7.2.0** @@ -589,18 +583,18 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-modules-amd@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-amd](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-amd) -1. **@babel/plugin-transform-modules-commonjs@7.7.0** +1. **@babel/plugin-transform-modules-commonjs@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-commonjs](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-commonjs) -1. **@babel/plugin-transform-modules-systemjs@7.7.0** +1. **@babel/plugin-transform-modules-systemjs@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-systemjs](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-systemjs) -1. **@babel/plugin-transform-modules-umd@7.7.0** +1. **@babel/plugin-transform-modules-umd@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-umd](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-umd) -1. **@babel/plugin-transform-named-capturing-groups-regex@7.7.0** +1. **@babel/plugin-transform-named-capturing-groups-regex@7.4.5** * Licenses: MIT - * Repository: [https://github.com/babel/babel](https://github.com/babel/babel) + * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-named-capturing-groups-regex](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-named-capturing-groups-regex) 1. **@babel/plugin-transform-new-target@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-new-target](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-new-target) @@ -613,7 +607,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-property-literals@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-property-literals](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-property-literals) -1. **@babel/plugin-transform-regenerator@7.7.0** +1. **@babel/plugin-transform-regenerator@7.4.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-regenerator](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-regenerator) 1. **@babel/plugin-transform-reserved-words@7.2.0** @@ -622,7 +616,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-shorthand-properties@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-shorthand-properties](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-shorthand-properties) -1. **@babel/plugin-transform-spread@7.6.2** +1. **@babel/plugin-transform-spread@7.2.2** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-spread](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-spread) 1. **@babel/plugin-transform-sticky-regex@7.2.0** @@ -634,91 +628,91 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-typeof-symbol@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-typeof-symbol](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-typeof-symbol) -1. **@babel/plugin-transform-unicode-regex@7.7.0** +1. **@babel/plugin-transform-unicode-regex@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-unicode-regex](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-unicode-regex) -1. **@babel/preset-env@7.7.1** +1. **@babel/preset-env@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-preset-env](https://github.com/babel/babel/tree/master/packages/babel-preset-env) -1. **@babel/register@7.7.0** +1. **@babel/register@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-register](https://github.com/babel/babel/tree/master/packages/babel-register) -1. **@babel/template@7.7.0** +1. **@babel/template@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-template](https://github.com/babel/babel/tree/master/packages/babel-template) -1. **@babel/traverse@7.7.2** +1. **@babel/traverse@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-traverse](https://github.com/babel/babel/tree/master/packages/babel-traverse) -1. **@babel/types@7.7.2** +1. **@babel/types@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-types](https://github.com/babel/babel/tree/master/packages/babel-types) 1. **@firebase/app-types@0.4.3** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/app@0.4.17** +1. **@firebase/app@0.4.14** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/auth-types@0.8.0** +1. **@firebase/auth-types@0.7.2** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/auth@0.12.0** +1. **@firebase/auth@0.11.8** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/database-types@0.4.3** +1. **@firebase/database-types@0.4.2** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/database@0.5.4** +1. **@firebase/database@0.4.12** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/firestore-types@1.5.0** +1. **@firebase/firestore-types@1.4.4** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/firestore@1.5.3** +1. **@firebase/firestore@1.4.9** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/functions-types@0.3.8** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/functions@0.4.18** +1. **@firebase/functions@0.4.15** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/installations-types@0.1.2** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/installations@0.2.7** +1. **@firebase/installations@0.2.4** * Licenses: Apache-2.0 * Repository: unknown -1. **@firebase/logger@0.1.25** +1. **@firebase/logger@0.1.22** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/messaging-types@0.3.2** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/messaging@0.4.11** +1. **@firebase/messaging@0.4.8** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/performance-types@0.0.3** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk/tree/master/packages/performance-types](https://github.com/firebase/firebase-js-sdk/tree/master/packages/performance-types) -1. **@firebase/performance@0.2.19** +1. **@firebase/performance@0.2.15** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk/tree/master/packages/performance](https://github.com/firebase/firebase-js-sdk/tree/master/packages/performance) -1. **@firebase/polyfill@0.3.22** +1. **@firebase/polyfill@0.3.19** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/storage-types@0.3.3** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/storage@0.3.12** +1. **@firebase/storage@0.3.9** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/util@0.2.28** +1. **@firebase/util@0.2.25** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/webchannel-wrapper@0.2.26** +1. **@firebase/webchannel-wrapper@0.2.24** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@grpc/proto-loader@0.5.3** +1. **@grpc/proto-loader@0.5.1** * Licenses: Apache-2.0 * Repository: [https://github.com/grpc/grpc-node](https://github.com/grpc/grpc-node) 1. **@protobufjs/aspromise@1.1.2** @@ -751,25 +745,22 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@protobufjs/utf8@1.1.0** * Licenses: BSD-3-Clause * Repository: [https://github.com/dcodeIO/protobuf.js](https://github.com/dcodeIO/protobuf.js) -1. **@sinonjs/commons@1.6.0** +1. **@sinonjs/commons@1.4.0** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/commons](https://github.com/sinonjs/commons) -1. **@sinonjs/formatio@3.2.2** +1. **@sinonjs/formatio@3.2.1** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/formatio](https://github.com/sinonjs/formatio) -1. **@sinonjs/samsam@3.3.3** +1. **@sinonjs/samsam@3.3.2** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/samsam](https://github.com/sinonjs/samsam) 1. **@sinonjs/text-encoding@0.7.1** * Licenses: (Unlicense OR Apache-2.0) * Repository: [https://github.com/inexorabletash/text-encoding](https://github.com/inexorabletash/text-encoding) -1. **@types/bytebuffer@5.0.40** - * Licenses: MIT - * Repository: [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) 1. **@types/long@4.0.0** * Licenses: MIT * Repository: [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) -1. **@types/node@10.17.5** +1. **@types/node@10.14.15** * Licenses: MIT * Repository: [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) 1. **@webassemblyjs/ast@1.8.5** @@ -967,7 +958,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **binary-extensions@1.13.1** * Licenses: MIT * Repository: [https://github.com/sindresorhus/binary-extensions](https://github.com/sindresorhus/binary-extensions) -1. **bluebird@3.7.1** +1. **bluebird@3.5.5** * Licenses: MIT * Repository: [https://github.com/petkaantonov/bluebird](https://github.com/petkaantonov/bluebird) 1. **bn.js@4.11.8** @@ -1003,7 +994,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **browserify-zlib@0.2.0** * Licenses: MIT * Repository: [https://github.com/devongovett/browserify-zlib](https://github.com/devongovett/browserify-zlib) -1. **browserslist@4.7.2** +1. **browserslist@4.6.6** * Licenses: MIT * Repository: [https://github.com/browserslist/browserslist](https://github.com/browserslist/browserslist) 1. **buffer-from@1.1.1** @@ -1012,7 +1003,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **buffer-xor@1.0.3** * Licenses: MIT * Repository: [https://github.com/crypto-browserify/buffer-xor](https://github.com/crypto-browserify/buffer-xor) -1. **buffer@4.9.2** +1. **buffer@4.9.1** * Licenses: MIT * Repository: [https://github.com/feross/buffer](https://github.com/feross/buffer) 1. **builtin-status-codes@3.0.0** @@ -1021,7 +1012,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **bytebuffer@5.0.1** * Licenses: Apache-2.0 * Repository: [https://github.com/dcodeIO/bytebuffer.js](https://github.com/dcodeIO/bytebuffer.js) -1. **cacache@12.0.3** +1. **cacache@12.0.2** * Licenses: ISC * Repository: [https://github.com/npm/cacache](https://github.com/npm/cacache) 1. **cache-base@1.0.1** @@ -1036,7 +1027,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **camelcase@5.3.1** * Licenses: MIT * Repository: [https://github.com/sindresorhus/camelcase](https://github.com/sindresorhus/camelcase) -1. **caniuse-lite@1.0.30001008** +1. **caniuse-lite@1.0.30000989** * Licenses: CC-BY-4.0 * Repository: [https://github.com/ben-eb/caniuse-lite](https://github.com/ben-eb/caniuse-lite) 1. **chalk@1.1.3** @@ -1045,7 +1036,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **chalk@2.4.2** * Licenses: MIT * Repository: [https://github.com/chalk/chalk](https://github.com/chalk/chalk) -1. **chokidar@2.1.8** +1. **chokidar@2.1.6** * Licenses: MIT * Repository: [https://github.com/paulmillr/chokidar](https://github.com/paulmillr/chokidar) 1. **chownr@1.1.1** @@ -1054,9 +1045,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **chownr@1.1.2** * Licenses: ISC * Repository: [https://github.com/isaacs/chownr](https://github.com/isaacs/chownr) -1. **chownr@1.1.3** - * Licenses: ISC - * Repository: [https://github.com/isaacs/chownr](https://github.com/isaacs/chownr) 1. **chrome-trace-event@1.0.2** * Licenses: MIT * Repository: [github.com:samccone/chrome-trace-event](github.com:samccone/chrome-trace-event) @@ -1075,7 +1063,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **code-point-at@1.1.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/code-point-at](https://github.com/sindresorhus/code-point-at) -1. **codecov@3.6.1** +1. **codecov@3.5.0** * Licenses: MIT * Repository: [https://github.com/codecov/codecov-node](https://github.com/codecov/codecov-node) 1. **collection-visit@1.0.0** @@ -1093,7 +1081,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **commander@2.15.1** * Licenses: MIT * Repository: [https://github.com/tj/commander.js](https://github.com/tj/commander.js) -1. **commander@2.20.3** +1. **commander@2.20.0** * Licenses: MIT * Repository: [https://github.com/tj/commander.js](https://github.com/tj/commander.js) 1. **commondir@1.0.1** @@ -1108,16 +1096,16 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **concat-stream@1.6.2** * Licenses: MIT * Repository: [https://github.com/maxogden/concat-stream](https://github.com/maxogden/concat-stream) -1. **console-browserify@1.2.0** +1. **console-browserify@1.1.0** * Licenses: MIT - * Repository: [https://github.com/browserify/console-browserify](https://github.com/browserify/console-browserify) + * Repository: [https://github.com/Raynos/console-browserify](https://github.com/Raynos/console-browserify) 1. **console-control-strings@1.1.0** * Licenses: ISC * Repository: [https://github.com/iarna/console-control-strings](https://github.com/iarna/console-control-strings) 1. **constants-browserify@1.0.0** * Licenses: MIT * Repository: [https://github.com/juliangruber/constants-browserify](https://github.com/juliangruber/constants-browserify) -1. **convert-source-map@1.7.0** +1. **convert-source-map@1.6.0** * Licenses: MIT * Repository: [https://github.com/thlorenz/convert-source-map](https://github.com/thlorenz/convert-source-map) 1. **copy-concurrently@1.0.5** @@ -1126,13 +1114,16 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **copy-descriptor@0.1.1** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/copy-descriptor](https://github.com/jonschlinkert/copy-descriptor) -1. **core-js-compat@3.4.0** +1. **core-js-compat@3.2.0** * Licenses: MIT * Repository: [https://github.com/zloirock/core-js](https://github.com/zloirock/core-js) -1. **core-js@2.6.10** +1. **core-js@2.6.9** * Licenses: MIT * Repository: [https://github.com/zloirock/core-js](https://github.com/zloirock/core-js) -1. **core-js@3.2.1** +1. **core-js@3.1.4** + * Licenses: MIT + * Repository: [https://github.com/zloirock/core-js](https://github.com/zloirock/core-js) +1. **core-js@3.2.0** * Licenses: MIT * Repository: [https://github.com/zloirock/core-js](https://github.com/zloirock/core-js) 1. **core-util-is@1.0.2** @@ -1159,9 +1150,12 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **crypto-browserify@3.12.0** * Licenses: MIT * Repository: [https://github.com/crypto-browserify/crypto-browserify](https://github.com/crypto-browserify/crypto-browserify) -1. **cyclist@1.0.1** - * Licenses: MIT +1. **cyclist@0.2.2** + * Licenses: MIT* * Repository: [https://github.com/mafintosh/cyclist](https://github.com/mafintosh/cyclist) +1. **date-now@0.1.4** + * Licenses: MIT + * Repository: [https://github.com/Colingo/date-now](https://github.com/Colingo/date-now) 1. **debug@2.6.9** * Licenses: MIT * Repository: [https://github.com/visionmedia/debug](https://github.com/visionmedia/debug) @@ -1231,10 +1225,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **duplexify@3.7.1** * Licenses: MIT * Repository: [https://github.com/mafintosh/duplexify](https://github.com/mafintosh/duplexify) -1. **electron-to-chromium@1.3.306** +1. **electron-to-chromium@1.3.224** * Licenses: ISC * Repository: [https://github.com/kilian/electron-to-chromium](https://github.com/kilian/electron-to-chromium) -1. **elliptic@6.5.1** +1. **elliptic@6.5.0** * Licenses: MIT * Repository: [https://github.com/indutny/elliptic](https://github.com/indutny/elliptic) 1. **emoji-regex@7.0.3** @@ -1243,15 +1237,12 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **emojis-list@2.1.0** * Licenses: MIT * Repository: [https://github.com/kikobeats/emojis-list](https://github.com/kikobeats/emojis-list) -1. **end-of-stream@1.4.4** +1. **end-of-stream@1.4.1** * Licenses: MIT * Repository: [https://github.com/mafintosh/end-of-stream](https://github.com/mafintosh/end-of-stream) 1. **enhanced-resolve@4.1.0** * Licenses: MIT * Repository: [https://github.com/webpack/enhanced-resolve](https://github.com/webpack/enhanced-resolve) -1. **enhanced-resolve@4.1.1** - * Licenses: MIT - * Repository: [https://github.com/webpack/enhanced-resolve](https://github.com/webpack/enhanced-resolve) 1. **errno@0.1.7** * Licenses: MIT * Repository: [https://github.com/rvagg/node-errno](https://github.com/rvagg/node-errno) @@ -1279,7 +1270,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **esrecurse@4.2.1** * Licenses: BSD-2-Clause * Repository: [https://github.com/estools/esrecurse](https://github.com/estools/esrecurse) -1. **estraverse@4.3.0** +1. **estraverse@4.2.0** * Licenses: BSD-2-Clause * Repository: [https://github.com/estools/estraverse](https://github.com/estools/estraverse) 1. **esutils@2.0.3** @@ -1339,7 +1330,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **findup-sync@3.0.0** * Licenses: MIT * Repository: [https://github.com/gulpjs/findup-sync](https://github.com/gulpjs/findup-sync) -1. **firebase@6.6.2** +1. **firebase@6.3.5** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **flush-write-stream@1.1.1** @@ -1357,9 +1348,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **from2@2.3.0** * Licenses: MIT * Repository: [https://github.com/hughsk/from2](https://github.com/hughsk/from2) -1. **fs-minipass@1.2.5** - * Licenses: ISC - * Repository: [https://github.com/npm/fs-minipass](https://github.com/npm/fs-minipass) 1. **fs-minipass@1.2.6** * Licenses: ISC * Repository: [https://github.com/npm/fs-minipass](https://github.com/npm/fs-minipass) @@ -1372,9 +1360,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **fs.realpath@1.0.0** * Licenses: ISC * Repository: [https://github.com/isaacs/fs.realpath](https://github.com/isaacs/fs.realpath) -1. **fsevents@1.2.9** - * Licenses: MIT - * Repository: [https://github.com/strongloop/fsevents](https://github.com/strongloop/fsevents) 1. **function-bind@1.1.1** * Licenses: MIT * Repository: [https://github.com/Raynos/function-bind](https://github.com/Raynos/function-bind) @@ -1396,15 +1381,9 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **glob@7.1.2** * Licenses: ISC * Repository: [https://github.com/isaacs/node-glob](https://github.com/isaacs/node-glob) -1. **glob@7.1.3** - * Licenses: ISC - * Repository: [https://github.com/isaacs/node-glob](https://github.com/isaacs/node-glob) 1. **glob@7.1.4** * Licenses: ISC * Repository: [https://github.com/isaacs/node-glob](https://github.com/isaacs/node-glob) -1. **glob@7.1.6** - * Licenses: ISC - * Repository: [https://github.com/isaacs/node-glob](https://github.com/isaacs/node-glob) 1. **global-modules@1.0.0** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/global-modules](https://github.com/jonschlinkert/global-modules) @@ -1423,16 +1402,16 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **globals@9.18.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/globals](https://github.com/sindresorhus/globals) -1. **graceful-fs@4.2.3** +1. **graceful-fs@4.2.1** * Licenses: ISC * Repository: [https://github.com/isaacs/node-graceful-fs](https://github.com/isaacs/node-graceful-fs) 1. **growl@1.10.5** * Licenses: MIT * Repository: [https://github.com/tj/node-growl](https://github.com/tj/node-growl) -1. **grpc@1.23.3** +1. **grpc@1.22.2** * Licenses: Apache-2.0 * Repository: [https://github.com/grpc/grpc-node](https://github.com/grpc/grpc-node) -1. **handlebars@4.5.1** +1. **handlebars@4.3.1** * Licenses: MIT * Repository: [https://github.com/wycats/handlebars.js](https://github.com/wycats/handlebars.js) 1. **has-ansi@2.0.0** @@ -1477,7 +1456,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **homedir-polyfill@1.0.3** * Licenses: MIT * Repository: [https://github.com/doowb/homedir-polyfill](https://github.com/doowb/homedir-polyfill) -1. **hosted-git-info@2.8.5** +1. **hosted-git-info@2.8.2** * Licenses: ISC * Repository: [https://github.com/npm/hosted-git-info](https://github.com/npm/hosted-git-info) 1. **http-parser-js@0.4.10** @@ -1486,10 +1465,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **https-browserify@1.0.0** * Licenses: MIT * Repository: [https://github.com/substack/https-browserify](https://github.com/substack/https-browserify) -1. **https-proxy-agent@2.2.4** +1. **https-proxy-agent@2.2.2** * Licenses: MIT * Repository: [https://github.com/TooTallNate/node-https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) -1. **iconv-lite@0.4.24** +1. **iconv-lite@0.4.23** * Licenses: MIT * Repository: [https://github.com/ashtuchkin/iconv-lite](https://github.com/ashtuchkin/iconv-lite) 1. **idb@3.0.2** @@ -1504,9 +1483,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **ignore-walk@3.0.1** * Licenses: ISC * Repository: [https://github.com/isaacs/ignore-walk](https://github.com/isaacs/ignore-walk) -1. **ignore-walk@3.0.3** - * Licenses: ISC - * Repository: [https://github.com/isaacs/ignore-walk](https://github.com/isaacs/ignore-walk) 1. **import-local@2.0.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/import-local](https://github.com/sindresorhus/import-local) @@ -1594,6 +1570,9 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **is-number@3.0.0** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/is-number](https://github.com/jonschlinkert/is-number) +1. **is-plain-obj@1.1.0** + * Licenses: MIT + * Repository: [https://github.com/sindresorhus/is-plain-obj](https://github.com/sindresorhus/is-plain-obj) 1. **is-plain-object@2.0.4** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/is-plain-object](https://github.com/jonschlinkert/is-plain-object) @@ -1666,7 +1645,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **json5@1.0.1** * Licenses: MIT * Repository: [https://github.com/json5/json5](https://github.com/json5/json5) -1. **json5@2.1.1** +1. **json5@2.1.0** * Licenses: MIT * Repository: [https://github.com/json5/json5](https://github.com/json5/json5) 1. **just-extend@4.0.2** @@ -1762,9 +1741,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **memory-fs@0.4.1** * Licenses: MIT * Repository: [https://github.com/webpack/memory-fs](https://github.com/webpack/memory-fs) -1. **memory-fs@0.5.0** - * Licenses: MIT - * Repository: [https://github.com/webpack/memory-fs](https://github.com/webpack/memory-fs) 1. **merge-source-map@1.1.0** * Licenses: MIT * Repository: [https://github.com/keik/merge-source-map](https://github.com/keik/merge-source-map) @@ -1786,9 +1762,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **minimatch@3.0.4** * Licenses: ISC * Repository: [https://github.com/isaacs/minimatch](https://github.com/isaacs/minimatch) -1. **minimist@0.0.10** - * Licenses: MIT - * Repository: [https://github.com/substack/minimist](https://github.com/substack/minimist) 1. **minimist@0.0.8** * Licenses: MIT * Repository: [https://github.com/substack/minimist](https://github.com/substack/minimist) @@ -1819,9 +1792,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **ms@2.0.0** * Licenses: MIT * Repository: [https://github.com/zeit/ms](https://github.com/zeit/ms) -1. **ms@2.1.1** - * Licenses: MIT - * Repository: [https://github.com/zeit/ms](https://github.com/zeit/ms) 1. **ms@2.1.2** * Licenses: MIT * Repository: [https://github.com/zeit/ms](https://github.com/zeit/ms) @@ -1831,9 +1801,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **nanomatch@1.2.13** * Licenses: MIT * Repository: [https://github.com/micromatch/nanomatch](https://github.com/micromatch/nanomatch) -1. **needle@2.3.0** - * Licenses: MIT - * Repository: [https://github.com/tomas/needle](https://github.com/tomas/needle) 1. **needle@2.4.0** * Licenses: MIT * Repository: [https://github.com/tomas/needle](https://github.com/tomas/needle) @@ -1846,7 +1813,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **nice-try@1.0.5** * Licenses: MIT * Repository: [https://github.com/electerious/nice-try](https://github.com/electerious/nice-try) -1. **nise@1.5.2** +1. **nise@1.5.1** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/nise](https://github.com/sinonjs/nise) 1. **node-fetch@2.6.0** @@ -1858,13 +1825,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **node-modules-regexp@1.0.0** * Licenses: MIT * Repository: [https://github.com/jamestalmage/node-modules-regexp](https://github.com/jamestalmage/node-modules-regexp) -1. **node-pre-gyp@0.12.0** - * Licenses: BSD-3-Clause - * Repository: [https://github.com/mapbox/node-pre-gyp](https://github.com/mapbox/node-pre-gyp) 1. **node-pre-gyp@0.13.0** * Licenses: BSD-3-Clause * Repository: [https://github.com/mapbox/node-pre-gyp](https://github.com/mapbox/node-pre-gyp) -1. **node-releases@1.1.39** +1. **node-releases@1.1.27** * Licenses: MIT * Repository: [https://github.com/chicoxyzzy/node-releases](https://github.com/chicoxyzzy/node-releases) 1. **nopt@4.0.1** @@ -1885,9 +1849,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **npm-packlist@1.4.1** * Licenses: ISC * Repository: [https://github.com/npm/npm-packlist](https://github.com/npm/npm-packlist) -1. **npm-packlist@1.4.4** - * Licenses: ISC - * Repository: [https://github.com/npm/npm-packlist](https://github.com/npm/npm-packlist) 1. **npm-run-path@2.0.2** * Licenses: MIT * Repository: [https://github.com/sindresorhus/npm-run-path](https://github.com/sindresorhus/npm-run-path) @@ -1945,6 +1906,9 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **osenv@0.1.5** * Licenses: ISC * Repository: [https://github.com/npm/osenv](https://github.com/npm/osenv) +1. **output-file-sync@2.0.1** + * Licenses: ISC + * Repository: [https://github.com/shinnn/output-file-sync](https://github.com/shinnn/output-file-sync) 1. **p-defer@1.0.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/p-defer](https://github.com/sindresorhus/p-defer) @@ -1957,7 +1921,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **p-limit@1.3.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/p-limit](https://github.com/sindresorhus/p-limit) -1. **p-limit@2.2.1** +1. **p-limit@2.2.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/p-limit](https://github.com/sindresorhus/p-limit) 1. **p-locate@2.0.0** @@ -1978,10 +1942,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **pako@1.0.10** * Licenses: (MIT AND Zlib) * Repository: [https://github.com/nodeca/pako](https://github.com/nodeca/pako) -1. **parallel-transform@1.2.0** +1. **parallel-transform@1.1.0** * Licenses: MIT * Repository: [https://github.com/mafintosh/parallel-transform](https://github.com/mafintosh/parallel-transform) -1. **parse-asn1@5.1.5** +1. **parse-asn1@5.1.4** * Licenses: ISC * Repository: [https://github.com/crypto-browserify/parse-asn1](https://github.com/crypto-browserify/parse-asn1) 1. **parse-json@4.0.0** @@ -2011,7 +1975,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **path-parse@1.0.6** * Licenses: MIT * Repository: [https://github.com/jbgutierrez/path-parse](https://github.com/jbgutierrez/path-parse) -1. **path-to-regexp@1.8.0** +1. **path-to-regexp@1.7.0** * Licenses: MIT * Repository: [https://github.com/pillarjs/path-to-regexp](https://github.com/pillarjs/path-to-regexp) 1. **path-type@3.0.0** @@ -2041,9 +2005,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **private@0.1.8** * Licenses: MIT * Repository: [https://github.com/benjamn/private](https://github.com/benjamn/private) -1. **process-nextick-args@2.0.0** - * Licenses: MIT - * Repository: [https://github.com/calvinmetcalf/process-nextick-args](https://github.com/calvinmetcalf/process-nextick-args) 1. **process-nextick-args@2.0.1** * Licenses: MIT * Repository: [https://github.com/calvinmetcalf/process-nextick-args](https://github.com/calvinmetcalf/process-nextick-args) @@ -2107,7 +2068,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **read-installed@4.0.3** * Licenses: ISC * Repository: [https://github.com/isaacs/read-installed](https://github.com/isaacs/read-installed) -1. **read-package-json@2.1.0** +1. **read-package-json@2.0.13** * Licenses: ISC * Repository: [https://github.com/npm/read-package-json](https://github.com/npm/read-package-json) 1. **read-pkg-up@4.0.0** @@ -2140,10 +2101,13 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **regex-not@1.0.2** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/regex-not](https://github.com/jonschlinkert/regex-not) -1. **regexpu-core@4.6.0** +1. **regexp-tree@0.1.11** + * Licenses: MIT + * Repository: [https://github.com/DmitrySoshnikov/regexp-tree](https://github.com/DmitrySoshnikov/regexp-tree) +1. **regexpu-core@4.5.5** * Licenses: MIT * Repository: [https://github.com/mathiasbynens/regexpu-core](https://github.com/mathiasbynens/regexpu-core) -1. **regjsgen@0.5.1** +1. **regjsgen@0.5.0** * Licenses: MIT * Repository: [https://github.com/bnjmnt4n/regjsgen](https://github.com/bnjmnt4n/regjsgen) 1. **regjsparser@0.6.0** @@ -2194,9 +2158,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **rimraf@2.6.3** * Licenses: ISC * Repository: [https://github.com/isaacs/rimraf](https://github.com/isaacs/rimraf) -1. **rimraf@2.7.1** - * Licenses: ISC - * Repository: [https://github.com/isaacs/rimraf](https://github.com/isaacs/rimraf) 1. **ripemd160@2.0.2** * Licenses: MIT * Repository: [https://github.com/crypto-browserify/ripemd160](https://github.com/crypto-browserify/ripemd160) @@ -2221,13 +2182,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **semver@5.7.0** * Licenses: ISC * Repository: [https://github.com/npm/node-semver](https://github.com/npm/node-semver) -1. **semver@5.7.1** - * Licenses: ISC - * Repository: [https://github.com/npm/node-semver](https://github.com/npm/node-semver) 1. **semver@6.3.0** * Licenses: ISC * Repository: [https://github.com/npm/node-semver](https://github.com/npm/node-semver) -1. **serialize-javascript@1.9.1** +1. **serialize-javascript@1.7.0** * Licenses: BSD-3-Clause * Repository: [https://github.com/yahoo/serialize-javascript](https://github.com/yahoo/serialize-javascript) 1. **set-blocking@2.0.0** @@ -2248,10 +2206,13 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **shebang-regex@1.0.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/shebang-regex](https://github.com/sindresorhus/shebang-regex) +1. **signal-exit@3.0.1** + * Licenses: ISC + * Repository: [https://github.com/tapjs/signal-exit](https://github.com/tapjs/signal-exit) 1. **signal-exit@3.0.2** * Licenses: ISC * Repository: [https://github.com/tapjs/signal-exit](https://github.com/tapjs/signal-exit) -1. **sinon@7.5.0** +1. **sinon@7.4.1** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/sinon](https://github.com/sinonjs/sinon) 1. **slash@1.0.0** @@ -2278,7 +2239,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **source-map-resolve@0.5.2** * Licenses: MIT * Repository: [https://github.com/lydell/source-map-resolve](https://github.com/lydell/source-map-resolve) -1. **source-map-support@0.5.16** +1. **source-map-support@0.5.13** * Licenses: MIT * Repository: [https://github.com/evanw/node-source-map-support](https://github.com/evanw/node-source-map-support) 1. **source-map-url@0.4.0** @@ -2314,7 +2275,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **spdx-satisfies@4.0.1** * Licenses: MIT * Repository: [https://github.com/kemitchell/spdx-satisfies.js](https://github.com/kemitchell/spdx-satisfies.js) -1. **spine-web@1.2.1** +1. **spine-web@1.2.6** * Licenses: Apache-2.0 * Repository: [https://github.com/SpineEventEngine/web](https://github.com/SpineEventEngine/web) 1. **split-string@3.1.0** @@ -2383,18 +2344,15 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **tar@4.4.10** * Licenses: ISC * Repository: [https://github.com/npm/node-tar](https://github.com/npm/node-tar) -1. **tar@4.4.8** - * Licenses: ISC - * Repository: [https://github.com/npm/node-tar](https://github.com/npm/node-tar) 1. **teeny-request@3.11.3** * Licenses: Apache-2.0 * Repository: [https://github.com/fhinkel/teeny-request](https://github.com/fhinkel/teeny-request) 1. **terser-webpack-plugin@1.4.1** * Licenses: MIT * Repository: [https://github.com/webpack-contrib/terser-webpack-plugin](https://github.com/webpack-contrib/terser-webpack-plugin) -1. **terser@4.4.0** +1. **terser@4.1.4** * Licenses: BSD-2-Clause - * Repository: [https://github.com/terser/terser](https://github.com/terser/terser) + * Repository: [https://github.com/fabiosantoscode/terser](https://github.com/fabiosantoscode/terser) 1. **test-exclude@5.2.3** * Licenses: ISC * Repository: [https://github.com/istanbuljs/istanbuljs](https://github.com/istanbuljs/istanbuljs) @@ -2425,6 +2383,9 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **treeify@1.1.0** * Licenses: MIT * Repository: [https://github.com/notatestuser/treeify](https://github.com/notatestuser/treeify) +1. **trim-right@1.0.1** + * Licenses: MIT + * Repository: [https://github.com/sindresorhus/trim-right](https://github.com/sindresorhus/trim-right) 1. **tty-browserify@0.0.0** * Licenses: MIT * Repository: [https://github.com/substack/tty-browserify](https://github.com/substack/tty-browserify) @@ -2434,7 +2395,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **typedarray@0.0.6** * Licenses: MIT * Repository: [https://github.com/substack/typedarray](https://github.com/substack/typedarray) -1. **uglify-js@3.6.8** +1. **uglify-js@3.6.0** * Licenses: BSD-2-Clause * Repository: [https://github.com/mishoo/UglifyJS2](https://github.com/mishoo/UglifyJS2) 1. **unicode-canonical-property-names-ecmascript@1.0.4** @@ -2461,7 +2422,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **unset-value@1.0.0** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/unset-value](https://github.com/jonschlinkert/unset-value) -1. **upath@1.2.0** +1. **upath@1.1.2** * Licenses: MIT * Repository: [https://github.com/anodynos/upath](https://github.com/anodynos/upath) 1. **uri-js@4.2.2** @@ -2497,22 +2458,22 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **validate-npm-package-license@3.0.4** * Licenses: Apache-2.0 * Repository: [https://github.com/kemitchell/validate-npm-package-license.js](https://github.com/kemitchell/validate-npm-package-license.js) -1. **vm-browserify@1.1.2** +1. **vm-browserify@1.1.0** * Licenses: MIT * Repository: [https://github.com/substack/vm-browserify](https://github.com/substack/vm-browserify) 1. **watchpack@1.6.0** * Licenses: MIT * Repository: [https://github.com/webpack/watchpack](https://github.com/webpack/watchpack) -1. **webpack-cli@3.3.10** +1. **webpack-cli@3.3.6** * Licenses: MIT * Repository: [https://github.com/webpack/webpack-cli](https://github.com/webpack/webpack-cli) -1. **webpack-merge@4.2.2** +1. **webpack-merge@4.2.1** * Licenses: MIT * Repository: [https://github.com/survivejs/webpack-merge](https://github.com/survivejs/webpack-merge) 1. **webpack-sources@1.4.3** * Licenses: MIT * Repository: [https://github.com/webpack/webpack-sources](https://github.com/webpack/webpack-sources) -1. **webpack@4.41.2** +1. **webpack@4.39.1** * Licenses: MIT * Repository: [https://github.com/webpack/webpack](https://github.com/webpack/webpack) 1. **websocket-driver@0.7.3** @@ -2572,9 +2533,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **yallist@3.0.3** * Licenses: ISC * Repository: [https://github.com/isaacs/yallist](https://github.com/isaacs/yallist) -1. **yallist@3.1.1** - * Licenses: ISC - * Repository: [https://github.com/isaacs/yallist](https://github.com/isaacs/yallist) 1. **yargs-parser@13.1.1** * Licenses: ISC * Repository: [https://github.com/yargs/yargs-parser](https://github.com/yargs/yargs-parser) @@ -2589,12 +2547,12 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice * Repository: [https://github.com/bcoe/yargs](https://github.com/bcoe/yargs) -This report was generated on **Mon Nov 11 2019 18:27:21 GMT+0200 (EET)** using [NPM License Checker](https://github.com/davglass/license-checker) library. +This report was generated on **Tue Dec 03 2019 12:59:59 GMT+0200 (Eastern European Standard Time)** using [NPM License Checker](https://github.com/davglass/license-checker) library. -# Dependencies of `io.spine.gcloud:spine-firebase-web:1.2.1` +# Dependencies of `io.spine.gcloud:spine-firebase-web:1.2.6` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -3437,12 +3395,12 @@ This report was generated on **Mon Nov 11 2019 18:27:21 GMT+0200 (EET)** using [ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:27:22 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Tue Dec 03 13:00:14 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-js-tests:1.2.1` +# Dependencies of `io.spine:spine-js-tests:1.2.6` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -3848,12 +3806,12 @@ This report was generated on **Mon Nov 11 18:27:22 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-test-app:1.2.1` +# Dependencies of `io.spine:spine-test-app:1.2.6` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -5519,12 +5477,12 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:28:07 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Tue Dec 03 13:01:36 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-testutil-web:1.2.1` +# Dependencies of `io.spine:spine-testutil-web:1.2.6` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -5991,12 +5949,12 @@ This report was generated on **Mon Nov 11 18:28:07 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:28:08 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Tue Dec 03 13:01:46 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-web:1.2.1` +# Dependencies of `io.spine:spine-web:1.2.6` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -6539,4 +6497,4 @@ This report was generated on **Mon Nov 11 18:28:08 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:28:08 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Tue Dec 03 13:01:56 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index c475e0b95..961674f6e 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-web -1.2.1 +1.2.6 2015 @@ -76,7 +76,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-server - 1.2.1 + 1.2.6 compile @@ -100,13 +100,13 @@ all modules and does not describe the project structure per-subproject. io.spine spine-testutil-client - 1.2.1 + 1.2.6 test io.spine.tools spine-mute-logging - 1.2.1 + 1.2.3 test @@ -168,17 +168,17 @@ all modules and does not describe the project structure per-subproject. io.spine spine-client - 1.2.1 + 1.2.6 io.spine.tools spine-errorprone-checks - 1.2.1 + 1.2.3 io.spine.tools spine-protoc-plugin - 1.2.1 + 1.2.3 javax.servlet diff --git a/version.gradle b/version.gradle index c2d596979..c990314f5 100644 --- a/version.gradle +++ b/version.gradle @@ -19,10 +19,10 @@ */ ext { - spineVersion = '1.2.1' - spineBaseVersion = '1.2.1' + spineVersion = '1.2.6' + spineBaseVersion = '1.2.3' - versionToPublish = '1.2.1' + versionToPublish = '1.2.6' versionToPublishJs = versionToPublish servletApiVersion = '3.1.0' From 6fb39f470cbaeed81781bb45a85d561f2792f437 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 3 Dec 2019 14:53:42 +0200 Subject: [PATCH 21/70] Remove redundant overrides --- client-js/main/client/composite-client.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index 9ce0e5f1c..9b30c6911 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -210,24 +210,6 @@ export class NoOpSubscribingClient extends SubscribingClient { super(actorRequestFactory) } - /** - * Always throws an error. - * - * @override - */ - subscribeTo(type) { - throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); - } - - /** - * Always throws an error. - * - * @override - */ - subscribeToEvent(type, client) { - throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); - } - /** * Always throws an error. * From b7bcd89787e7abd8fd130b5a3a051c72fc3b00f5 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 3 Dec 2019 21:17:24 +0200 Subject: [PATCH 22/70] Repair the tests The integration tests are now re-enabled, as the corresponding issue with Gretty was resolved in the latest version. --- .travis.yml | 3 +- client-js/main/client/client-request.js | 32 +++++-- client-js/main/client/client.js | 4 +- client-js/main/client/composite-client.js | 20 ++-- client-js/main/client/firebase-client.js | 2 +- client-js/test/client/direct-client-test.js | 2 +- integration-tests/js-tests/package.json | 2 +- .../test/direct-client/given/direct-client.js | 6 +- .../js-tests/test/direct-client/query-test.js | 11 +-- .../test/firebase-client/fetch-test.js | 93 ++++++++++--------- .../firebase-client/given/test-environment.js | 6 +- .../test/firebase-client/query-test.js | 32 ++----- .../test/firebase-client/send-command-test.js | 87 +++++++++-------- .../test/firebase-client/subscribe-test.js | 78 +++++++++------- .../test/firebase-client/topic-test.js | 41 +++----- .../test/given/users-test-environment.js | 20 +++- integration-tests/test-app/build.gradle | 23 ++++- pom.xml | 26 ++---- 18 files changed, 261 insertions(+), 227 deletions(-) diff --git a/.travis.yml b/.travis.yml index 55037ac18..075c4f3d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,7 @@ script: - ./gradlew check --stacktrace # Run integration tests of `spine-web` library. - # - ./gradlew integrationTest - # TODO:2019-11-11:dmytro.dashenkov: Run integration tests when https://github.com/gretty-gradle-plugin/gretty/issues/130 is resolved. + - ./gradlew integrationTest # The publishing script should be executed in `script` section in order to # fail the Travis build if execution of this script is failed. diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 19104ea6f..5b1d6cab2 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -71,21 +71,27 @@ class FilteringRequest extends ClientRequest { } /** - * @param ids {![]|Number[]|String[]} + * @param ids {!|Number|String|[]|Number[]|String[]} * @return {this} self for method chaining * * @template a Protobuf type of IDs */ byId(ids) { + ids = FilteringRequest._ensureArray(ids); this._builder().byIds(ids); return this._self(); } /** - * @param {!Filter[]|CompositeFilter[]} predicates + * ... + * + * The subsequent calls override each other. + * + * @param {!Filter|CompositeFilter|Filter[]|CompositeFilter[]} predicates * @return {this} self for method chaining */ where(predicates) { + predicates = FilteringRequest._ensureArray(predicates); this._builder().where(predicates); return this._self(); } @@ -95,6 +101,7 @@ class FilteringRequest extends ClientRequest { * @return {this} self for method chaining */ withMask(fieldNames) { + fieldNames = FilteringRequest._ensureArray(fieldNames); this._builder().withMask(fieldNames); return this._self(); } @@ -110,7 +117,7 @@ class FilteringRequest extends ClientRequest { // TODO:2019-11-27:dmytro.kuzmin:WIP Check that setting to some initial value is // unnecessary. if (!this._builderInstance) { - this._builderInstance = this._newBuilderFn().apply(this._requestFactory); + this._builderInstance = this._newBuilderFn()(this._requestFactory); } return this._builderInstance; } @@ -138,6 +145,19 @@ class FilteringRequest extends ClientRequest { _self() { throw new Error('Not implemented in abstract base.'); } + + /** + * @private + */ + static _ensureArray(values) { + if (!values) { + return []; + } + if (!(values instanceof Array)) { + return [values] + } + return values; + } } export class QueryRequest extends FilteringRequest { @@ -312,7 +332,7 @@ export class CommandRequest extends ClientRequest{ * * @return {CommandRequest} self */ - onAck(callback) { + onOk(callback) { this._onAck = callback; return this; } @@ -352,9 +372,9 @@ export class CommandRequest extends ClientRequest{ */ post() { const command = this._requestFactory.command().create(this._commandMessage); - const ackCallback = + const onAck = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; - this._client.post(command, ackCallback); + this._client.post(command, onAck); const promises = []; this._observedTypes.forEach(type => { const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index a51835035..d10b36a51 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -144,9 +144,9 @@ export class Client { /** * @param {!spine.core.Command} command a Command sent to Spine server - * @param {!AckCallback} ackCallback + * @param {!AckCallback} onAck */ - post(command, ackCallback) { + post(command, onAck) { throw new Error('Not implemented in abstract base.'); } } diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index 9b30c6911..9208b1be2 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -119,8 +119,8 @@ export class CompositeClient extends Client { /** * @override */ - post(command, ackCallback) { - this._commanding.post(command, ackCallback); + post(command, onAck) { + this._commanding.post(command, onAck); } } @@ -247,36 +247,36 @@ export class CommandingClient { return new CommandRequest(commandMessage, client, this._requestFactory); } - post(command, ackCallback) { + post(command, onAck) { const cmd = TypedMessage.of(command); this._endpoint.command(cmd) - .then(ack => this._onAck(ack, ackCallback)) + .then(ack => this._onAck(ack, onAck)) .catch(error => { - ackCallback.onError(new CommandHandlingError(error.message, error)); + onAck.onError(new CommandHandlingError(error.message, error)); }); } - _onAck(ack, ackCallback) { + _onAck(ack, onAck) { const responseStatus = ack.status; const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); const responseStatusCase = responseStatusProto.getStatusCase(); switch (responseStatusCase) { case Status.StatusCase.OK: - ackCallback.onOk(); + onAck.onOk(); break; case Status.StatusCase.ERROR: const error = responseStatusProto.getError(); const message = error.getMessage(); - ackCallback.onError(error.hasValidationError() + onAck.onError(error.hasValidationError() ? new CommandValidationError(message, error) : new CommandHandlingError(message, error)); break; case Status.StatusCase.REJECTION: - ackCallback.onRejection(responseStatusProto.getRejection()); + onAck.onRejection(responseStatusProto.getRejection()); break; default: - ackCallback.onError( + onAck.onError( new SpineError(`Unknown response status case ${responseStatusCase}`) ); } diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 483598279..65b400f8e 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -202,7 +202,7 @@ class FirebaseSubscribingClient extends SubscribingClient { const internalSubscription = FirebaseSubscribingClient.internalSubscription(path, topic); - const subscription = createSubscriptionFn.apply(path, internalSubscription); + const subscription = createSubscriptionFn.call(this, path, internalSubscription); resolve(subscription.toObject()); this._subscriptionService.add(subscription); diff --git a/client-js/test/client/direct-client-test.js b/client-js/test/client/direct-client-test.js index ffe959c2a..970e9fd55 100644 --- a/client-js/test/client/direct-client-test.js +++ b/client-js/test/client/direct-client-test.js @@ -34,7 +34,7 @@ describe('Direct client should', () => { actorProvider: ActorProvider.ANONYMOUS }); try { - client.subscribeTo(client.newTopic().select(TaskCreated).build()); + client.subscribeTo(TaskCreated).post(); assert.fail(); } catch (e) { assert.ok(e instanceof Error); diff --git a/integration-tests/js-tests/package.json b/integration-tests/js-tests/package.json index 381006284..d58f21c01 100644 --- a/integration-tests/js-tests/package.json +++ b/integration-tests/js-tests/package.json @@ -4,7 +4,7 @@ "license": "Apache-2.0", "description": "Tests of a `spine-web` JS library against the Spine-based application.", "scripts": { - "test": "mocha --require @babel/register --recursive --exit --full-trace ./test", + "test": "mocha --require @babel/register --recursive --exit --full-trace ./test/firebase-client/query-test.js", "installLinkedLib": "npm link spine-web" }, "devDependencies": { diff --git a/integration-tests/js-tests/test/direct-client/given/direct-client.js b/integration-tests/js-tests/test/direct-client/given/direct-client.js index 54586f9a9..e75a0943b 100644 --- a/integration-tests/js-tests/test/direct-client/given/direct-client.js +++ b/integration-tests/js-tests/test/direct-client/given/direct-client.js @@ -23,7 +23,7 @@ import * as spineWeb from '@lib/index'; import {ActorProvider} from '@lib/client/actor-request-factory'; /** - * Initializes the {@link DirectClient client} that interacts with Gretty-based + * Initializes the {@link DirectClientFactory client} that interacts with Gretty-based * local backend server. * * See `integration-tests/README.MD` for details. @@ -40,8 +40,8 @@ export function initClient() { } /** - * A {@link DirectClient client} instance for tests. + * A client instance for tests. * - * @type {DirectClient} + * @type {Client} */ export const client = initClient(); diff --git a/integration-tests/js-tests/test/direct-client/query-test.js b/integration-tests/js-tests/test/direct-client/query-test.js index 286d7aa5d..edb77356c 100644 --- a/integration-tests/js-tests/test/direct-client/query-test.js +++ b/integration-tests/js-tests/test/direct-client/query-test.js @@ -24,7 +24,7 @@ import {UserTasksTestEnvironment as TestEnvironment} from '../given/users-test-e import {client} from './given/direct-client'; import {UserTasks} from '@testProto/spine/web/test/given/user_tasks_pb'; -describe('DirectClient executes query built', function () { +describe('`DirectClient` executes query built', function () { let users; @@ -58,12 +58,9 @@ describe('DirectClient executes query built', function () { it(`by IDs and returns correct values`, done => { const ids = users.map(user => user.id); - const query = client.newQuery() - .select(UserTasks) - .byIds(ids) - .build(); - - client.execute(query) + client.select(UserTasks) + .byId(ids) + .run() .then(userTasksList => { assert.ok(ensureUserTasks(userTasksList, users)); done(); diff --git a/integration-tests/js-tests/test/firebase-client/fetch-test.js b/integration-tests/js-tests/test/firebase-client/fetch-test.js index db2bfdd18..628ffc2ca 100644 --- a/integration-tests/js-tests/test/firebase-client/fetch-test.js +++ b/integration-tests/js-tests/test/firebase-client/fetch-test.js @@ -50,7 +50,11 @@ describe('FirebaseClient "fetch"', function () { const promise = new Promise(resolve => reportTaskCreated = resolve); createTasksPromises.push(promise); - client.sendCommand(command, () => reportTaskCreated(), fail(done), fail(done)) + client.command(command) + .onOk(() => reportTaskCreated()) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); }); Promise.all(createTasksPromises) @@ -60,29 +64,39 @@ describe('FirebaseClient "fetch"', function () { }); }); - it('returns correct value by single ID', done => { - const id = taskIds[0]; - client.fetch({entity: Task, byIds: id}) + it('returns all values of type', done => { + client.select(Task) + .run() .then(data => { - assert.ok(Array.isArray(data)); - assert.equal(data.length, 1); - const item = data[0]; - assert.ok(item.getId().getValue() === id.getValue()); + taskIds.forEach(taskId => { + const targetObject = data.find( + item => item.getId().getValue() === taskId.getValue() + ); + assert.ok(targetObject); + }); + done(); }, fail(done)); }); - it('ignores `byIds` parameter when empty list specified', done => { - client.fetch({entity: Task, byIds: []}) + it('returns correct value by single ID', done => { + const id = taskIds[0]; + client.select(Task) + .byId(id) + .run() .then(data => { assert.ok(Array.isArray(data)); - assert.ok(data.length >= taskIds.length); + assert.equal(data.length, 1); + const item = data[0]; + assert.ok(item.getId().getValue() === id.getValue()); done(); }, fail(done)); }); - it('ignores `byIds` parameter when `null` value specified', done => { - client.fetch({entity: Task, byIds: null}) + it('ignores `byId` parameter when empty list is specified', done => { + client.select(Task) + .byId([]) + .run() .then(data => { assert.ok(Array.isArray(data)); assert.ok(data.length >= taskIds.length); @@ -90,8 +104,10 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('ignores `byIds` parameter when empty list specified', done => { - client.fetch({entity: Task, byIds: []}) + it('ignores `byId` parameter when `null` value is specified', done => { + client.select(Task) + .byId(null) + .run() .then(data => { assert.ok(Array.isArray(data)); assert.ok(data.length >= taskIds.length); @@ -99,11 +115,12 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('returns empty list when fetches entity by single ID that is missing', done => { const taskId = TestEnvironment.taskId({}); - client.fetch({entity: Task, byIds: taskId}) + client.select(Task) + .byId(taskId) + .run() .then(data => { assert.ok(Array.isArray(data)); assert.ok(data.length === 0); @@ -112,7 +129,9 @@ describe('FirebaseClient "fetch"', function () { }); it('returns correct values by IDs', done => { - client.fetch({entity: Task, byIds: taskIds}) + client.select(Task) + .byId(taskIds) + .run() .then(data => { assert.ok(Array.isArray(data)); assert.equal(data.length, taskIds.length); @@ -125,20 +144,9 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('retrieves the existing entities of given type when no IDs specified', done => { - client.fetch({entity: Task}) - .then(data => { - taskIds.forEach(taskId => { - const targetObject = data.find(item => item.getId().getValue() === taskId.getValue()); - assert.ok(targetObject); - }); - - done(); - }, fail(done)); - }); - it('retrieves an empty list for entity that does not get created', done => { - client.fetch({entity: Project}) + client.select(Project) + .run() .then(data => { assert.ok(data.length === 0); done(); @@ -153,16 +161,17 @@ describe('FirebaseClient "fetch"', function () { return 'spine.web/fails.malformed.type' } }; - - client.sendCommand(command, () => { - - client.fetch({entity: Unknown}) - .then(fail(done), error => { - assert.ok(error instanceof ServerError); - assert.equal(error.message, 'Server Error'); - done(); - }); - - }, fail(done), fail(done)); + const selectAndCheckFailed = () => client.select(Unknown) + .run() + .then(fail(done), error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server Error'); + done(); + }); + client.command(command) + .onOk(selectAndCheckFailed) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); }); }); diff --git a/integration-tests/js-tests/test/firebase-client/given/test-environment.js b/integration-tests/js-tests/test/firebase-client/given/test-environment.js index 9d9ba8c96..84a305c15 100644 --- a/integration-tests/js-tests/test/firebase-client/given/test-environment.js +++ b/integration-tests/js-tests/test/firebase-client/given/test-environment.js @@ -38,7 +38,7 @@ import {UserId} from '@testProto/spine/core/user_id_pb'; export default class TestEnvironment { constructor() { - throw new Error('A utility TestEnvironment class cannot be instantiated.'); + throw new Error('A utility `TestEnvironment` class cannot be instantiated.'); } /** @@ -62,7 +62,9 @@ export default class TestEnvironment { const taskId = this.taskId({value: id, withPrefix: idPrefix}); name = typeof name === 'undefined' ? this.DEFAULT_TASK_NAME : name; - description = typeof description === 'undefined' ? this.DEFAULT_TASK_DESCRIPTION : description; + description = typeof description === 'undefined' + ? this.DEFAULT_TASK_DESCRIPTION + : description; const command = new CreateTask(); command.setId(taskId); diff --git a/integration-tests/js-tests/test/firebase-client/query-test.js b/integration-tests/js-tests/test/firebase-client/query-test.js index a7013d3a0..7ecc7a385 100644 --- a/integration-tests/js-tests/test/firebase-client/query-test.js +++ b/integration-tests/js-tests/test/firebase-client/query-test.js @@ -154,25 +154,15 @@ describe('FirebaseClient executes query built', function () { } ]; - function buildQueryFor({ids, filters}) { - const queryBuilder = client.newQuery() - .select(UserTasks) - .byIds(ids ? ids : toUserIds(users)); - - if (!!filters) { - queryBuilder.where(filters) - } - - return queryBuilder.build(); - } - tests.forEach(test => { it(`${test.message} and returns correct values`, done => { const ids = test.ids ? test.ids() : undefined; const filters = test.filters; - const query = buildQueryFor({ids: ids, filters: filters}); - client.execute(query) + client.select(UserTasks) + .byId(ids) + .where(filters) + .run() .then(userTasksList => { assert.ok(ensureUserTasks(userTasksList, test.expectedUsers())); done(); @@ -184,7 +174,9 @@ describe('FirebaseClient executes query built', function () { it('with Date-based filter and returns correct values', (done) => { const userIds = toUserIds(users); - client.fetch({entity: UserTasks, byIds: userIds}) + client.select(UserTasks) + .byId(userIds) + .run() .then(data => { assert.ok(Array.isArray(data)); assert.equal(data.length, userIds.length); @@ -197,13 +189,9 @@ describe('FirebaseClient executes query built', function () { const millis = seconds * 1000 + nanos / 1000000; const whenFirstUserGotTask = new Date(millis); - const query = buildQueryFor({ - filters: [ - Filters.eq('last_updated', whenFirstUserGotTask), - ] - }); - - client.execute(query) + client.select(UserTasks) + .where(Filters.eq('last_updated', whenFirstUserGotTask)) + .run() .then(userTasksList => { assert.ok(Array.isArray(userTasksList)); assert.equal(userTasksList.length, 1); diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index 341e866df..ae7ec98a7 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -40,9 +40,10 @@ describe('FirebaseClient command sending', function () { const taskId = command.getId(); - client.sendCommand(command, () => { - - client.fetch({entity: Task, byIds: taskId}) + const fetchAndCheck = () => { + client.select(Task) + .byId(taskId) + .run() .then(data => { assert.equal(data.length, 1); const item = data[0]; @@ -53,8 +54,13 @@ describe('FirebaseClient command sending', function () { done(); }, fail(done)); + }; - }, fail(done), fail(done)); + client.command(command) + .onOk(fetchAndCheck) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); }); it('fails when wrong server endpoint specified', done => { @@ -66,45 +72,48 @@ describe('FirebaseClient command sending', function () { describedAs: 'Spine Web need integration tests' }); - malformedBackendClient.sendCommand( - command, - fail(done, 'A command was acknowledged when it was expected to fail.'), - error => { - try { - assert.ok(error instanceof CommandHandlingError); - assert.ok(error.message.startsWith(`request to ${fakeBaseUrl}/command failed`)); - const connectionError = error.getCause(); - assert.ok(connectionError instanceof ConnectionError); - done(); - } catch (e) { - fail(done, e.message) - } - }, - fail(done, 'A command was rejected when an error was expected.')); + const checkError = error => { + try { + assert.ok(error instanceof CommandHandlingError); + assert.ok(error.message.startsWith(`request to ${fakeBaseUrl}/command failed`)); + const connectionError = error.getCause(); + assert.ok(connectionError instanceof ConnectionError); + done(); + } catch (e) { + fail(done, e.message) + } + }; + malformedBackendClient.command(command) + .onOk(fail(done, 'A command was acknowledged when it was expected to fail.')) + .onError(checkError) + .onRejection(fail(done, 'A command was rejected when an error was expected.')) + .post(); }); it('fails with `CommandValidationError` for invalid command message', done => { const command = TestEnvironment.createTaskCommand({withId: null}); - client.sendCommand( - command, - fail(done, 'A command was acknowledged when it was expected to fail.'), - error => { - try { - assert.ok(error instanceof CommandValidationError); - assert.ok(error.validationError()); - // TODO:2019-06-05:yegor.udovchenko: Find the reason of failing assertion - // assert.ok(error.assuresCommandNeglected()); - - const cause = error.getCause(); - assert.ok(cause); - assert.equal(cause.getCode(), 2); - assert.equal(cause.getType(), 'spine.core.CommandValidationError'); - done(); - } catch (e) { - fail(done, e.message) - } - }, - fail(done, 'A command was rejected when an error was expected.')); + const checkError = error => { + try { + assert.ok(error instanceof CommandValidationError); + assert.ok(error.validationError()); + // TODO:2019-06-05:yegor.udovchenko: Find the reason of failing assertion + // assert.ok(error.assuresCommandNeglected()); + + const cause = error.getCause(); + assert.ok(cause); + assert.equal(cause.getCode(), 2); + assert.equal(cause.getType(), 'spine.core.CommandValidationError'); + done(); + } catch (e) { + fail(done, e.message) + } + }; + + client.command(command) + .onOk(fail(done, 'A command was acknowledged when it was expected to fail.')) + .onError(checkError) + .onRejection(fail(done, 'A command was rejected when an error was expected.')) + .post(); }); }); diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index 0229cc9f4..02750e70d 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -40,7 +40,8 @@ describe('FirebaseClient subscription', function () { })); const taskIds = commands.map(command => command.getId().getValue()); - client.subscribe({entity: Task}) + client.subscribeTo(Task) + .post() .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { itemAdded.subscribe({ next: task => { @@ -62,7 +63,10 @@ describe('FirebaseClient subscription', function () { next: fail(done, 'Unexpected entity change during entity create subscription test.') }); commands.forEach(command => { - client.sendCommand(command, TestEnvironment.noop, fail(done), fail(done)); + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); }); }) .catch(fail(done)); @@ -72,7 +76,8 @@ describe('FirebaseClient subscription', function () { const INITIAL_TASK_NAME = "Task to test entity updates"; const UPDATED_TASK_NAME = "RENAMED Task to test entity updates"; let taskId; - client.subscribe({entity: Task}) + client.subscribeTo(Task) + .post() .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { itemAdded.subscribe({ next: item => { @@ -87,12 +92,12 @@ describe('FirebaseClient subscription', function () { withId: taskId, to: UPDATED_TASK_NAME }); - client.sendCommand( - renameCommand, - () => console.log(`Task '${taskId}' renamed.`), - fail(done, 'Unexpected error while renaming a task.'), - fail(done, 'Unexpected rejection while renaming a task.') - ); + client.command(renameCommand) + .onOk(() => console.log(`Task '${taskId}' renamed.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, + 'Unexpected rejection while renaming a task.')) + .post(); } } }); @@ -120,12 +125,11 @@ describe('FirebaseClient subscription', function () { }); taskId = createCommand.getId().getValue(); - client.sendCommand( - createCommand, - () => console.log(`Task '${createCommand.getId().getValue()}' created.`), - fail(done, 'Unexpected error while creating a task.'), - fail(done, 'Unexpected rejection while creating a task.') - ); + client.command(createCommand) + .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); }) .catch(fail(done)); }); @@ -143,7 +147,9 @@ describe('FirebaseClient subscription', function () { const taskIdValue = createCommand.getId().getValue(); let changesCount = 0; - client.subscribe({entity: Task, byIds: taskId}) + client.subscribeTo(Task) + .byId(taskId) + .post() .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { itemAdded.subscribe({ next: item => { @@ -159,12 +165,12 @@ describe('FirebaseClient subscription', function () { withId: taskIdValue, to: UPDATED_NAMES[0] }); - client.sendCommand( - renameCommand, - () => console.log(`Task '${taskIdValue}' renamed for the first time.`), - fail(done, 'Unexpected error while renaming a task.'), - fail(done, 'Unexpected rejection while renaming a task.') - ); + client.command(renameCommand) + .onOk(() => console.log( + `Task '${taskIdValue}' renamed for the first time.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) + .post(); } }); itemRemoved.subscribe({ @@ -185,22 +191,23 @@ describe('FirebaseClient subscription', function () { withId: taskIdValue, to: UPDATED_NAMES[1] }); - client.sendCommand( - renameCommand, - () => console.log(`Task '${taskIdValue}' renamed for the second time.`), - fail(done, 'Unexpected error while renaming a task.'), - fail(done, 'Unexpected rejection while renaming a task.') - ); + client.command(renameCommand) + .onOk(() => console.log( + `Task '${taskIdValue}' renamed for the second time.`)) + .onError(fail(done, + 'Unexpected error while renaming a task.')) + .onRejection(fail(done, + 'Unexpected rejection while renaming a task.')) + .post(); } } } }); - client.sendCommand( - createCommand, - () => console.log(`Task '${taskIdValue}' created.`), - fail(done, 'Unexpected error while creating a task.'), - fail(done, 'Unexpected rejection while creating a task.') - ); + client.command(createCommand) + .onOk(() => console.log(`Task '${taskIdValue}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); }) .catch(fail(done)); }); @@ -212,7 +219,8 @@ describe('FirebaseClient subscription', function () { } }; - client.subscribe({entity: Unknown}) + client.subscribeTo(Unknown) + .post() .then(() => { done(new Error('A malformed subscription should not yield results.')); }) diff --git a/integration-tests/js-tests/test/firebase-client/topic-test.js b/integration-tests/js-tests/test/firebase-client/topic-test.js index 2010829c7..7abc9be12 100644 --- a/integration-tests/js-tests/test/firebase-client/topic-test.js +++ b/integration-tests/js-tests/test/firebase-client/topic-test.js @@ -181,22 +181,10 @@ describe('FirebaseClient subscribes to topic', function () { teardownSubscription(); }); - function buildTopicFor({ids, filters}) { - const topicBuilder = client.newTopic() - .select(UserTasks) - .byIds(ids); - - if (!!filters) { - topicBuilder.where(filters) - } - - return topicBuilder.build(); - } - it('built by IDs and retrieves correct data', (done) => { - const topic = buildTopicFor({ids: [user1.id, user2.id]}); - - client.subscribeTo(topic) + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .post() .then(subscription => { teardownSubscription = subscription.unsubscribe; const userTasksList$ = toListObservable(subscription, compareUserTasks); @@ -214,14 +202,10 @@ describe('FirebaseClient subscribes to topic', function () { }); it('built by IDs and filters and retrieves correct data', (done) => { - const topic = buildTopicFor({ - ids: [user1.id, user2.id], - filters: [ - Filters.eq('task_count', 2) - ] - }); - - client.subscribeTo(topic) + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .where(Filters.eq('task_count', 2)) + .post() .then(subscription => { teardownSubscription = subscription.unsubscribe; const userTasksList$ = toListObservable(subscription, compareUserTasks); @@ -241,14 +225,11 @@ describe('FirebaseClient subscribes to topic', function () { it('built by IDs and filters and updates data correctly when state changes', (done) => { // TODO:2019-08-01:dmytro.dashenkov: Re-enable the test when columns filtering is implemented. done(); - const topic = buildTopicFor({ - ids: [user1.id, user2.id], - filters: [ - Filters.ge('task_count', 2) - ] - }); - client.subscribeTo(topic) + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .where(Filters.ge('task_count', 2)) + .post() .then(subscription => { teardownSubscription = subscription.unsubscribe; const userTasksList$ = toListObservable(subscription, compareUserTasks); diff --git a/integration-tests/js-tests/test/given/users-test-environment.js b/integration-tests/js-tests/test/given/users-test-environment.js index a8a520dbe..8d254a523 100644 --- a/integration-tests/js-tests/test/given/users-test-environment.js +++ b/integration-tests/js-tests/test/given/users-test-environment.js @@ -56,10 +56,14 @@ export class UserTasksTestEnvironment extends TestEnvironment { createTaskFailed = reject; }); createTaskPromises.push(promise); - client.sendCommand(command, () => { - user.tasks.push(taskId); - createTaskAcknowledged(); - }, createTaskFailed); + client.command(command) + .onOk(() => { + user.tasks.push(taskId); + createTaskAcknowledged(); + }) + .onError(createTaskFailed) + .onRejection(createTaskFailed) + .post(); } return Promise.all(createTaskPromises); @@ -78,7 +82,13 @@ export class UserTasksTestEnvironment extends TestEnvironment { const command = new ReassignTask(); command.setId(taskId); command.setNewAssignee(newAssignee); - client.sendCommand(command, () => resolve(), () => reject()); + + // TODO:2019-11-27:dmytro.kuzmin:WIP Try remove these lambdas. + client.command(command) + .onOk(() => resolve()) + .onError(() => reject()) + .onRejection(() => reject()) + .post(); }) } diff --git a/integration-tests/test-app/build.gradle b/integration-tests/test-app/build.gradle index 624e3cbef..4e07e86cf 100644 --- a/integration-tests/test-app/build.gradle +++ b/integration-tests/test-app/build.gradle @@ -18,12 +18,33 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +// TODO:2019-11-27:dmytro.kuzmin:WIP Remove when the necessary configurations are merged into +// `config`. Here and below. +buildscript { + configurations.all { + resolutionStrategy { + //noinspection GroovyAssignabilityCheck + force( + 'commons-cli:commons-cli:1.4' + ) + } + } +} + plugins { id 'java' - id 'org.gretty' version '2.3.1' + id 'org.gretty' version '3.0.1' id "com.github.psxpaul.execfork" version '0.1.12' } +configurations.all { + resolutionStrategy { + force( + 'commons-cli:commons-cli:1.4' + ) + } +} + apply plugin: 'io.spine.tools.spine-model-compiler' apply from: "$rootDir/config/gradle/model-compiler.gradle" diff --git a/pom.xml b/pom.xml index 961674f6e..ed1b2b73d 100644 --- a/pom.xml +++ b/pom.xml @@ -198,52 +198,42 @@ all modules and does not describe the project structure per-subproject. org.gretty gretty-runner-jetty7 - 2.3.1 + 3.0.1 org.gretty gretty-runner-jetty8 - 2.3.1 + 3.0.1 org.gretty gretty-runner-jetty9 - 2.3.1 + 3.0.1 org.gretty gretty-runner-jetty93 - 2.3.1 + 3.0.1 org.gretty gretty-runner-jetty94 - 2.3.1 - - - org.gretty - gretty-runner-tomcat7 - 2.3.1 - - - org.gretty - gretty-runner-tomcat8 - 2.3.1 + 3.0.1 org.gretty gretty-runner-tomcat85 - 2.3.1 + 3.0.1 org.gretty gretty-runner-tomcat9 - 2.3.1 + 3.0.1 org.gretty gretty-starter - 2.3.1 + 3.0.1 org.jacoco From c54d044e2d3e2574a5c2b877b8b4ec9dbbaf8ea2 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 4 Dec 2019 13:41:34 +0200 Subject: [PATCH 23/70] Fix the IDs used in query test --- integration-tests/js-tests/test/firebase-client/query-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/js-tests/test/firebase-client/query-test.js b/integration-tests/js-tests/test/firebase-client/query-test.js index 7ecc7a385..f4005066e 100644 --- a/integration-tests/js-tests/test/firebase-client/query-test.js +++ b/integration-tests/js-tests/test/firebase-client/query-test.js @@ -156,7 +156,7 @@ describe('FirebaseClient executes query built', function () { tests.forEach(test => { it(`${test.message} and returns correct values`, done => { - const ids = test.ids ? test.ids() : undefined; + const ids = test.ids ? test.ids() : toUserIds(users); const filters = test.filters; client.select(UserTasks) From c080f3dc85af20c91401b4069109d78563be3aae Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 4 Dec 2019 13:41:49 +0200 Subject: [PATCH 24/70] Remove temporary test config --- integration-tests/js-tests/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/js-tests/package.json b/integration-tests/js-tests/package.json index d58f21c01..381006284 100644 --- a/integration-tests/js-tests/package.json +++ b/integration-tests/js-tests/package.json @@ -4,7 +4,7 @@ "license": "Apache-2.0", "description": "Tests of a `spine-web` JS library against the Spine-based application.", "scripts": { - "test": "mocha --require @babel/register --recursive --exit --full-trace ./test/firebase-client/query-test.js", + "test": "mocha --require @babel/register --recursive --exit --full-trace ./test", "installLinkedLib": "npm link spine-web" }, "devDependencies": { From 037f6c28c9c461bfc3e753d5ed71bea98d565bb8 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 4 Dec 2019 21:14:32 +0200 Subject: [PATCH 25/70] Add more integration tests for subscriptions --- .../test/firebase-client/subscribe-test.js | 127 +++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index 02750e70d..231680360 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -21,8 +21,12 @@ import assert from 'assert'; import {fail} from '../test-helpers'; import TestEnvironment from './given/test-environment'; +import {TaskRenamed} from '@testProto/spine/web/test/given/events_pb'; import {Task} from '@testProto/spine/web/test/given/task_pb'; import {client} from './given/firebase-client'; +import {Filters} from '@lib/client/actor-request-factory'; +import {AnyPacker} from '@lib/client/any-packer'; +import {Type} from '@lib/client/typed-message'; describe('FirebaseClient subscription', function () { @@ -134,7 +138,7 @@ describe('FirebaseClient subscription', function () { .catch(fail(done)); }); - it('retrieves updates by id', done => { + it('retrieves updates by ID', done => { const expectedChangesCount = 2; const INITIAL_TASK_NAME = 'Initial task name'; const UPDATED_NAMES = ['Renamed once', 'Renamed twice']; @@ -212,7 +216,126 @@ describe('FirebaseClient subscription', function () { .catch(fail(done)); }); - it('fails for a malformed entity type', done => { + // TODO:2019-11-27:dmytro.kuzmin:WIP Resolve presence/absence of backticks in string messages, + // formatting of chained method calls, at least in the modified files. + it('is notified when the entity no longer matches the subscription criteria', done => { + const initialTaskName = 'Initial task name'; + const nameAfterRenamed = 'Renamed task'; + + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: initialTaskName + }); + const taskIdValue = createCommand.getId().getValue(); + + client.subscribeTo(Task) + .where(Filters.eq("name", initialTaskName)) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log(`Retrieved new task '${id}'.`); + if (taskIdValue === id) { + assert.equal( + initialTaskName, item.getName(), + `Task is named "${item.getName()}", + expected "${initialTaskName}".` + ); + } + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskIdValue, + to: nameAfterRenamed + }); + client.command(renameCommand) + .onOk(() => console.log( + `Task '${taskIdValue}' is renamed to '${nameAfterRenamed}'.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) + .post(); + } + }); + itemRemoved.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log('Task removed'); + assert.equal( + taskIdValue, id, + `A wrong Task item is removed, expected the task with ID + "${taskIdValue}", received the task with ID "${id}".` + ); + unsubscribe(); + done(); + } + }); + itemChanged.subscribe({ + next: fail(done, 'The `itemChanged` call is unexpected within this test.') + }); + client.command(createCommand) + .onOk(() => console.log(`Task '${taskIdValue}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + }) + .catch(fail(done)); + }); + + it('retrieves event updates', done => { + const initialTaskName = "The initial task name"; + const updatedTaskName = "Renamed task"; + + let taskId; + + client.subscribeToEvent(TaskRenamed) + .where(Filters.eq("name", updatedTaskName)) + .post() + .then(({eventEmitted, unsubscribe}) => { + eventEmitted.subscribe({ + next: event => { + const packedMessage = event.getMessage(); + const taskRenamedType = Type.forClass(TaskRenamed); + const message = AnyPacker.unpack(packedMessage).as(taskRenamedType); + const theTaskId = message.getId().getValue(); + assert.equal( + taskId, theTaskId, + `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` + ); + const newTaskName = message.getName(); + assert.equal( + updatedTaskName, newTaskName, + `Expected the new task name to be ${updatedTaskName}, got + ${newTaskName} instead.` + ); + unsubscribe(); + done(); + } + }); + }); + + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: initialTaskName + }); + taskId = createCommand.getId().getValue(); + client.command(createCommand) + .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskId, + to: updatedTaskName + }); + client.command(renameCommand) + .onOk(() => console.log(`Task '${taskId}' renamed.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, + 'Unexpected rejection while renaming a task.')) + .post(); + }); + + it('fails for a malformed type', done => { const Unknown = class { static typeUrl() { return 'spine.web/fails.malformed.type' From f428151756c51cb0ea24bd36c35df2d687953733 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 4 Dec 2019 21:20:42 +0200 Subject: [PATCH 26/70] Store empty subscription updates as real `null`s in Firebase database Previously, we added the node with the string value of `"null"` to the database on empty subscription update. This led to the issue when the `itemChanged` instead of `itemRemoved` callback was triggered on the client side when the entity state became no longer matching the subscription criteria. Now, the real `null` is stored to the database on empty subscription update, which in terms of Firebase means the child node is deleted. Thus, the "correct" `itemRemoved` callback is triggered. Also, a small bug with trying to store the `null` values with the `ImmutableMap` collector is fixed. --- .../io/spine/web/firebase/StoredJson.java | 14 -------- .../firebase/subscription/UpdatePayload.java | 33 +++++++++++++++---- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java b/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java index 717541109..aee03a6b8 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java @@ -62,20 +62,6 @@ public static StoredJson from(String value) { : new StoredJson(value); } - /** - * Tries to encode the given message into a {@code StoredJson}. - * - *

Returns the {@code null} JSON value if the given message is {@code null}; - * - * @param message - * message to encode - */ - public static StoredJson encodeOrNull(@Nullable Message message) { - return message != null - ? encode(message) - : NULL_JSON; - } - /** * Encodes the given message into a {@code StoredJson}. * diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java index a5b5d07e9..83b52558f 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java @@ -24,6 +24,7 @@ import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.protobuf.Any; +import com.google.protobuf.Empty; import com.google.protobuf.Message; import io.spine.client.EntityStateUpdate; import io.spine.client.SubscriptionUpdate; @@ -48,6 +49,10 @@ * *

Consists of entities or events, which ought to be written into the Firebase database under * a certain path. + * + *

The entity state updates that render entity state + * {@linkplain EntityStateUpdate#getNoLongerMatching() no longer matching} are stored as + * {@link Empty}. */ final class UpdatePayload { @@ -63,9 +68,9 @@ final class UpdatePayload { */ private static final HashFunction keyHashFunction = murmur3_128(); - private final ImmutableMap messages; + private final ImmutableMap messages; - private UpdatePayload(ImmutableMap messages) { + private UpdatePayload(ImmutableMap messages) { this.messages = messages; } @@ -83,15 +88,15 @@ private static UpdatePayload entityUpdates(SubscriptionUpdate update) { .getEntityUpdates() .getUpdateList() .stream() - .collect(toHashTable(EntityStateUpdate::getId, UpdatePayload::messageOrNull)); + .collect(toHashTable(EntityStateUpdate::getId, UpdatePayload::messageOrEmpty)); checkArgument(!messages.isEmpty(), "Empty subscription update: %s", update); return new UpdatePayload(messages); } - private static @Nullable Message messageOrNull(EntityStateUpdate update) { + private static Message messageOrEmpty(EntityStateUpdate update) { return update.hasState() ? unpack(update.getState()) - : null; + : Empty.getDefaultInstance(); } private static UpdatePayload eventUpdates(SubscriptionUpdate update) { @@ -136,10 +141,26 @@ private static String key(Message id) { */ NodeValue asNodeValue() { NodeValue node = NodeValue.empty(); - messages.forEach((id, message) -> node.addChild(id, StoredJson.encodeOrNull(message))); + messages.forEach((id, message) -> addChildOrNull(node, id, message)); return node; } + /** + * Adds an {@code } key-value pair as a child of the given node. + * + *

If the message is {@link Empty}, adds a {@code null} child under the given key, deleting + * it from the database. + */ + private static void addChildOrNull(NodeValue node, String id, Message message) { + boolean isEmpty = Empty.getDefaultInstance() + .equals(message); + if (isEmpty) { + node.addNullChild(id); + } else { + node.addChild(id, StoredJson.encode(message)); + } + } + /** * An identity function for {@link Event}-to-{@link Message} conversion. * From 5be8d5be87e0a76fec6e2cf986d2f502379b0f18 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 4 Dec 2019 22:22:57 +0200 Subject: [PATCH 27/70] Add tests for observing events generated by the command --- .../test/firebase-client/send-command-test.js | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index ae7ec98a7..407b9ce65 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -21,9 +21,12 @@ import assert from 'assert'; import TestEnvironment from './given/test-environment'; import {CommandHandlingError, CommandValidationError, ConnectionError} from '@lib/index'; +import {TaskCreated} from '@testProto/spine/web/test/given/events_pb'; import {Task} from '@testProto/spine/web/test/given/task_pb'; import {fail} from '../test-helpers'; import {client, initClient} from './given/firebase-client'; +import {AnyPacker} from '@lib/client/any-packer'; +import {Type} from '@lib/client/typed-message'; describe('FirebaseClient command sending', function () { @@ -116,4 +119,83 @@ describe('FirebaseClient command sending', function () { .onRejection(fail(done, 'A command was rejected when an error was expected.')) .post(); }); + + it('allows to observe the produced events of a given type', done => { + // TODO:2019-11-27:dmytro.kuzmin:WIP Enable when the latest `core-java` is published + // enabling filtering events by `context.*some_field*`. + done(); + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: 'Implement Spine Web JS client tests', + describedAs: 'Spine Web need integration tests' + }); + + const taskId = command.getId(); + + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .observe(TaskCreated) + .post() + .then(({eventEmitted, unsubscribe}) => { + eventEmitted.subscribe({ + next: event => { + const packedMessage = event.getMessage(); + const taskCreatedType = Type.forClass(TaskCreated); + const message = AnyPacker.unpack(packedMessage).as(taskCreatedType); + const theTaskId = message.getId().getValue(); + assert.equal( + taskId, theTaskId, + `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` + ); + const newTaskName = message.getName(); + assert.equal( + updatedTaskName, newTaskName, + `Expected the new task name to be ${updatedTaskName}, got + ${newTaskName} instead.` + ); + const origin = event.getPastMessage().getMessage(); + const originType = origin.getTypeUrl(); + const expectedOriginType = taskCreatedType.url().value(); + assert.equal( + expectedOriginType, originType, + `Expected origin to be of type ${expectedOriginType}, got ${originType} + instead.` + ); + unsubscribe(); + done(); + } + }); + }); + }); + + it.only('fails when trying to observe a malformed event type', done => { + const Unknown = class { + static typeUrl() { + return 'spine.web/fails.malformed.type' + } + }; + + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: 'Implement Spine Web JS client tests', + describedAs: 'Spine Web need integration tests' + }); + + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .observe(Unknown) + .post() + .then(() => { + done(new Error('An attempt to observe a malformed event type should not yield ' + + 'results.')); + }) + .catch(error => { + console.log('Error:'); + console.log(error); + assert.ok(true); + done(); + }); + }); }); From d5a5f78493c466b8340e005769ecc5f4245d31a7 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 4 Dec 2019 22:23:10 +0200 Subject: [PATCH 28/70] Fix an issue with ID packing --- client-js/main/client/client-request.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 5b1d6cab2..81ade4f2b 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -23,10 +23,11 @@ import {Message} from 'google-protobuf'; import {CompositeFilter, Filter} from '../proto/spine/client/filters_pb'; import {OrderBy} from '../proto/spine/client/query_pb'; -import {Command} from '../proto/spine/core/command_pb'; +import {Command, CommandId} from '../proto/spine/core/command_pb'; import {MessageId, Origin} from '../proto/spine/core/diagnostics_pb'; import {AnyPacker} from "./any-packer"; import {Filters} from "./actor-request-factory"; +import {Type} from "./typed-message"; /** * @abstract @@ -400,7 +401,8 @@ export class CommandRequest extends ClientRequest{ const result = new Origin(); const messageId = new MessageId(); - const packedId = AnyPacker.pack(command.getId()); + const commandIdType = Type.forClass(CommandId); + const packedId = AnyPacker.pack(command.getId()).as(commandIdType); messageId.setId(packedId); const typeUrl = command.getMessage().getTypeUrl(); messageId.setTypeUrl(typeUrl); From e15280a6d0f4d7141bf93774471fe3b6665c8350 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 4 Dec 2019 22:40:55 +0200 Subject: [PATCH 29/70] Remove an unused import --- firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java | 1 - 1 file changed, 1 deletion(-) diff --git a/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java b/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java index aee03a6b8..eede722ed 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java @@ -28,7 +28,6 @@ import com.google.protobuf.Message; import io.spine.protobuf.AnyPacker; import io.spine.value.StringTypeValue; -import org.checkerframework.checker.nullness.qual.Nullable; import static com.google.common.base.Preconditions.checkNotNull; import static io.spine.json.Json.toCompactJson; From 9f771a09d503f65d266080fdaab25f13b892ba61 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 4 Dec 2019 22:41:17 +0200 Subject: [PATCH 30/70] Remove excessive test logging --- .../js-tests/test/firebase-client/send-command-test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index 407b9ce65..c30043b96 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -192,8 +192,6 @@ describe('FirebaseClient command sending', function () { 'results.')); }) .catch(error => { - console.log('Error:'); - console.log(error); assert.ok(true); done(); }); From dfe3912db63e8271fff8306504d3126ec417f484 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Thu, 5 Dec 2019 19:31:38 +0200 Subject: [PATCH 31/70] Fix issue with `null` grand origin specified to the filter The grand origin in the command context is never actually `null` on the server. It either has some value or equals to the default instance. So for the filter value to equal the value in the corresponding command context on the server, we need to set the origin in a same way. --- client-js/main/client/client-request.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 81ade4f2b..e038828c6 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -380,7 +380,7 @@ export class CommandRequest extends ClientRequest{ this._observedTypes.forEach(type => { const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); const promise = this._client.subscribeToEvent(type) - .where([originFilter]) + .where(originFilter) .post(); promises.push(promise); }); @@ -408,7 +408,10 @@ export class CommandRequest extends ClientRequest{ messageId.setTypeUrl(typeUrl); result.setMessage(messageId); - const grandOrigin = command.getContext().getOrigin(); + let grandOrigin = command.getContext().getOrigin(); + if (!grandOrigin) { + grandOrigin = new Origin(); + } result.setGrandOrigin(grandOrigin); const actorContext = command.getContext().getActorContext(); From c16ead9a83b3ab1eb52b4ba98c4beb21f85ff117 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Thu, 5 Dec 2019 19:32:16 +0200 Subject: [PATCH 32/70] Update Spine version to `1.2.9` --- client-js/package.json | 2 +- integration-tests/js-tests/package.json | 2 +- license-report.md | 186 ++++++++---------------- pom.xml | 8 +- version.gradle | 4 +- 5 files changed, 67 insertions(+), 135 deletions(-) diff --git a/client-js/package.json b/client-js/package.json index 2e483ded7..6315be937 100644 --- a/client-js/package.json +++ b/client-js/package.json @@ -1,6 +1,6 @@ { "name": "spine-web", - "version": "1.2.6", + "version": "1.2.9", "license": "Apache-2.0", "description": "A JS client for interacting with Spine applications.", "homepage": "https://spine.io", diff --git a/integration-tests/js-tests/package.json b/integration-tests/js-tests/package.json index 381006284..7ba3e694d 100644 --- a/integration-tests/js-tests/package.json +++ b/integration-tests/js-tests/package.json @@ -1,6 +1,6 @@ { "name": "client-js-tests", - "version": "1.2.6", + "version": "1.2.9", "license": "Apache-2.0", "description": "Tests of a `spine-web` JS library against the Spine-based application.", "scripts": { diff --git a/license-report.md b/license-report.md index 4c80aefef..449fa9245 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client-js:1.2.6` +# Dependencies of `io.spine:spine-client-js:1.2.9` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -376,10 +376,10 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Dec 03 12:59:56 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:15:44 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -#NPM dependencies of `spine-web@1.2.6` +#NPM dependencies of `spine-web@1.2.9` ## `Production` dependencies: @@ -410,7 +410,7 @@ This report was generated on **Tue Dec 03 12:59:56 EET 2019** using [Gradle-Lice 1. **safer-buffer@2.1.2** * Licenses: MIT * Repository: [https://github.com/ChALkeR/safer-buffer](https://github.com/ChALkeR/safer-buffer) -1. **spine-web@1.2.6** +1. **spine-web@1.2.9** * Licenses: Apache-2.0 * Repository: [https://github.com/SpineEventEngine/web](https://github.com/SpineEventEngine/web) 1. **tslib@1.10.0** @@ -2275,7 +2275,7 @@ This report was generated on **Tue Dec 03 12:59:56 EET 2019** using [Gradle-Lice 1. **spdx-satisfies@4.0.1** * Licenses: MIT * Repository: [https://github.com/kemitchell/spdx-satisfies.js](https://github.com/kemitchell/spdx-satisfies.js) -1. **spine-web@1.2.6** +1. **spine-web@1.2.9** * Licenses: Apache-2.0 * Repository: [https://github.com/SpineEventEngine/web](https://github.com/SpineEventEngine/web) 1. **split-string@3.1.0** @@ -2547,12 +2547,12 @@ This report was generated on **Tue Dec 03 12:59:56 EET 2019** using [Gradle-Lice * Repository: [https://github.com/bcoe/yargs](https://github.com/bcoe/yargs) -This report was generated on **Tue Dec 03 2019 12:59:59 GMT+0200 (Eastern European Standard Time)** using [NPM License Checker](https://github.com/davglass/license-checker) library. +This report was generated on **Thu Dec 05 2019 13:15:47 GMT+0200 (Eastern European Standard Time)** using [NPM License Checker](https://github.com/davglass/license-checker) library. -# Dependencies of `io.spine.gcloud:spine-firebase-web:1.2.6` +# Dependencies of `io.spine.gcloud:spine-firebase-web:1.2.9` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -3395,12 +3395,12 @@ This report was generated on **Tue Dec 03 2019 12:59:59 GMT+0200 (Eastern Europe The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Dec 03 13:00:14 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:16:39 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-js-tests:1.2.6` +# Dependencies of `io.spine:spine-js-tests:1.2.9` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -3806,12 +3806,12 @@ This report was generated on **Tue Dec 03 13:00:14 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:17:11 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-test-app:1.2.6` +# Dependencies of `io.spine:spine-test-app:1.2.9` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -4347,9 +4347,9 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [http://github.com/square/javapoet/](http://github.com/square/javapoet/) * **POM License: Apache 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** commons-cli **Name:** commons-cli **Version:** 1.2 - * **Project URL:** [http://commons.apache.org/cli/](http://commons.apache.org/cli/) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group:** commons-cli **Name:** commons-cli **Version:** 1.4 + * **Project URL:** [http://commons.apache.org/proper/commons-cli/](http://commons.apache.org/proper/commons-cli/) + * **POM License: Apache License, Version 2.0** - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group:** commons-codec **Name:** commons-codec **Version:** 1.10 * **Project URL:** [http://commons.apache.org/proper/commons-codec/](http://commons.apache.org/proper/commons-codec/) @@ -4564,18 +4564,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice 1. **Group:** org.apache.maven **Name:** maven-plugin-api **Version:** 3.2.1 * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.servicemix.bundles **Name:** org.apache.servicemix.bundles.bcprov-jdk16 **Version:** 1.46_3 - * **Manifest Project URL:** [http://www.apache.org/](http://www.apache.org/) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat **Name:** tomcat-annotations-api **Version:** 7.0.92 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat **Name:** tomcat-annotations-api **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat **Name:** tomcat-annotations-api **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4584,14 +4572,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-core **Version:** 7.0.92 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-core **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-core **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4600,14 +4580,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-el **Version:** 7.0.92 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-el **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-el **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4616,14 +4588,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-jasper **Version:** 7.0.92 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-jasper **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-jasper **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4632,18 +4596,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-logging-log4j **Version:** 7.0.92 - * **POM Project URL:** [http://tomcat.apache.org/](http://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-websocket **Version:** 7.0.92 - * **POM Project URL:** [http://tomcat.apache.org/](http://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-websocket **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-websocket **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4656,6 +4608,10 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **POM License: The Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group:** org.bouncycastle **Name:** bcprov-jdk15on **Version:** 1.60 + * **POM Project URL:** [http://www.bouncycastle.org/java.html](http://www.bouncycastle.org/java.html) + * **POM License: Bouncy Castle Licence** - [http://www.bouncycastle.org/licence.html](http://www.bouncycastle.org/licence.html) + 1. **Group:** org.checkerframework **Name:** checker-compat-qual **Version:** 2.5.3 * **POM Project URL:** [https://checkerframework.org](https://checkerframework.org) * **POM License: GNU General Public License, version 2 (GPL2), with the classpath exception** - [http://www.gnu.org/software/classpath/license.html](http://www.gnu.org/software/classpath/license.html) @@ -4676,12 +4632,16 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM License: GNU General Public License, version 2 (GPL2), with the classpath exception** - [http://www.gnu.org/software/classpath/license.html](http://www.gnu.org/software/classpath/license.html) * **POM License: The MIT License** - [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT) -1. **Group:** org.codehaus.groovy **Name:** groovy **Version:** 2.4.15 - * **POM Project URL:** [http://groovy-lang.org](http://groovy-lang.org) +1. **Group:** org.codehaus.groovy **Name:** groovy **Version:** 2.5.8 + * **POM Project URL:** [https://groovy-lang.org](https://groovy-lang.org) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.codehaus.groovy **Name:** groovy-json **Version:** 2.4.15 - * **POM Project URL:** [http://groovy-lang.org](http://groovy-lang.org) +1. **Group:** org.codehaus.groovy **Name:** groovy-cli-commons **Version:** 2.5.8 + * **POM Project URL:** [https://groovy-lang.org](https://groovy-lang.org) + * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group:** org.codehaus.groovy **Name:** groovy-json **Version:** 2.5.8 + * **POM Project URL:** [https://groovy-lang.org](https://groovy-lang.org) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group:** org.codehaus.mojo **Name:** animal-sniffer-annotations **Version:** 1.18 @@ -5223,63 +5183,55 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [http://jstl.java.net](http://jstl.java.net) * **POM License: CDDL + GPLv2 with classpath exception** - [http://glassfish.dev.java.net/nonav/public/CDDL+GPL.html](http://glassfish.dev.java.net/nonav/public/CDDL+GPL.html) -1. **Group:** org.gretty **Name:** gretty-core **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-core **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty7 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty7 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty8 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty8 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty9 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty9 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty93 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty93 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty94 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty94 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-tomcat **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat7 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-tomcat85 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat8 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-tomcat8plus **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat85 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-tomcat9 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat8plus **Version:** 2.3.1 - * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) - * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) - -1. **Group:** org.gretty **Name:** gretty-runner-tomcat9 **Version:** 2.3.1 - * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) - * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) - -1. **Group:** org.gretty **Name:** gretty-starter **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-starter **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) @@ -5352,12 +5304,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://github.com/ota4j-team/opentest4j](https://github.com/ota4j-team/opentest4j) * **POM License: The Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.ow2.asm **Name:** asm **Version:** 7.0 - * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) - * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) - * **POM License: BSD** - [http://asm.ow2.org/license.html](http://asm.ow2.org/license.html) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.ow2.asm **Name:** asm **Version:** 7.1 * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) @@ -5371,12 +5317,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM License: BSD-3-Clause** - [https://asm.ow2.io/license.html](https://asm.ow2.io/license.html) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.ow2.asm **Name:** asm-analysis **Version:** 7.0 - * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) - * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) - * **POM License: BSD** - [http://asm.ow2.org/license.html](http://asm.ow2.org/license.html) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.ow2.asm **Name:** asm-analysis **Version:** 7.2 * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) * **Manifest License:** BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) @@ -5384,12 +5324,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM License: BSD-3-Clause** - [https://asm.ow2.io/license.html](https://asm.ow2.io/license.html) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.ow2.asm **Name:** asm-commons **Version:** 7.0 - * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) - * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) - * **POM License: BSD** - [http://asm.ow2.org/license.html](http://asm.ow2.org/license.html) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.ow2.asm **Name:** asm-commons **Version:** 7.2 * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) * **Manifest License:** BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) @@ -5397,12 +5331,6 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM License: BSD-3-Clause** - [https://asm.ow2.io/license.html](https://asm.ow2.io/license.html) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.ow2.asm **Name:** asm-tree **Version:** 7.0 - * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) - * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) - * **POM License: BSD** - [http://asm.ow2.org/license.html](http://asm.ow2.org/license.html) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.ow2.asm **Name:** asm-tree **Version:** 7.2 * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) * **Manifest License:** BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) @@ -5431,23 +5359,27 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **Manifest Project URL:** [http://code.google.com/p/google-guice/](http://code.google.com/p/google-guice/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.springframework **Name:** spring-aop **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-aop **Version:** 5.0.6.RELEASE + * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) + * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +1. **Group:** org.springframework **Name:** spring-beans **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework **Name:** spring-beans **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-context **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework **Name:** spring-context **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-core **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework **Name:** spring-core **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-expression **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework **Name:** spring-expression **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-jcl **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) @@ -5455,16 +5387,16 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://github.com/spring-projects/spring-loaded](https://github.com/spring-projects/spring-loaded) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.springframework.boot **Name:** spring-boot **Version:** 1.5.12.RELEASE - * **POM Project URL:** [http://projects.spring.io/spring-boot/](http://projects.spring.io/spring-boot/) +1. **Group:** org.springframework.boot **Name:** spring-boot **Version:** 2.0.2.RELEASE + * **POM Project URL:** [https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot](https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework.boot **Name:** spring-boot-autoconfigure **Version:** 1.5.12.RELEASE - * **POM Project URL:** [http://projects.spring.io/spring-boot/](http://projects.spring.io/spring-boot/) +1. **Group:** org.springframework.boot **Name:** spring-boot-autoconfigure **Version:** 2.0.2.RELEASE + * **POM Project URL:** [https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-autoconfigure](https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-autoconfigure) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework.boot **Name:** spring-boot-devtools **Version:** 1.5.12.RELEASE - * **POM Project URL:** [http://projects.spring.io/spring-boot/](http://projects.spring.io/spring-boot/) +1. **Group:** org.springframework.boot **Name:** spring-boot-devtools **Version:** 2.0.2.RELEASE + * **POM Project URL:** [https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-devtools](https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-devtools) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 1. **Group:** org.threeten **Name:** threetenbp **Version:** 1.3.3 @@ -5477,12 +5409,12 @@ This report was generated on **Tue Dec 03 13:00:22 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Dec 03 13:01:36 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:18:19 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-testutil-web:1.2.6` +# Dependencies of `io.spine:spine-testutil-web:1.2.9` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -5949,12 +5881,12 @@ This report was generated on **Tue Dec 03 13:01:36 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Dec 03 13:01:46 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:18:28 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-web:1.2.6` +# Dependencies of `io.spine:spine-web:1.2.9` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -6497,4 +6429,4 @@ This report was generated on **Tue Dec 03 13:01:46 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Dec 03 13:01:56 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Thu Dec 05 13:18:39 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index ed1b2b73d..e11a8c23a 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-web -1.2.6 +1.2.9 2015 @@ -76,7 +76,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-server - 1.2.6 + 1.2.9 compile @@ -100,7 +100,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-testutil-client - 1.2.6 + 1.2.9 test @@ -168,7 +168,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-client - 1.2.6 + 1.2.9 io.spine.tools diff --git a/version.gradle b/version.gradle index c990314f5..ba0c60158 100644 --- a/version.gradle +++ b/version.gradle @@ -19,10 +19,10 @@ */ ext { - spineVersion = '1.2.6' + spineVersion = '1.2.9' spineBaseVersion = '1.2.3' - versionToPublish = '1.2.6' + versionToPublish = '1.2.9' versionToPublishJs = versionToPublish servletApiVersion = '3.1.0' From a9011c8052a3c2b71cf7b2e8042737c19e5a75ab Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Thu, 5 Dec 2019 19:34:19 +0200 Subject: [PATCH 33/70] Resolve "Send command" test errors Also tried to address the overall flakiness of integration tests. --- .../test/firebase-client/send-command-test.js | 24 +++++++++---------- .../test/firebase-client/subscribe-test.js | 13 +++++----- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index c30043b96..680476355 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -21,6 +21,7 @@ import assert from 'assert'; import TestEnvironment from './given/test-environment'; import {CommandHandlingError, CommandValidationError, ConnectionError} from '@lib/index'; +import {CreateTask} from '@testProto/spine/web/test/given/commands_pb'; import {TaskCreated} from '@testProto/spine/web/test/given/events_pb'; import {Task} from '@testProto/spine/web/test/given/task_pb'; import {fail} from '../test-helpers'; @@ -31,7 +32,7 @@ import {Type} from '@lib/client/typed-message'; describe('FirebaseClient command sending', function () { // Big timeout allows to receive model state changes during tests. - this.timeout(5000); + this.timeout(15000); it('completes with success', done => { @@ -121,18 +122,17 @@ describe('FirebaseClient command sending', function () { }); it('allows to observe the produced events of a given type', done => { - // TODO:2019-11-27:dmytro.kuzmin:WIP Enable when the latest `core-java` is published - // enabling filtering events by `context.*some_field*`. - done(); + const taskName = 'Implement Spine Web JS client tests'; const command = TestEnvironment.createTaskCommand({ withPrefix: 'spine-web-test-send-command', - named: 'Implement Spine Web JS client tests', + named: taskName, describedAs: 'Spine Web need integration tests' }); const taskId = command.getId(); client.command(command) + .onOk(() => console.log('Create task command posted.')) .onError(fail(done)) .onRejection(fail(done)) .observe(TaskCreated) @@ -148,15 +148,15 @@ describe('FirebaseClient command sending', function () { taskId, theTaskId, `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` ); - const newTaskName = message.getName(); + const theTaskName = message.getName(); assert.equal( - updatedTaskName, newTaskName, - `Expected the new task name to be ${updatedTaskName}, got - ${newTaskName} instead.` + taskName, theTaskName, + `Expected the task name to be ${taskName}, got ${theTaskName} instead.` ); - const origin = event.getPastMessage().getMessage(); + const origin = event.getContext().getPastMessage().getMessage(); const originType = origin.getTypeUrl(); - const expectedOriginType = taskCreatedType.url().value(); + const createTaskType = Type.forClass(CreateTask); + const expectedOriginType = createTaskType.url().value(); assert.equal( expectedOriginType, originType, `Expected origin to be of type ${expectedOriginType}, got ${originType} @@ -169,7 +169,7 @@ describe('FirebaseClient command sending', function () { }); }); - it.only('fails when trying to observe a malformed event type', done => { + it('fails when trying to observe a malformed event type', done => { const Unknown = class { static typeUrl() { return 'spine.web/fails.malformed.type' diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index 231680360..61da89fc8 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -285,9 +285,14 @@ describe('FirebaseClient subscription', function () { const updatedTaskName = "Renamed task"; let taskId; + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: initialTaskName + }); + taskId = createCommand.getId().getValue(); client.subscribeToEvent(TaskRenamed) - .where(Filters.eq("name", updatedTaskName)) + .where([Filters.eq("id.value", taskId), Filters.eq("name", updatedTaskName)]) .post() .then(({eventEmitted, unsubscribe}) => { eventEmitted.subscribe({ @@ -311,12 +316,6 @@ describe('FirebaseClient subscription', function () { } }); }); - - const createCommand = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: initialTaskName - }); - taskId = createCommand.getId().getValue(); client.command(createCommand) .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) .onError(fail(done, 'Unexpected error while creating a task.')) From b1e2bbdb6036ecd118c87bdd0cdaf0987fdd93fc Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Thu, 5 Dec 2019 20:10:10 +0200 Subject: [PATCH 34/70] Post command after the observed events subscriptions are done --- client-js/main/client/client-request.js | 2 +- .../js-tests/test/firebase-client/send-command-test.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index e038828c6..16661e6ad 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -375,7 +375,6 @@ export class CommandRequest extends ClientRequest{ const command = this._requestFactory.command().create(this._commandMessage); const onAck = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; - this._client.post(command, onAck); const promises = []; this._observedTypes.forEach(type => { const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); @@ -387,6 +386,7 @@ export class CommandRequest extends ClientRequest{ if (promises.length === 1) { return promises[0]; } + this._client.post(command, onAck); return Promise.all(promises); } diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index 680476355..03a418fd3 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -132,7 +132,6 @@ describe('FirebaseClient command sending', function () { const taskId = command.getId(); client.command(command) - .onOk(() => console.log('Create task command posted.')) .onError(fail(done)) .onRejection(fail(done)) .observe(TaskCreated) From ca6bda69682c668ff5b99d7cf0d4441d7decebcf Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Fri, 6 Dec 2019 15:49:34 +0200 Subject: [PATCH 35/70] Add a test for query request creation --- client-js/main/client/client-request.js | 17 +- client-js/test/client/query-request-test.js | 231 ++++++++++++++++++++ 2 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 client-js/test/client/query-request-test.js diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 16661e6ad..8d5a75607 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -98,7 +98,7 @@ class FilteringRequest extends ClientRequest { } /** - * @param {!String[]} fieldNames + * @param {!String|String[]} fieldNames * @return {this} self for method chaining */ withMask(fieldNames) { @@ -191,13 +191,20 @@ export class QueryRequest extends FilteringRequest { return this._self(); } + /** + * @return {spine.client.Query} + */ + query() { + return this._builder().build(); + } + /** * @return {Promise<[]>} * * @template a Protobuf type of entities being the target of a query */ run() { - const query = this._builder().build(); + const query = this.query(); return this._client.read(query); } @@ -225,13 +232,17 @@ export class QueryRequest extends FilteringRequest { */ class SubscribingRequest extends FilteringRequest { + topic() { + return this._builder().build(); + } + /** * @return {Promise | EventSubscriptionObject>} * * @template a Protobuf type of entities being the target of a subscription */ post() { - const topic = this._builder().build(); + const topic = this.topic(); return this._subscribe(topic); } diff --git a/client-js/test/client/query-request-test.js b/client-js/test/client/query-request-test.js new file mode 100644 index 000000000..f7b452543 --- /dev/null +++ b/client-js/test/client/query-request-test.js @@ -0,0 +1,231 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +import assert from 'assert'; +import uuid from 'uuid'; +import {ActorProvider, ActorRequestFactory, Filters} from '@lib/client/actor-request-factory'; +import {AnyPacker} from '@lib/client/any-packer'; +import {QueryRequest} from "@lib/client/client-request"; +import {Duration} from "@lib/client/time-utils"; +import {Type} from "@lib/client/typed-message"; +import {OrderBy} from '@proto/spine/client/query_pb'; +import {Task, TaskId} from '@testProto/spine/test/js/task_pb'; +import {MockClient} from "./test-helpers"; + +describe('QueryRequest', function () { + + const timeoutDuration = new Duration({seconds: 5}); + this.timeout(timeoutDuration.inMs()); + + const targetType = Task; + const clientStub = new MockClient(); + const actorRequestFactory = new ActorRequestFactory(new ActorProvider()); + + const targetTypeUrl = Type.forClass(targetType).url().value(); + + let request; + + beforeEach(done => { + request = new QueryRequest(targetType, clientStub, actorRequestFactory); + done(); + }); + + it('creates a `select all` query', done => { + const query = request.query(); + const target = query.getTarget(); + assertIsIncludeAll(target); + done(); + }); + + it('creates a query filtering entities by a single ID', done => { + const taskId = new TaskId(); + const idValue = uuid.v4(); + taskId.setValue(idValue); + + const query = request.byId(taskId).query(); + const target = query.getTarget(); + assertTargetTypeEquals(target); + const idFilter = target.getFilters().getIdFilter(); + const idList = idFilter.getIdList(); + const length = idList.length; + assert.equal( + 1, length, + `Expected the ID list to contain a single ID, the actual length: ${length}.` + ); + const taskIdType = Type.forClass(TaskId); + const targetId = AnyPacker.unpack(idList[0]).as(taskIdType); + const actualId = targetId.getValue(); + assert.equal( + idValue, actualId, + `Unexpected target ID ${actualId}, expected: ${idValue}.` + ); + done(); + }); + + it('creates a query filtering entities by a group of IDs', done => { + const taskId1 = new TaskId(); + const idValue1 = uuid.v4(); + taskId1.setValue(idValue1); + const taskId2 = new TaskId(); + const idValue2 = uuid.v4(); + taskId2.setValue(idValue2); + + const query = request.byId([taskId1, taskId2]).query(); + const target = query.getTarget(); + assertTargetTypeEquals(target); + const idFilter = target.getFilters().getIdFilter(); + const idList = idFilter.getIdList(); + const length = idList.length; + assert.equal( + 2, length, + `Expected the ID list to contain two IDs, the actual length: ${length}.` + ); + done(); + }); + + it('ignores a `null` ID specified to the `byId` method', done => { + const query = request.byId(null).query(); + const target = query.getTarget(); + assertIsIncludeAll(target); + done(); + }); + + it('ignores an empty array of IDs specified to the `byId` method', done => { + const query = request.byId([]).query(); + const target = query.getTarget(); + assertIsIncludeAll(target); + done(); + }); + + it('creates a query filtering entities by a single filter', done => { + const filter = Filters.eq('name', 'some task name'); + const query = request.where(filter).query(); + const target = query.getTarget(); + const compositeFilters = target.getFilters().getFilterList(); + const compositeFiltersLength = compositeFilters.length; + assert.equal( + 1, compositeFiltersLength, + `Expected the composite filter list to contain a single filter, the actual + length: ${compositeFiltersLength}.` + ); + const filters = compositeFilters[0].getFilterList(); + const length = filters.length; + assert.equal( + 1, length, + `Expected the filter list to contain a single filter, the actual length: ${length}.` + ); + const targetFilter = filters[0]; + assert.equal( + filter, targetFilter, + `Unexpected filter value ${targetFilter}, expected: ${filter}.` + ); + done(); + }); + + it('creates a query filtering entities by a group of filters', done => { + const filter1 = Filters.eq('name', 'some task name'); + const filter2 = Filters.eq('description', 'some task description'); + const query = request.where([filter1, filter2]).query(); + const target = query.getTarget(); + const compositeFilters = target.getFilters().getFilterList(); + const compositeFiltersLength = compositeFilters.length; + assert.equal( + 1, compositeFiltersLength, + `Expected the composite filter list to contain a single filter, the actual + length: ${compositeFiltersLength}.` + ); + const filters = compositeFilters[0].getFilterList(); + const length = filters.length; + assert.equal( + 2, length, + `Expected the filter list to contain two filters, the actual length: ${length}.` + ); + done(); + }); + + it('ignores a `null` filter specified to the `where` method', done => { + const query = request.where(null).query(); + const target = query.getTarget(); + assertIsIncludeAll(target); + done(); + }); + + it('ignores an empty filter list specified to the `where` method', done => { + const query = request.where([]).query(); + const target = query.getTarget(); + assertIsIncludeAll(target); + done(); + }); + + it('allows to set a field mask', done => { + const fields = ['id', 'name', 'description']; + const query = request.withMask(fields).query(); + const responseFormat = query.getFormat(); + const fieldMask = responseFormat.getFieldMask(); + const pathList = fieldMask.getPathsList(); + assert.equal( + fields, pathList, + `Unexpected list of fields in the field mask: ${pathList}, expected: ${fields}.` + ); + done(); + }); + + it('allows to set ordering and limit', done => { + const column = 'name'; + const direction = OrderBy.Direction.ASCENDING; + const query = request.orderBy(column, direction) + .limit(2) + .query(); + const responseFormat = query.getFormat(); + const orderBy = responseFormat.getOrderBy(); + const orderByColumn = orderBy.getColumn(); + assert.equal( + column, orderByColumn, + `Unexpected column specified in the order by: ${orderByColumn}, expected: ${column}.` + ); + const orderByDirection = orderBy.getDirection(); + assert.equal( + direction, orderByDirection, + `Unexpected direction specified in the order by: ${orderByDirection}, + expected: ${direction}.` + ); + done(); + }); + + function assertIsIncludeAll(target) { + assertTargetTypeEquals(target); + assert.equal( + true, target.getIncludeAll(), + 'Expected `target.include_all` to be `true`.' + ); + } + + /** + * @param {!spine.client.Target} target + */ + function assertTargetTypeEquals(target) { + const actualType = target.getType(); + assert.equal( + targetTypeUrl, actualType, + `The unexpected target type ${actualType}, expected: ${targetTypeUrl}.` + ); + } +}); From 8402c3e419200a130ca0a2df9ba4b5b0be27f3be Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Fri, 6 Dec 2019 17:03:11 +0200 Subject: [PATCH 36/70] Add a `SubscriptionRequest` test --- .../test/client/filtering-request-test.js | 230 ++++++++++++++++++ client-js/test/client/query-request-test.js | 191 ++------------- .../test/client/subscription-request-test.js | 48 ++++ 3 files changed, 295 insertions(+), 174 deletions(-) create mode 100644 client-js/test/client/filtering-request-test.js create mode 100644 client-js/test/client/subscription-request-test.js diff --git a/client-js/test/client/filtering-request-test.js b/client-js/test/client/filtering-request-test.js new file mode 100644 index 000000000..2c9ed9281 --- /dev/null +++ b/client-js/test/client/filtering-request-test.js @@ -0,0 +1,230 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +import assert from 'assert'; +import uuid from 'uuid'; +import {ActorProvider, ActorRequestFactory, Filters} from '@lib/client/actor-request-factory'; +import {AnyPacker} from '@lib/client/any-packer'; +import {Type} from "@lib/client/typed-message"; +import {Task, TaskId} from '@testProto/spine/test/js/task_pb'; +import {MockClient} from "./test-helpers"; + +export class Given { + + constructor() { + throw new Error('A utility `Given` class cannot be instantiated.'); + } + + static targetType() { + return Task; + } + + static client() { + return new MockClient(); + } + + static actorRequestFactory() { + return new ActorRequestFactory(new ActorProvider()); + } +} + +export function filteringRequestTest(newRequest, buildResult, getTarget, getFieldMask) { + + const targetType = Given.targetType(); + const client = Given.client(); + const actorRequestFactory = Given.actorRequestFactory(); + + const targetTypeUrl = Type.forClass(targetType).url().value(); + + let request; + + beforeEach(done => { + request = newRequest(targetType, client, actorRequestFactory); + done(); + }); + + it('creates a `select all` target', done => { + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('creates a target filtering entities by a single ID', done => { + const taskId = new TaskId(); + const idValue = uuid.v4(); + taskId.setValue(idValue); + request.byId(taskId); + + const result = buildResult(request); + const target = getTarget(result); + assertTargetTypeEquals(target); + const idFilter = target.getFilters().getIdFilter(); + const idList = idFilter.getIdList(); + const length = idList.length; + assert.equal( + 1, length, + `Expected the ID list to contain a single ID, the actual length: ${length}.` + ); + const taskIdType = Type.forClass(TaskId); + const targetId = AnyPacker.unpack(idList[0]).as(taskIdType); + const actualId = targetId.getValue(); + assert.equal( + idValue, actualId, + `Unexpected target ID ${actualId}, expected: ${idValue}.` + ); + done(); + }); + + it('creates a target filtering entities by a group of IDs', done => { + const taskId1 = new TaskId(); + const idValue1 = uuid.v4(); + taskId1.setValue(idValue1); + const taskId2 = new TaskId(); + const idValue2 = uuid.v4(); + taskId2.setValue(idValue2); + + request.byId([taskId1, taskId2]); + const result = buildResult(request); + const target = getTarget(result); + assertTargetTypeEquals(target); + const idFilter = target.getFilters().getIdFilter(); + const idList = idFilter.getIdList(); + const length = idList.length; + assert.equal( + 2, length, + `Expected the ID list to contain two IDs, the actual length: ${length}.` + ); + done(); + }); + + it('ignores a `null` ID specified to the `byId` method', done => { + request.byId(null); + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('ignores an empty array of IDs specified to the `byId` method', done => { + request.byId([]); + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('creates a target filtering entities by a single filter', done => { + const filter = Filters.eq('name', 'some task name'); + request.where(filter); + const result = buildResult(request); + const target = getTarget(result); + const compositeFilters = target.getFilters().getFilterList(); + const compositeFiltersLength = compositeFilters.length; + assert.equal( + 1, compositeFiltersLength, + `Expected the composite filter list to contain a single filter, the actual + length: ${compositeFiltersLength}.` + ); + const filters = compositeFilters[0].getFilterList(); + const length = filters.length; + assert.equal( + 1, length, + `Expected the filter list to contain a single filter, the actual length: ${length}.` + ); + const targetFilter = filters[0]; + assert.equal( + filter, targetFilter, + `Unexpected filter value ${targetFilter}, expected: ${filter}.` + ); + done(); + }); + + it('creates a target filtering entities by a group of filters', done => { + const filter1 = Filters.eq('name', 'some task name'); + const filter2 = Filters.eq('description', 'some task description'); + request.where([filter1, filter2]); + const result = buildResult(request); + const target = getTarget(result); + const compositeFilters = target.getFilters().getFilterList(); + const compositeFiltersLength = compositeFilters.length; + assert.equal( + 1, compositeFiltersLength, + `Expected the composite filter list to contain a single filter, the actual + length: ${compositeFiltersLength}.` + ); + const filters = compositeFilters[0].getFilterList(); + const length = filters.length; + assert.equal( + 2, length, + `Expected the filter list to contain two filters, the actual length: ${length}.` + ); + done(); + }); + + it('ignores a `null` filter specified to the `where` method', done => { + request.where(null); + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('ignores an empty filter list specified to the `where` method', done => { + request.where([]); + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('allows to specify a field mask', done => { + const fields = ['id', 'name', 'description']; + request.withMask(fields); + const result = buildResult(request); + const fieldMask = getFieldMask(result); + const pathList = fieldMask.getPathsList(); + assert.equal( + fields, pathList, + `Unexpected list of fields in the field mask: ${pathList}, expected: ${fields}.` + ); + done(); + }); + + function assertIsIncludeAll(target) { + assertTargetTypeEquals(target); + assert.equal( + true, target.getIncludeAll(), + 'Expected `target.include_all` to be `true`.' + ); + } + + /** + * @param {!spine.client.Target} target + */ + function assertTargetTypeEquals(target) { + const actualType = target.getType(); + assert.equal( + targetTypeUrl, actualType, + `The unexpected target type ${actualType}, expected: ${targetTypeUrl}.` + ); + } +} diff --git a/client-js/test/client/query-request-test.js b/client-js/test/client/query-request-test.js index f7b452543..5d1d5881c 100644 --- a/client-js/test/client/query-request-test.js +++ b/client-js/test/client/query-request-test.js @@ -20,175 +20,21 @@ import assert from 'assert'; -import uuid from 'uuid'; -import {ActorProvider, ActorRequestFactory, Filters} from '@lib/client/actor-request-factory'; -import {AnyPacker} from '@lib/client/any-packer'; import {QueryRequest} from "@lib/client/client-request"; import {Duration} from "@lib/client/time-utils"; -import {Type} from "@lib/client/typed-message"; import {OrderBy} from '@proto/spine/client/query_pb'; -import {Task, TaskId} from '@testProto/spine/test/js/task_pb'; -import {MockClient} from "./test-helpers"; +import {filteringRequestTest, Given} from "./filtering-request-test"; describe('QueryRequest', function () { const timeoutDuration = new Duration({seconds: 5}); this.timeout(timeoutDuration.inMs()); - const targetType = Task; - const clientStub = new MockClient(); - const actorRequestFactory = new ActorRequestFactory(new ActorProvider()); - - const targetTypeUrl = Type.forClass(targetType).url().value(); - - let request; - - beforeEach(done => { - request = new QueryRequest(targetType, clientStub, actorRequestFactory); - done(); - }); - - it('creates a `select all` query', done => { - const query = request.query(); - const target = query.getTarget(); - assertIsIncludeAll(target); - done(); - }); - - it('creates a query filtering entities by a single ID', done => { - const taskId = new TaskId(); - const idValue = uuid.v4(); - taskId.setValue(idValue); - - const query = request.byId(taskId).query(); - const target = query.getTarget(); - assertTargetTypeEquals(target); - const idFilter = target.getFilters().getIdFilter(); - const idList = idFilter.getIdList(); - const length = idList.length; - assert.equal( - 1, length, - `Expected the ID list to contain a single ID, the actual length: ${length}.` - ); - const taskIdType = Type.forClass(TaskId); - const targetId = AnyPacker.unpack(idList[0]).as(taskIdType); - const actualId = targetId.getValue(); - assert.equal( - idValue, actualId, - `Unexpected target ID ${actualId}, expected: ${idValue}.` - ); - done(); - }); - - it('creates a query filtering entities by a group of IDs', done => { - const taskId1 = new TaskId(); - const idValue1 = uuid.v4(); - taskId1.setValue(idValue1); - const taskId2 = new TaskId(); - const idValue2 = uuid.v4(); - taskId2.setValue(idValue2); - - const query = request.byId([taskId1, taskId2]).query(); - const target = query.getTarget(); - assertTargetTypeEquals(target); - const idFilter = target.getFilters().getIdFilter(); - const idList = idFilter.getIdList(); - const length = idList.length; - assert.equal( - 2, length, - `Expected the ID list to contain two IDs, the actual length: ${length}.` - ); - done(); - }); - - it('ignores a `null` ID specified to the `byId` method', done => { - const query = request.byId(null).query(); - const target = query.getTarget(); - assertIsIncludeAll(target); - done(); - }); - - it('ignores an empty array of IDs specified to the `byId` method', done => { - const query = request.byId([]).query(); - const target = query.getTarget(); - assertIsIncludeAll(target); - done(); - }); - - it('creates a query filtering entities by a single filter', done => { - const filter = Filters.eq('name', 'some task name'); - const query = request.where(filter).query(); - const target = query.getTarget(); - const compositeFilters = target.getFilters().getFilterList(); - const compositeFiltersLength = compositeFilters.length; - assert.equal( - 1, compositeFiltersLength, - `Expected the composite filter list to contain a single filter, the actual - length: ${compositeFiltersLength}.` - ); - const filters = compositeFilters[0].getFilterList(); - const length = filters.length; - assert.equal( - 1, length, - `Expected the filter list to contain a single filter, the actual length: ${length}.` - ); - const targetFilter = filters[0]; - assert.equal( - filter, targetFilter, - `Unexpected filter value ${targetFilter}, expected: ${filter}.` - ); - done(); - }); - - it('creates a query filtering entities by a group of filters', done => { - const filter1 = Filters.eq('name', 'some task name'); - const filter2 = Filters.eq('description', 'some task description'); - const query = request.where([filter1, filter2]).query(); - const target = query.getTarget(); - const compositeFilters = target.getFilters().getFilterList(); - const compositeFiltersLength = compositeFilters.length; - assert.equal( - 1, compositeFiltersLength, - `Expected the composite filter list to contain a single filter, the actual - length: ${compositeFiltersLength}.` - ); - const filters = compositeFilters[0].getFilterList(); - const length = filters.length; - assert.equal( - 2, length, - `Expected the filter list to contain two filters, the actual length: ${length}.` - ); - done(); - }); - - it('ignores a `null` filter specified to the `where` method', done => { - const query = request.where(null).query(); - const target = query.getTarget(); - assertIsIncludeAll(target); - done(); - }); - - it('ignores an empty filter list specified to the `where` method', done => { - const query = request.where([]).query(); - const target = query.getTarget(); - assertIsIncludeAll(target); - done(); - }); - - it('allows to set a field mask', done => { - const fields = ['id', 'name', 'description']; - const query = request.withMask(fields).query(); - const responseFormat = query.getFormat(); - const fieldMask = responseFormat.getFieldMask(); - const pathList = fieldMask.getPathsList(); - assert.equal( - fields, pathList, - `Unexpected list of fields in the field mask: ${pathList}, expected: ${fields}.` - ); - done(); - }); + filteringRequestTest(newQueryRequest, buildQuery, getTarget, getFieldMask); it('allows to set ordering and limit', done => { + const request = + newQueryRequest(Given.targetType(), Given.client(), Given.actorRequestFactory()); const column = 'name'; const direction = OrderBy.Direction.ASCENDING; const query = request.orderBy(column, direction) @@ -204,28 +50,25 @@ describe('QueryRequest', function () { const orderByDirection = orderBy.getDirection(); assert.equal( direction, orderByDirection, - `Unexpected direction specified in the order by: ${orderByDirection}, + `Unexpected direction specified in the order by: ${orderByDirection}, expected: ${direction}.` ); done(); }); - function assertIsIncludeAll(target) { - assertTargetTypeEquals(target); - assert.equal( - true, target.getIncludeAll(), - 'Expected `target.include_all` to be `true`.' - ); + function newQueryRequest(targetType, clientStub, actorRequestFactory) { + return new QueryRequest(targetType, clientStub, actorRequestFactory); } - /** - * @param {!spine.client.Target} target - */ - function assertTargetTypeEquals(target) { - const actualType = target.getType(); - assert.equal( - targetTypeUrl, actualType, - `The unexpected target type ${actualType}, expected: ${targetTypeUrl}.` - ); + function buildQuery(request) { + return request.query(); + } + + function getTarget(query) { + return query.getTarget(); + } + + function getFieldMask(query) { + return query.getFormat().getFieldMask(); } }); diff --git a/client-js/test/client/subscription-request-test.js b/client-js/test/client/subscription-request-test.js new file mode 100644 index 000000000..e852513ad --- /dev/null +++ b/client-js/test/client/subscription-request-test.js @@ -0,0 +1,48 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +import {SubscriptionRequest} from "@lib/client/client-request"; +import {Duration} from "@lib/client/time-utils"; +import {filteringRequestTest} from "./filtering-request-test"; + +describe('SubscriptionRequest', function () { + + const timeoutDuration = new Duration({seconds: 5}); + this.timeout(timeoutDuration.inMs()); + + filteringRequestTest(newSubscriptionRequest, buildTopic, getTarget, getFieldMask); + + function newSubscriptionRequest(targetType, clientStub, actorRequestFactory) { + return new SubscriptionRequest(targetType, clientStub, actorRequestFactory); + } + + function buildTopic(request) { + return request.topic(); + } + + function getTarget(topic) { + return topic.getTarget(); + } + + function getFieldMask(topic) { + return topic.getFieldMask(); + } +}); From 686a9c3c0c65437b394587e8abad3b2241f42ad9 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Fri, 6 Dec 2019 18:07:59 +0200 Subject: [PATCH 37/70] Fix a race condition when subscribing to command-produced events --- client-js/main/client/client-request.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 8d5a75607..90c795c4b 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -394,11 +394,14 @@ export class CommandRequest extends ClientRequest{ .post(); promises.push(promise); }); - if (promises.length === 1) { - return promises[0]; - } - this._client.post(command, onAck); - return Promise.all(promises); + const subscriptionPromise = promises.length === 1 + ? promises[0] + : Promise.all(promises); + // noinspection JSValidateTypes the types are actually correct. + return subscriptionPromise.then((subscriptionObject) => { + this._client.post(command, onAck); + return subscriptionObject; + }); } /** From 92572add1c67c3ec858868c31523e353e931b061 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Fri, 6 Dec 2019 18:26:32 +0200 Subject: [PATCH 38/70] Improve formatting --- client-js/main/client/client-request.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 90c795c4b..e3b18af50 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -397,6 +397,7 @@ export class CommandRequest extends ClientRequest{ const subscriptionPromise = promises.length === 1 ? promises[0] : Promise.all(promises); + // noinspection JSValidateTypes the types are actually correct. return subscriptionPromise.then((subscriptionObject) => { this._client.post(command, onAck); From 6a7ceefbd11836db8c088f87317be024ed106a6e Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 9 Dec 2019 14:08:54 +0200 Subject: [PATCH 39/70] Resolve todos, clean up the code --- client-js/main/client/client-request.js | 7 ++-- client-js/main/client/http-endpoint.js | 42 +++++++++++-------- .../test/client/filtering-request-test.js | 20 ++++----- client-js/test/client/query-request-test.js | 7 ++-- .../test/client/subscription-request-test.js | 1 - .../firebase/subscription/UpdatePayload.java | 11 +++-- .../js-tests/test/direct-client/query-test.js | 18 ++++---- .../test/firebase-client/fetch-test.js | 22 +++++----- .../firebase-client/given/test-environment.js | 4 +- .../test/firebase-client/query-test.js | 2 +- .../test/firebase-client/send-command-test.js | 9 ++-- .../test/firebase-client/subscribe-test.js | 42 ++++++++++--------- .../test/given/users-test-environment.js | 7 ++-- 13 files changed, 100 insertions(+), 92 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index e3b18af50..e8e55b724 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -115,10 +115,9 @@ class FilteringRequest extends ClientRequest { * @protected */ _builder() { - // TODO:2019-11-27:dmytro.kuzmin:WIP Check that setting to some initial value is - // unnecessary. if (!this._builderInstance) { - this._builderInstance = this._newBuilderFn()(this._requestFactory); + const newBuilderFn = this._newBuilderFn(); + this._builderInstance = newBuilderFn(this._requestFactory); } return this._builderInstance; } @@ -398,7 +397,7 @@ export class CommandRequest extends ClientRequest{ ? promises[0] : Promise.all(promises); - // noinspection JSValidateTypes the types are actually correct. + // noinspection JSValidateTypes return subscriptionPromise.then((subscriptionObject) => { this._client.post(command, onAck); return subscriptionObject; diff --git a/client-js/main/client/http-endpoint.js b/client-js/main/client/http-endpoint.js index 733b66c8d..386fe623e 100644 --- a/client-js/main/client/http-endpoint.js +++ b/client-js/main/client/http-endpoint.js @@ -50,7 +50,7 @@ class Endpoint { /** * Sends off a command to the endpoint. * - * @param {!TypedMessage} command a Command sent to Spine server + * @param {!TypedMessage} command a Command to send to the Spine server * @return {Promise} a promise of a successful server response, rejected if * an error occurs */ @@ -110,7 +110,7 @@ class Endpoint { /** - * @param {!TypedMessage} command a Command sent to Spine server + * @param {!TypedMessage} command a Command to send to the Spine server * @return {Promise} a promise of a successful server response, rejected if * an error occurs * @protected @@ -184,9 +184,10 @@ export class HttpEndpoint extends Endpoint { /** * Sends off a command to the endpoint. * - * @param {!TypedMessage} command a Command sent to Spine server - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or a connection error occurs + * @param {!TypedMessage} command a Command to send to the Spine server + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _executeCommand(command) { @@ -198,8 +199,9 @@ export class HttpEndpoint extends Endpoint { * Sends off a query to the endpoint. * * @param {!TypedMessage} query a Query to Spine server to retrieve some domain entities - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or a connection error occurs + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _performQuery(query) { @@ -211,8 +213,9 @@ export class HttpEndpoint extends Endpoint { * Sends off a request to create a subscription for a topic. * * @param {!TypedMessage} topic a topic to subscribe to - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or a connection error occurs + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _subscribeTo(topic) { @@ -227,7 +230,8 @@ export class HttpEndpoint extends Endpoint { * @param {!TypedMessage} subscription a subscription that is prevented * from being closed by server * @return {Promise} a promise of a successful server response JSON data, - * rejected if the client response is not 2xx or a connection error occurs + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _keepUp(subscription) { @@ -241,7 +245,8 @@ export class HttpEndpoint extends Endpoint { * * @param {!TypedMessage} subscription a subscription to be canceled * @return {Promise} a promise of a successful server response JSON data, - * rejected if the client response is not 2xx or a connection error occurs + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _cancel(subscription) { @@ -255,8 +260,9 @@ export class HttpEndpoint extends Endpoint { * * @param {!string} endpoint an endpoint to send the message to * @param {!TypedMessage} message a message to send, as a {@link TypedMessage} - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or a connection error occurs + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or a + * connection error occurs * @private */ _sendMessage(endpoint, message) { @@ -273,8 +279,9 @@ export class HttpEndpoint extends Endpoint { * with a respective error otherwise. * * @param {!Response} response an HTTP request response - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or if JSON parsing fails + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or if JSON + * parsing fails * @private */ static _jsonOrError(response) { @@ -294,8 +301,9 @@ export class HttpEndpoint extends Endpoint { * Parses the given response JSON data, rejects if parsing fails. * * @param {!Response} response an HTTP request response - * @return {Promise} a promise of a server response parsing to be fulfilled with a JSON - * data or rejected with {@link SpineError} if JSON parsing fails. + * @return {Promise} a promise of a server response parsing to be fulfilled + * with a JSON data or rejected with {@link SpineError} if + * JSON parsing fails. * @private */ static _parseJson(response) { diff --git a/client-js/test/client/filtering-request-test.js b/client-js/test/client/filtering-request-test.js index 2c9ed9281..a932240e0 100644 --- a/client-js/test/client/filtering-request-test.js +++ b/client-js/test/client/filtering-request-test.js @@ -82,14 +82,14 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel const length = idList.length; assert.equal( 1, length, - `Expected the ID list to contain a single ID, the actual length: ${length}.` + `Expected the ID list to contain a single ID, the actual length: '${length}'.` ); const taskIdType = Type.forClass(TaskId); const targetId = AnyPacker.unpack(idList[0]).as(taskIdType); const actualId = targetId.getValue(); assert.equal( idValue, actualId, - `Unexpected target ID ${actualId}, expected: ${idValue}.` + `Unexpected target ID '${actualId}', expected: '${idValue}'.` ); done(); }); @@ -111,7 +111,7 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel const length = idList.length; assert.equal( 2, length, - `Expected the ID list to contain two IDs, the actual length: ${length}.` + `Expected the ID list to contain two IDs, the actual length: '${length}'.` ); done(); }); @@ -142,18 +142,18 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel assert.equal( 1, compositeFiltersLength, `Expected the composite filter list to contain a single filter, the actual - length: ${compositeFiltersLength}.` + length: '${compositeFiltersLength}'.` ); const filters = compositeFilters[0].getFilterList(); const length = filters.length; assert.equal( 1, length, - `Expected the filter list to contain a single filter, the actual length: ${length}.` + `Expected the filter list to contain a single filter, the actual length: '${length}'.` ); const targetFilter = filters[0]; assert.equal( filter, targetFilter, - `Unexpected filter value ${targetFilter}, expected: ${filter}.` + `Unexpected filter value '${targetFilter}', expected: '${filter}'.` ); done(); }); @@ -169,13 +169,13 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel assert.equal( 1, compositeFiltersLength, `Expected the composite filter list to contain a single filter, the actual - length: ${compositeFiltersLength}.` + length: '${compositeFiltersLength}'.` ); const filters = compositeFilters[0].getFilterList(); const length = filters.length; assert.equal( 2, length, - `Expected the filter list to contain two filters, the actual length: ${length}.` + `Expected the filter list to contain two filters, the actual length: '${length}'.` ); done(); }); @@ -204,7 +204,7 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel const pathList = fieldMask.getPathsList(); assert.equal( fields, pathList, - `Unexpected list of fields in the field mask: ${pathList}, expected: ${fields}.` + `Unexpected list of fields in the field mask: '${pathList}', expected: '${fields}'.` ); done(); }); @@ -224,7 +224,7 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel const actualType = target.getType(); assert.equal( targetTypeUrl, actualType, - `The unexpected target type ${actualType}, expected: ${targetTypeUrl}.` + `The unexpected target type '${actualType}', expected: '${targetTypeUrl}'.` ); } } diff --git a/client-js/test/client/query-request-test.js b/client-js/test/client/query-request-test.js index 5d1d5881c..a497ed18b 100644 --- a/client-js/test/client/query-request-test.js +++ b/client-js/test/client/query-request-test.js @@ -18,7 +18,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - import assert from 'assert'; import {QueryRequest} from "@lib/client/client-request"; import {Duration} from "@lib/client/time-utils"; @@ -45,13 +44,13 @@ describe('QueryRequest', function () { const orderByColumn = orderBy.getColumn(); assert.equal( column, orderByColumn, - `Unexpected column specified in the order by: ${orderByColumn}, expected: ${column}.` + `Unexpected column specified in the order by: '${orderByColumn}', expected: '${column}'.` ); const orderByDirection = orderBy.getDirection(); assert.equal( direction, orderByDirection, - `Unexpected direction specified in the order by: ${orderByDirection}, - expected: ${direction}.` + `Unexpected direction specified in the order by: '${orderByDirection}', + expected: '${direction}'.` ); done(); }); diff --git a/client-js/test/client/subscription-request-test.js b/client-js/test/client/subscription-request-test.js index e852513ad..4028fda57 100644 --- a/client-js/test/client/subscription-request-test.js +++ b/client-js/test/client/subscription-request-test.js @@ -18,7 +18,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - import {SubscriptionRequest} from "@lib/client/client-request"; import {Duration} from "@lib/client/time-utils"; import {filteringRequestTest} from "./filtering-request-test"; diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java index 83b52558f..5989445dd 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java @@ -50,9 +50,8 @@ *

Consists of entities or events, which ought to be written into the Firebase database under * a certain path. * - *

The entity state updates that render entity state - * {@linkplain EntityStateUpdate#getNoLongerMatching() no longer matching} are stored as - * {@link Empty}. + *

The updates about {@linkplain EntityStateUpdate#getNoLongerMatching() no longer matching} + * entity states are represented as {@link Empty}. */ final class UpdatePayload { @@ -148,8 +147,8 @@ NodeValue asNodeValue() { /** * Adds an {@code } key-value pair as a child of the given node. * - *

If the message is {@link Empty}, adds a {@code null} child under the given key, deleting - * it from the database. + *

If the message is {@link Empty}, adds a {@code null} child under the given key, + * effectively deleting it from the database. */ private static void addChildOrNull(NodeValue node, String id, Message message) { boolean isEmpty = Empty.getDefaultInstance() @@ -162,7 +161,7 @@ private static void addChildOrNull(NodeValue node, String id, Message message) { } /** - * An identity function for {@link Event}-to-{@link Message} conversion. + * An identity {@link Event}-to-{@link Message} conversion. * *

The standard {@link Function#identity()} cannot be applied because of the type arguments * mismatch. diff --git a/integration-tests/js-tests/test/direct-client/query-test.js b/integration-tests/js-tests/test/direct-client/query-test.js index edb77356c..bf20c5019 100644 --- a/integration-tests/js-tests/test/direct-client/query-test.js +++ b/integration-tests/js-tests/test/direct-client/query-test.js @@ -24,7 +24,7 @@ import {UserTasksTestEnvironment as TestEnvironment} from '../given/users-test-e import {client} from './given/direct-client'; import {UserTasks} from '@testProto/spine/web/test/given/user_tasks_pb'; -describe('`DirectClient` executes query built', function () { +describe('DirectClient executes query built', function () { let users; @@ -32,7 +32,7 @@ describe('`DirectClient` executes query built', function () { * Prepares environment, where four users have one, two, three, and four tasks * assigned respectively. */ - before(function(done) { + before(function (done) { // Big timeout allows to complete environment setup. this.timeout(10 * 1000); @@ -59,12 +59,12 @@ describe('`DirectClient` executes query built', function () { it(`by IDs and returns correct values`, done => { const ids = users.map(user => user.id); client.select(UserTasks) - .byId(ids) - .run() - .then(userTasksList => { - assert.ok(ensureUserTasks(userTasksList, users)); - done(); - }) - .catch(fail(done)); + .byId(ids) + .run() + .then(userTasksList => { + assert.ok(ensureUserTasks(userTasksList, users)); + done(); + }) + .catch(fail(done)); }); }); diff --git a/integration-tests/js-tests/test/firebase-client/fetch-test.js b/integration-tests/js-tests/test/firebase-client/fetch-test.js index 628ffc2ca..b010cecd0 100644 --- a/integration-tests/js-tests/test/firebase-client/fetch-test.js +++ b/integration-tests/js-tests/test/firebase-client/fetch-test.js @@ -33,7 +33,7 @@ describe('FirebaseClient "fetch"', function () { * Prepares the environment for `FirebaseClient#fetch()` tests where * two tasks are created. */ - before(function(done) { + before(function (done) { // Big timeout allows complete environment setup. this.timeout(20 * 1000); @@ -64,7 +64,7 @@ describe('FirebaseClient "fetch"', function () { }); }); - it('returns all values of type', done => { + it('returns all values of a type', done => { client.select(Task) .run() .then(data => { @@ -79,7 +79,7 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('returns correct value by single ID', done => { + it('returns the correct value by a single ID', done => { const id = taskIds[0]; client.select(Task) .byId(id) @@ -93,7 +93,7 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('ignores `byId` parameter when empty list is specified', done => { + it('ignores `byId` parameter when an empty list is specified', done => { client.select(Task) .byId([]) .run() @@ -104,7 +104,7 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('ignores `byId` parameter when `null` value is specified', done => { + it('ignores `byId` parameter when a `null` value is specified', done => { client.select(Task) .byId(null) .run() @@ -115,7 +115,7 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('returns empty list when fetches entity by single ID that is missing', done => { + it('returns empty list when fetches entity by a single ID that is missing', done => { const taskId = TestEnvironment.taskId({}); client.select(Task) @@ -128,7 +128,7 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('returns correct values by IDs', done => { + it('returns correct values by multiple IDs', done => { client.select(Task) .byId(taskIds) .run() @@ -136,7 +136,8 @@ describe('FirebaseClient "fetch"', function () { assert.ok(Array.isArray(data)); assert.equal(data.length, taskIds.length); taskIds.forEach(taskId => { - const targetObject = data.find(item => item.getId().getValue() === taskId.getValue()); + const targetObject = + data.find(item => item.getId().getValue() === taskId.getValue()); assert.ok(targetObject); }); @@ -144,7 +145,7 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); - it('retrieves an empty list for entity that does not get created', done => { + it('retrieves an empty list for an entity type that does not get instantiated', done => { client.select(Project) .run() .then(data => { @@ -154,7 +155,8 @@ describe('FirebaseClient "fetch"', function () { }); it('fails a malformed query', done => { - const command = TestEnvironment.createTaskCommand({withPrefix: 'spine-web-test-malformed-query'}); + const command = + TestEnvironment.createTaskCommand({withPrefix: 'spine-web-test-malformed-query'}); const Unknown = class { static typeUrl() { diff --git a/integration-tests/js-tests/test/firebase-client/given/test-environment.js b/integration-tests/js-tests/test/firebase-client/given/test-environment.js index 84a305c15..89fb345ce 100644 --- a/integration-tests/js-tests/test/firebase-client/given/test-environment.js +++ b/integration-tests/js-tests/test/firebase-client/given/test-environment.js @@ -63,8 +63,8 @@ export default class TestEnvironment { name = typeof name === 'undefined' ? this.DEFAULT_TASK_NAME : name; description = typeof description === 'undefined' - ? this.DEFAULT_TASK_DESCRIPTION - : description; + ? this.DEFAULT_TASK_DESCRIPTION + : description; const command = new CreateTask(); command.setId(taskId); diff --git a/integration-tests/js-tests/test/firebase-client/query-test.js b/integration-tests/js-tests/test/firebase-client/query-test.js index f4005066e..504ea72be 100644 --- a/integration-tests/js-tests/test/firebase-client/query-test.js +++ b/integration-tests/js-tests/test/firebase-client/query-test.js @@ -171,7 +171,7 @@ describe('FirebaseClient executes query built', function () { }); }); - it('with Date-based filter and returns correct values', (done) => { + it('with `Date`-based filter and returns correct values', (done) => { const userIds = toUserIds(users); client.select(UserTasks) diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index 03a418fd3..0cf580d46 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -145,12 +145,13 @@ describe('FirebaseClient command sending', function () { const theTaskId = message.getId().getValue(); assert.equal( taskId, theTaskId, - `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` + `Expected the task ID to be '${taskId}', got '${theTaskId}' instead.` ); const theTaskName = message.getName(); assert.equal( taskName, theTaskName, - `Expected the task name to be ${taskName}, got ${theTaskName} instead.` + `Expected the task name to be '${taskName}', got '${theTaskName}' + instead.` ); const origin = event.getContext().getPastMessage().getMessage(); const originType = origin.getTypeUrl(); @@ -158,8 +159,8 @@ describe('FirebaseClient command sending', function () { const expectedOriginType = createTaskType.url().value(); assert.equal( expectedOriginType, originType, - `Expected origin to be of type ${expectedOriginType}, got ${originType} - instead.` + `Expected origin to be of type '${expectedOriginType}', got + '${originType}' instead.` ); unsubscribe(); done(); diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index 61da89fc8..b8dde0ce2 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -61,10 +61,12 @@ describe('FirebaseClient subscription', function () { } }); itemRemoved.subscribe({ - next: fail(done, 'Unexpected entity remove during entity create subscription test.') + next: fail(done, + 'Unexpected entity remove during entity create subscription test.') }); itemChanged.subscribe({ - next: fail(done, 'Unexpected entity change during entity create subscription test.') + next: fail(done, + 'Unexpected entity change during entity create subscription test.') }); commands.forEach(command => { client.command(command) @@ -116,7 +118,7 @@ describe('FirebaseClient subscription', function () { item.getName(), UPDATED_TASK_NAME, `Task is named "${item.getName()}", expected "${UPDATED_TASK_NAME}"` ); - console.log(`Got task changes for ${id}.`); + console.log(`Got task changes for '${id}'.`); unsubscribe(); done(); } @@ -296,24 +298,24 @@ describe('FirebaseClient subscription', function () { .post() .then(({eventEmitted, unsubscribe}) => { eventEmitted.subscribe({ - next: event => { - const packedMessage = event.getMessage(); - const taskRenamedType = Type.forClass(TaskRenamed); - const message = AnyPacker.unpack(packedMessage).as(taskRenamedType); - const theTaskId = message.getId().getValue(); - assert.equal( - taskId, theTaskId, - `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` - ); - const newTaskName = message.getName(); - assert.equal( - updatedTaskName, newTaskName, - `Expected the new task name to be ${updatedTaskName}, got + next: event => { + const packedMessage = event.getMessage(); + const taskRenamedType = Type.forClass(TaskRenamed); + const message = AnyPacker.unpack(packedMessage).as(taskRenamedType); + const theTaskId = message.getId().getValue(); + assert.equal( + taskId, theTaskId, + `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` + ); + const newTaskName = message.getName(); + assert.equal( + updatedTaskName, newTaskName, + `Expected the new task name to be ${updatedTaskName}, got ${newTaskName} instead.` - ); - unsubscribe(); - done(); - } + ); + unsubscribe(); + done(); + } }); }); client.command(createCommand) diff --git a/integration-tests/js-tests/test/given/users-test-environment.js b/integration-tests/js-tests/test/given/users-test-environment.js index 8d254a523..7e42d2117 100644 --- a/integration-tests/js-tests/test/given/users-test-environment.js +++ b/integration-tests/js-tests/test/given/users-test-environment.js @@ -83,11 +83,10 @@ export class UserTasksTestEnvironment extends TestEnvironment { command.setId(taskId); command.setNewAssignee(newAssignee); - // TODO:2019-11-27:dmytro.kuzmin:WIP Try remove these lambdas. client.command(command) - .onOk(() => resolve()) - .onError(() => reject()) - .onRejection(() => reject()) + .onOk(resolve) + .onError(reject) + .onRejection(reject) .post(); }) } From c30ef0633bf49d8a0a24002ed2d148ce697b2444 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 9 Dec 2019 20:37:37 +0200 Subject: [PATCH 40/70] Add doc to client requests --- client-js/main/client/client-request.js | 277 ++++++++++++++++++------ 1 file changed, 216 insertions(+), 61 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index e8e55b724..2a5f534ff 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -30,24 +30,27 @@ import {Filters} from "./actor-request-factory"; import {Type} from "./typed-message"; /** + * A request from a client to the Spine backend. + * * @abstract */ class ClientRequest { /** - * @param {!Client} client - * @param {!ActorRequestFactory} requestFactory - * * @protected */ constructor(client, requestFactory) { /** + * @type Client + * * @protected */ this._client = client; /** + * @type ActorRequestFactory + * * @protected */ this._requestFactory = requestFactory; @@ -55,14 +58,19 @@ class ClientRequest { } /** + * An abstract base for client requests that filter messages by certain criteria. + * * @abstract + * + * @template the type of the builder wrapped by this request + * @template the type of the messages that store the request data */ class FilteringRequest extends ClientRequest { /** - * @param {!Class} targetType - * @param {!Client} client - * @param {!ActorRequestFactory} actorRequestFactory + * @param {!Class} targetType the target type of the request + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory * * @protected */ @@ -72,10 +80,11 @@ class FilteringRequest extends ClientRequest { } /** - * @param ids {!|Number|String|[]|Number[]|String[]} - * @return {this} self for method chaining + * Adds filtering by IDs to the built request. * - * @template a Protobuf type of IDs + * @param ids {!|Number|String|[]|Number[]|String[]} + * the IDs of interest + * @return {this} self for method chaining */ byId(ids) { ids = FilteringRequest._ensureArray(ids); @@ -84,11 +93,11 @@ class FilteringRequest extends ClientRequest { } /** - * ... + * Adds filtering by predicates to the built request. * - * The subsequent calls override each other. + * Filters specified in a list are considered to be joined using `AND` operator. * - * @param {!Filter|CompositeFilter|Filter[]|CompositeFilter[]} predicates + * @param {!Filter|CompositeFilter|Filter[]|CompositeFilter[]} predicates the filters * @return {this} self for method chaining */ where(predicates) { @@ -98,19 +107,24 @@ class FilteringRequest extends ClientRequest { } /** - * @param {!String|String[]} fieldNames + * Applies a field mask to the request results. + * + * The names of the fields must be formatted according to the `google.protobuf.FieldMask` + * specification. + * + * @param {!String|String[]} fieldPaths the fields to include to the mask * @return {this} self for method chaining */ - withMask(fieldNames) { - fieldNames = FilteringRequest._ensureArray(fieldNames); - this._builder().withMask(fieldNames); + withMask(fieldPaths) { + fieldPaths = FilteringRequest._ensureArray(fieldPaths); + this._builder().withMask(fieldPaths); return this._self(); } /** - * @return {AbstractTargetBuilder} + * Returns the builder for messages that store request data. * - * @template + * @return {AbstractTargetBuilder} the builder instance * * @protected */ @@ -123,11 +137,10 @@ class FilteringRequest extends ClientRequest { } /** - * @abstract - * - * @return {Function} + * Returns the function with which the {@link _builderInstance} can be created. * - * @template + * @abstract + * @return {Function} the function * * @protected */ @@ -137,7 +150,6 @@ class FilteringRequest extends ClientRequest { /** * @abstract - * * @return {this} * * @protected @@ -147,6 +159,12 @@ class FilteringRequest extends ClientRequest { } /** + * Wraps the passed argument into array if it's not an array already. + * + * The `null` values are converted into an empty array. + * + * @return {Array} the passed argument as an array + * * @private */ static _ensureArray(values) { @@ -160,17 +178,48 @@ class FilteringRequest extends ClientRequest { } } +/** + * A request to retrieve entities of the given type. + * + * Allows to post the query data to the Spine backend and receive the entity states as `Promise`. + * + * A usage example: + * ``` + * const customers = + * client.select(Customer.class) + * .byId(westCoastCustomerIds()) + * .withMask("name", "address", "email") + * .where(Filters.eq("type", "permanent"), + * Filters.eq("discount_percent", 10), + * Filters.eq("company_size", Company.Size.SMALL)) + * .orderBy("name", OrderBy.Direction.ASCENDING) + * .limit(20) + * .run(); // The returned type is `Promise`. + * } + * ``` + * + * All of the called filtering methods are optional. If none of them are specified, all entities + * of type will be retrieved. + * + * @template the query target type + */ export class QueryRequest extends FilteringRequest { + /** + * @param {!Class} targetType the target type of entities + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ constructor(targetType, client, actorRequestFactory) { super(targetType, client, actorRequestFactory) } /** + * Sets the sorting order for the retrieved results. * - * @param {!String} column - * @param {!OrderBy.Direction} direction - * @return {QueryRequest} self + * @param {!String} column the column to order by + * @param {!OrderBy.Direction} direction the ascending/descending direction + * @return {this} self for method chaining */ orderBy(column, direction) { if (direction === OrderBy.Direction.ASCENDING) { @@ -182,8 +231,12 @@ export class QueryRequest extends FilteringRequest { } /** + * Sets the maximum number of returned entities. + * + * Can only be used in conjunction with the {@link #orderBy} condition. + * * @param {number} count the max number of response entities - * @return {QueryRequest} self + * @return {this} self for method chaining */ limit(count) { this._builder().limit(count); @@ -191,16 +244,18 @@ export class QueryRequest extends FilteringRequest { } /** - * @return {spine.client.Query} + * Builds a `Query` instance based on currently specified filters. + * + * @return {spine.client.Query} a `Query` instance */ query() { return this._builder().build(); } /** - * @return {Promise<[]>} + * Runs the query and obtains the results as `Promise`. * - * @template a Protobuf type of entities being the target of a query + * @return {Promise<[]>} the asynchronously resolved query results */ run() { const query = this.query(); @@ -209,8 +264,6 @@ export class QueryRequest extends FilteringRequest { /** * @inheritDoc - * - * @return {Function} */ _newBuilderFn() { return requestFactory => requestFactory.query().select(this.targetType); @@ -218,8 +271,6 @@ export class QueryRequest extends FilteringRequest { /** * @inheritDoc - * - * @return {QueryRequest} */ _self() { return this; @@ -227,18 +278,27 @@ export class QueryRequest extends FilteringRequest { } /** + * An abstract base for requests that subscribe to messages of a certain type. + * * @abstract + * @template the target type of messages, for events the type is always `spine.core.Event` */ class SubscribingRequest extends FilteringRequest { + /** + * Builds a `Topic` instance based on the currently specified filters. + * + * @return {spine.client.Topic} a `Topic` instance + */ topic() { return this._builder().build(); } /** - * @return {Promise | EventSubscriptionObject>} + * Posts a subscription request and returns the result as `Promise`. * - * @template a Protobuf type of entities being the target of a subscription + * @return {Promise | EventSubscriptionObject>} + * the asynchronously resolved subscription object */ post() { const topic = this.topic(); @@ -247,8 +307,6 @@ class SubscribingRequest extends FilteringRequest { /** * @inheritDoc - * - * @return {Function} */ _newBuilderFn() { return requestFactory => requestFactory.topic().select(this.targetType); @@ -256,11 +314,8 @@ class SubscribingRequest extends FilteringRequest { /** * @abstract - * * @return {Promise | EventSubscriptionObject>} * - * @template a Protobuf type of entities being the target of a subscription - * * @protected */ _subscribe(topic) { @@ -268,8 +323,42 @@ class SubscribingRequest extends FilteringRequest { } } +/** + * A request to subscribe to updates of entity states of a certain type. + * + * Allows to obtain the `EntitySubscriptionObject` which exposes the entity changes in a form of + * callbacks which can be subscribed to. + * + * A usage example: + * ``` + * client.subscribeTo(Task.class) + * .where(Filters.eq("status", Task.Status.ACTIVE)) + * // Additional filtering can be done here. + * .post() + * .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + * itemAdded.subscribe(_addDisplayedTask); + * itemChanged.subscribe(_changeDisplayedTask); + * itemRemoved.subscribe(_removeDisplayedTask); + * }); + * } + * ``` + * + * If the entity matched the subscription criteria at one point, but stopped to do so, the + * `itemRemoved` callback will be triggered for it. The callback will contain the last entity state + * that matched the subscription. + * + * Please note that the subscription object should be manually unsubscribed when it's no longer + * needed to receive the updates. This can be done with the help of `unsubscribe` callback. + * + * @template the target entity type + */ export class SubscriptionRequest extends SubscribingRequest { + /** + * @param {!Class} entityType the target entity type + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ constructor(entityType, client, actorRequestFactory) { super(entityType, client, actorRequestFactory) } @@ -278,8 +367,6 @@ export class SubscriptionRequest extends SubscribingRequest { * @inheritDoc * * @return {Promise>} - * - * @template */ _subscribe(topic) { return this._client.subscribe(topic); @@ -287,16 +374,46 @@ export class SubscriptionRequest extends SubscribingRequest { /** * @inheritDoc - * - * @return {SubscriptionRequest} */ _self() { return this; } } +/** + * A request to subscribe to events of a certain type. + * + * Allows to obtain the `EventSubscriptionObject` which reflects the events that happened in the + * system and match the subscription criteria. + * + * A usage example: + * ``` + * client.subscribeTo(TaskCreated.class) + * .where([Filters.eq("task_priority", Task.Priority.HIGH), + * Filters.eq("context.past_message.actor_context.actor", userId)]) + * .post() + * .then(({eventEmitted, unsubscribe}) => { + * eventEmitted.subscribe(_logEvent); + * }); + * } + * ``` + * + * The fields specified to the `where` filters should either be a part of the event message or + * have a `context.` prefix and address one of the fields of the `EventContext` type. + * + * The `eventEmitted` callback reflects all the events that occurred in the system and match the + * subscription criteria, in the form of `spine.core.Event`. + * + * Please note that the subscription object should be manually unsubscribed when it's no longer + * needed to receive the updates. This can be done with the help of `unsubscribe` callback. + */ export class EventSubscriptionRequest extends SubscribingRequest { + /** + * @param {!Class} eventType the target event type + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ constructor(eventType, client, actorRequestFactory) { super(eventType, client, actorRequestFactory) } @@ -312,8 +429,6 @@ export class EventSubscriptionRequest extends SubscribingRequest { /** * @inheritDoc - * - * @return {EventSubscriptionRequest} */ _self() { return this; @@ -322,12 +437,40 @@ export class EventSubscriptionRequest extends SubscribingRequest { const NOOP_CALLBACK = () => {}; -export class CommandRequest extends ClientRequest{ +/** + * A request to post a command to the Spine backend. + * + * Optionally allows to subscribe to events that are the immediate results of handling the command. + * + * A usage example: + * ``` + * client.command(logInUser) + * .onOk(_logOk) + * .onError(_logError) + * .observe(UserLoggedIn.class) + * .observe(UserAlreadyLoggedIn.class) + * .post() + * .then(([{eventEmitted1, unsubscribe1}, {eventEmitted2, unsubscribe2}]) => { + * eventEmitted1.subscribe(_logUserLoggedIn); + * eventEmitted2.subscribe(_logUserAlreadyLoggedIn); + * unsubscribe1(); + * unsubscribe2(); + * }); + * ``` + * + * The `Promise` returned from `post` will be resolved with either a single + * `EventSubscriptionObject` if a single type is observed or with an array of + * `EventSubscriptionObject`s otherwise. + * + * Please note that the returned subscription objects should be manually unsubscribed with the help + * of `unsubscribe` callback. + */ +export class CommandRequest extends ClientRequest { /** - * @param {!Message} commandMessage - * @param {!Client} client - * @param {!ActorRequestFactory} actorRequestFactory + * @param {!Message} commandMessage the command to post + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory */ constructor(commandMessage, client, actorRequestFactory) { super(client, actorRequestFactory); @@ -339,9 +482,10 @@ export class CommandRequest extends ClientRequest{ } /** - * @param {!parameterlessCallback} callback + * Runs the callback if the command is successfully handled by the Spine server. * - * @return {CommandRequest} self + * @param {!parameterlessCallback} callback the callback to run + * @return {this} self for method chaining */ onOk(callback) { this._onAck = callback; @@ -349,9 +493,11 @@ export class CommandRequest extends ClientRequest{ } /** - * @param {!consumerCallback} callback + * Runs the callback if the command could not be handled by the Spine server due to the + * technical error. * - * @return {CommandRequest} self + * @param {!consumerCallback} callback the callback to run + * @return {this} self for method chaining */ onError(callback) { this._onError = callback; @@ -359,9 +505,14 @@ export class CommandRequest extends ClientRequest{ } /** - * @param {!consumerCallback} callback + * Runs the callback if the server responded with the `rejection` status on a command. * - * @return {CommandRequest} self + * Note that with the current Spine server implementation this never happens. At the moment, + * prefer using the `observe` method if you need to check for the rejection being a command + * handling result. + * + * @param {!consumerCallback} callback + * @return {this} self for method chaining */ onRejection(callback) { this._onRejection = callback; @@ -369,9 +520,10 @@ export class CommandRequest extends ClientRequest{ } /** - * @param {!Class} eventType a Protobuf type of the observed events + * Adds the event type to the list of observed command handling results. * - * @return {CommandRequest} self + * @param {!Class} eventType a type of the observed events + * @return {this} self for method chaining */ observe(eventType) { this._observedTypes.push(eventType); @@ -379,7 +531,11 @@ export class CommandRequest extends ClientRequest{ } /** - * @return {Promise} + * Posts the command to the server and subscribes to all observed types. + * + * @return {Promise} + * the asynchronously resolved subscription objects for the events specified in + * the `observe` */ post() { const command = this._requestFactory.command().create(this._commandMessage); @@ -406,7 +562,6 @@ export class CommandRequest extends ClientRequest{ /** * @param {!Command} command - * * @return {Origin} * * @private From b378305b57dbbbee6bd5e0d5e04cc63728ac340a Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 9 Dec 2019 20:56:49 +0200 Subject: [PATCH 41/70] Add/update the `Client` doc --- client-js/main/client/client.js | 69 ++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index d10b36a51..20a8860e8 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -40,14 +40,14 @@ import {Observable} from 'rxjs'; /** * @typedef {Object} EntitySubscriptionObject * - * An object representing a result of the subscription to entities state changes. - * The entities that already exist will be initially passed to the `itemAdded` observer. + * An object representing a result of the subscription to entity state changes. * * @property {!Observable} itemAdded emits new items matching the subscription topic * @property {!Observable} itemChanged emits updated items matching the subscription topic * @property {!Observable} itemRemoved emits removed items matching the subscription topic * @property {!parameterlessCallback} unsubscribe a method to be called to cancel the subscription, - * stopping the subscribers from receiving new entities + * stopping the subscribers from receiving new + * entities * * @template a type of the subscription target entities */ @@ -55,13 +55,21 @@ import {Observable} from 'rxjs'; /** * @typedef {Object} EventSubscriptionObject * - * @property > eventEmitted - * @property {!parameterlessCallback} unsubscribe + * An object which represents a result of the subscription to events of a certain type. + * + * @property > eventEmitted emits new items when the new events + * matching the subscription topic occur in + * the system + * @property {!parameterlessCallback} unsubscribe a method to be called to cancel the subscription, + * stopping the subscribers from receiving new + * entities */ /** * @typedef AckCallback * + * Represents a command acknowledgement callback. + * * @property {!parameterlessCallback} onOk * @property {!consumerCallback} onError * @property {!consumerCallback} onRejection @@ -76,8 +84,10 @@ import {Observable} from 'rxjs'; export class Client { /** + * Creates a query request that allows to configure and post a new query. + * * @param {!Class} entityType a Protobuf type of the query target entities - * @return {QueryRequest} + * @return {QueryRequest} the builder to construct and post a new query */ select(entityType) { throw new Error('Not implemented in abstract base.'); @@ -99,34 +109,50 @@ export class Client { } /** + * Creates a subscription request that allows to configure and post a new entity subscription. + * * @param {!Class} entityType a Protobuf type of the target entities - * @return {SubscriptionRequest} + * @return {SubscriptionRequest} the builder for the new entity subscription */ subscribeTo(entityType) { throw new Error('Not implemented in abstract base.'); } /** - * @param {!Class} eventType a Protobuf type of the target events - * @return {EventSubscriptionRequest} + * Subscribes to the given `Topic` instance. + * + * The topic should have an entity type as target. Use {@link #subscribeToEvents} to subscribe to + * the topic that targets events. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise>} the subscription object which + * exposes entity changes via its + * callbacks + * + * @template a Protobuf type of entities being the target of a subscription */ - subscribeToEvent(eventType) { + subscribe(topic) { throw new Error('Not implemented in abstract base.'); } /** - * @param {!spine.client.Topic} topic - * - * @return {Promise>} + * Creates an event subscription request that allows to configure and post a new event + * subscription. * - * @template a Protobuf type of entities being the target of a subscription + * @param {!Class} eventType a Protobuf type of the target events + * @return {EventSubscriptionRequest} the builder for the new event subscription */ - subscribe(topic) { + subscribeToEvent(eventType) { throw new Error('Not implemented in abstract base.'); } /** - * @param {!spine.client.Topic} topic + * Subscribes to the given `Topic` instance. + * + * The given topic should target an event type. To perform an entity subscription, use + * {@link #subscribe}. + * + * @param {!spine.client.Topic} topic a topic to subscribe to * * @return {Promise} */ @@ -135,16 +161,21 @@ export class Client { } /** - * @param {!Message} commandMessage a Protobuf type of the query target entities - * @return {CommandRequest} + * Creates a new command request which allows to post a command to the Spine server and + * configures the command handling callbacks. + * + * @param {!Message} commandMessage a command to post to the server + * @return {CommandRequest} a new command request */ command(commandMessage) { throw new Error('Not implemented in abstract base.'); } /** + * Posts the given command to the Spine server. + * * @param {!spine.core.Command} command a Command sent to Spine server - * @param {!AckCallback} onAck + * @param {!AckCallback} onAck a command acknowledgement callback */ post(command, onAck) { throw new Error('Not implemented in abstract base.'); From 74cc6b4928940b86735aff999c8ff9bedb3ce6b9 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 9 Dec 2019 21:08:57 +0200 Subject: [PATCH 42/70] Add doc to the composite client --- client-js/main/client/client.js | 2 +- client-js/main/client/composite-client.js | 52 ++++++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index 20a8860e8..161af43db 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -172,7 +172,7 @@ export class Client { } /** - * Posts the given command to the Spine server. + * Posts a given command to the Spine server. * * @param {!spine.core.Command} command a Command sent to Spine server * @param {!AckCallback} onAck a command acknowledgement callback diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index 9208b1be2..32ad8d80d 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -139,6 +139,13 @@ export class QueryingClient { this._requestFactory = actorRequestFactory; } + /** + * Creates a new query request. + * + * @param {!Class} entityType the target entity type + * @param {!Client} client the client which initiated the request + * @return {QueryRequest} a new query request + */ select(entityType, client) { return new QueryRequest(entityType, client, this._requestFactory); } @@ -174,16 +181,22 @@ export class SubscribingClient { this._requestFactory = actorRequestFactory; } + /** + * Creates a new subscription request. + * + * @param {!Class} type the target entity type + * @param {!Client} client the client that initiated the request + * @return {SubscriptionRequest} a new subscription request + */ subscribeTo(type, client) { return new SubscriptionRequest(type, client, this._requestFactory); } - subscribeToEvent(type, client) { - return new EventSubscriptionRequest(type, client, this._requestFactory); - } - /** - * @return {Promise>} + * Subscribes to a given topic which targets an entity type. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise>} a subscription object * * @template a Protobuf type of entities being the target of a subscription */ @@ -192,7 +205,21 @@ export class SubscribingClient { } /** - * @return {Promise} + * Creates a new event subscription request. + * + * @param {!Class} type the target event type + * @param {!Client} client the client that initiated the request + * @return {EventSubscriptionRequest} a new event subscription request + */ + subscribeToEvent(type, client) { + return new EventSubscriptionRequest(type, client, this._requestFactory); + } + + /** + * Subscribes to the given topic which targets an event type. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise} a subscription object */ subscribeToEvents(topic) { throw new Error('Not implemented in abstract base.'); @@ -243,10 +270,23 @@ export class CommandingClient { this._endpoint = endpoint; } + /** + * Creates a new command request. + * + * @param {!Message} commandMessage the command to send to the server + * @param {!Client} client the client which initiated the request + * @return {CommandRequest} a new command request + */ command(commandMessage, client) { return new CommandRequest(commandMessage, client, this._requestFactory); } + /** + * Posts a given command to the Spine server. + * + * @param {!spine.core.Command} command a Command sent to Spine server + * @param {!AckCallback} onAck a command acknowledgement callback + */ post(command, onAck) { const cmd = TypedMessage.of(command); this._endpoint.command(cmd) From 48233293ca0c8d34afffd891c080f56b70ba61a5 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 9 Dec 2019 21:13:14 +0200 Subject: [PATCH 43/70] Reword doc --- client-js/main/client/firebase-subscription-service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-js/main/client/firebase-subscription-service.js b/client-js/main/client/firebase-subscription-service.js index 815a6e711..9901b9520 100644 --- a/client-js/main/client/firebase-subscription-service.js +++ b/client-js/main/client/firebase-subscription-service.js @@ -77,7 +77,7 @@ export class FirebaseSubscriptionService { /** * Sends the "keep-up" request for all active subscriptions. * - * The non-`OK` response status means the subscription has already been canceled by the server, + * The non-`OK` response status means the subscription has already been canceled on the server, * most likely due to a timeout. So, in such case, the subscription is removed from the list of * active ones. * From afd034eaef46404a42cba74e84cbefcbbbac2828 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 9 Dec 2019 21:19:32 +0200 Subject: [PATCH 44/70] Add doc to the new `FirebaseClient`-related entities --- client-js/main/client/firebase-client.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 65b400f8e..5c6b88475 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -40,11 +40,16 @@ import { } from "./composite-client"; /** + * An abstract base for subscription objects. + * * @abstract */ class SpineSubscription extends Subscription { /** + * @param {Function} unsubscribe the callbacks that allows to cancel the subscription + * @param {SubscriptionObject} subscription the wrapped subscription object + * * @protected */ constructor(unsubscribe, subscription) { @@ -75,9 +80,10 @@ class SpineSubscription extends Subscription { class EntitySubscription extends SpineSubscription { /** - * @param {Function} unsubscribe + * @param {Function} unsubscribe the callbacks that allows to cancel the subscription * @param {{itemAdded: Observable, itemChanged: Observable, itemRemoved: Observable}} observables - * @param {SubscriptionObject} subscription + * the observables for entity changes + * @param {SubscriptionObject} subscription the wrapped subscription object */ constructor({ unsubscribedBy: unsubscribe, @@ -96,12 +102,15 @@ class EntitySubscription extends SpineSubscription { } } +/** + * A subscription to events that occur in the system. + */ class EventSubscription extends SpineSubscription { /** - * @param {Function} unsubscribe - * @param {Observable} eventEmitted - * @param {SubscriptionObject} subscription + * @param {Function} unsubscribe the callbacks that allows to cancel the subscription + * @param {Observable} eventEmitted the observable for the emitted events + * @param {SubscriptionObject} subscription the wrapped subscription object */ constructor({ unsubscribedBy: unsubscribe, From 9457c401ba677e13fabec8c51b1dda63fa3a1e85 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Mon, 9 Dec 2019 21:26:54 +0200 Subject: [PATCH 45/70] Add doc to the filtering request test --- client-js/test/client/filtering-request-test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client-js/test/client/filtering-request-test.js b/client-js/test/client/filtering-request-test.js index a932240e0..aa7427597 100644 --- a/client-js/test/client/filtering-request-test.js +++ b/client-js/test/client/filtering-request-test.js @@ -46,6 +46,18 @@ export class Given { } } +/** + * Emulates an abstract test. + * + * To run, call the function inside the actual test with the necessary callbacks provided. + * + * @param newRequest a callback which accepts a target type, client and request factory and creates + * a new filtering request + * @param buildResult a callback which accepts a filtering request and builds the result message + * that is sent to the Spine server (e.g. `Query`, `Topic`) + * @param getTarget a callback which extracts the target from the result message + * @param getFieldMask a callback which extracts the field mask from the result message + */ export function filteringRequestTest(newRequest, buildResult, getTarget, getFieldMask) { const targetType = Given.targetType(); From adacc9e1d18774fbf159c65178b8193e1453fd81 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 10 Dec 2019 15:06:19 +0200 Subject: [PATCH 46/70] Improve doc wording and formatting --- client-js/main/client/client-request.js | 20 +++++++++---------- client-js/main/client/client.js | 18 +++++++++-------- .../test/client/filtering-request-test.js | 5 ++++- .../firebase/subscription/UpdatePayload.java | 3 --- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 2a5f534ff..ad2ccef2d 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -112,7 +112,7 @@ class FilteringRequest extends ClientRequest { * The names of the fields must be formatted according to the `google.protobuf.FieldMask` * specification. * - * @param {!String|String[]} fieldPaths the fields to include to the mask + * @param {!String|String[]} fieldPaths the fields to include in the mask * @return {this} self for method chaining */ withMask(fieldPaths) { @@ -140,7 +140,7 @@ class FilteringRequest extends ClientRequest { * Returns the function with which the {@link _builderInstance} can be created. * * @abstract - * @return {Function} the function + * @return {Function} * * @protected */ @@ -253,7 +253,7 @@ export class QueryRequest extends FilteringRequest { } /** - * Runs the query and obtains the results as `Promise`. + * Runs the query and obtains the results. * * @return {Promise<[]>} the asynchronously resolved query results */ @@ -401,8 +401,8 @@ export class SubscriptionRequest extends SubscribingRequest { * The fields specified to the `where` filters should either be a part of the event message or * have a `context.` prefix and address one of the fields of the `EventContext` type. * - * The `eventEmitted` callback reflects all the events that occurred in the system and match the - * subscription criteria, in the form of `spine.core.Event`. + * The `eventEmitted` observable reflects all events that occurred in the system and match the + * subscription criteria, in a form of `spine.core.Event`. * * Please note that the subscription object should be manually unsubscribed when it's no longer * needed to receive the updates. This can be done with the help of `unsubscribe` callback. @@ -496,7 +496,7 @@ export class CommandRequest extends ClientRequest { * Runs the callback if the command could not be handled by the Spine server due to the * technical error. * - * @param {!consumerCallback} callback the callback to run + * @param {!consumerCallback} callback the callback to run * @return {this} self for method chaining */ onError(callback) { @@ -507,11 +507,11 @@ export class CommandRequest extends ClientRequest { /** * Runs the callback if the server responded with the `rejection` status on a command. * - * Note that with the current Spine server implementation this never happens. At the moment, - * prefer using the `observe` method if you need to check for the rejection being a command - * handling result. + * Note that with the current Spine server implementation the command being rejected right away + * is very unlikely. In most cases, the command will be acknowledged with `OK` status and only + * then lead to a business rejection. You can check this scenario using the `observe` method. * - * @param {!consumerCallback} callback + * @param {!consumerCallback} callback * @return {this} self for method chaining */ onRejection(callback) { diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index 161af43db..5ecf244ed 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -40,7 +40,7 @@ import {Observable} from 'rxjs'; /** * @typedef {Object} EntitySubscriptionObject * - * An object representing a result of the subscription to entity state changes. + * An object representing the result of a subscription to entity state changes. * * @property {!Observable} itemAdded emits new items matching the subscription topic * @property {!Observable} itemChanged emits updated items matching the subscription topic @@ -55,7 +55,7 @@ import {Observable} from 'rxjs'; /** * @typedef {Object} EventSubscriptionObject * - * An object which represents a result of the subscription to events of a certain type. + * An object which represents the result of a subscription to events of a certain type. * * @property > eventEmitted emits new items when the new events * matching the subscription topic occur in @@ -70,9 +70,12 @@ import {Observable} from 'rxjs'; * * Represents a command acknowledgement callback. * - * @property {!parameterlessCallback} onOk - * @property {!consumerCallback} onError - * @property {!consumerCallback} onRejection + * @property {!parameterlessCallback} onOk the callback to run when the command is handled properly + * @property {!consumerCallback} onError the callback to run when the command cannot be + * handled due to a technical error + * @property {!consumerCallback} onRejection the callback to run when the command cannot + * be handled properly because of the business + * rejection */ /** @@ -125,9 +128,8 @@ export class Client { * the topic that targets events. * * @param {!spine.client.Topic} topic a topic to subscribe to - * @return {Promise>} the subscription object which - * exposes entity changes via its - * callbacks + * @return {Promise>} + * the subscription object which exposes entity changes via its callbacks * * @template a Protobuf type of entities being the target of a subscription */ diff --git a/client-js/test/client/filtering-request-test.js b/client-js/test/client/filtering-request-test.js index aa7427597..ca70e1f4e 100644 --- a/client-js/test/client/filtering-request-test.js +++ b/client-js/test/client/filtering-request-test.js @@ -53,7 +53,7 @@ export class Given { * * @param newRequest a callback which accepts a target type, client and request factory and creates * a new filtering request - * @param buildResult a callback which accepts a filtering request and builds the result message + * @param buildResult a callback which accepts a filtering request and builds the message * that is sent to the Spine server (e.g. `Query`, `Topic`) * @param getTarget a callback which extracts the target from the result message * @param getFieldMask a callback which extracts the field mask from the result message @@ -221,6 +221,9 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel done(); }); + /** + * @param {!spine.client.Target} target + */ function assertIsIncludeAll(target) { assertTargetTypeEquals(target); assert.equal( diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java index 5989445dd..e1ffd0d8e 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java @@ -162,9 +162,6 @@ private static void addChildOrNull(NodeValue node, String id, Message message) { /** * An identity {@link Event}-to-{@link Message} conversion. - * - *

The standard {@link Function#identity()} cannot be applied because of the type arguments - * mismatch. */ private static Function identity() { return event -> event; From 7de6f9b28a809ab7995f37bb5db1c83eafd7291a Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 10 Dec 2019 15:22:54 +0200 Subject: [PATCH 47/70] Fix a code example --- client-js/main/client/client-request.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index ad2ccef2d..aa106e3ef 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -189,9 +189,9 @@ class FilteringRequest extends ClientRequest { * client.select(Customer.class) * .byId(westCoastCustomerIds()) * .withMask("name", "address", "email") - * .where(Filters.eq("type", "permanent"), + * .where([Filters.eq("type", "permanent"), * Filters.eq("discount_percent", 10), - * Filters.eq("company_size", Company.Size.SMALL)) + * Filters.eq("company_size", Company.Size.SMALL)]) * .orderBy("name", OrderBy.Direction.ASCENDING) * .limit(20) * .run(); // The returned type is `Promise`. From da62a181826305b8fa21fac5c58c484b44ef4d86 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 10 Dec 2019 15:27:28 +0200 Subject: [PATCH 48/70] Remove redundant braces --- client-js/main/client/client-request.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index aa106e3ef..a3d8c2b78 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -195,7 +195,6 @@ class FilteringRequest extends ClientRequest { * .orderBy("name", OrderBy.Direction.ASCENDING) * .limit(20) * .run(); // The returned type is `Promise`. - * } * ``` * * All of the called filtering methods are optional. If none of them are specified, all entities @@ -340,7 +339,6 @@ class SubscribingRequest extends FilteringRequest { * itemChanged.subscribe(_changeDisplayedTask); * itemRemoved.subscribe(_removeDisplayedTask); * }); - * } * ``` * * If the entity matched the subscription criteria at one point, but stopped to do so, the @@ -395,7 +393,6 @@ export class SubscriptionRequest extends SubscribingRequest { * .then(({eventEmitted, unsubscribe}) => { * eventEmitted.subscribe(_logEvent); * }); - * } * ``` * * The fields specified to the `where` filters should either be a part of the event message or From 1504c96a53fbeefce92d0d1b8c4ef2bd63428bf4 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 10 Dec 2019 15:35:33 +0200 Subject: [PATCH 49/70] Fix the code example --- client-js/main/client/client-request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index a3d8c2b78..694c5411e 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -386,7 +386,7 @@ export class SubscriptionRequest extends SubscribingRequest { * * A usage example: * ``` - * client.subscribeTo(TaskCreated.class) + * client.subscribeToEvent(TaskCreated.class) * .where([Filters.eq("task_priority", Task.Priority.HIGH), * Filters.eq("context.past_message.actor_context.actor", userId)]) * .post() From 4527e81d6f95e846bb1102923de6771870518775 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 10 Dec 2019 16:07:02 +0200 Subject: [PATCH 50/70] Resolve todos --- .../test/firebase-client/subscribe-test.js | 2 -- integration-tests/test-app/build.gradle | 21 ------------------- 2 files changed, 23 deletions(-) diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index b8dde0ce2..dbc00c068 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -218,8 +218,6 @@ describe('FirebaseClient subscription', function () { .catch(fail(done)); }); - // TODO:2019-11-27:dmytro.kuzmin:WIP Resolve presence/absence of backticks in string messages, - // formatting of chained method calls, at least in the modified files. it('is notified when the entity no longer matches the subscription criteria', done => { const initialTaskName = 'Initial task name'; const nameAfterRenamed = 'Renamed task'; diff --git a/integration-tests/test-app/build.gradle b/integration-tests/test-app/build.gradle index 4e07e86cf..6119ad0b1 100644 --- a/integration-tests/test-app/build.gradle +++ b/integration-tests/test-app/build.gradle @@ -18,33 +18,12 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -// TODO:2019-11-27:dmytro.kuzmin:WIP Remove when the necessary configurations are merged into -// `config`. Here and below. -buildscript { - configurations.all { - resolutionStrategy { - //noinspection GroovyAssignabilityCheck - force( - 'commons-cli:commons-cli:1.4' - ) - } - } -} - plugins { id 'java' id 'org.gretty' version '3.0.1' id "com.github.psxpaul.execfork" version '0.1.12' } -configurations.all { - resolutionStrategy { - force( - 'commons-cli:commons-cli:1.4' - ) - } -} - apply plugin: 'io.spine.tools.spine-model-compiler' apply from: "$rootDir/config/gradle/model-compiler.gradle" From f9396f86399376701332df873ba2fd804c51f4d7 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Tue, 10 Dec 2019 16:39:19 +0200 Subject: [PATCH 51/70] Update `config` --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 5dfa89584..d0c113735 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 5dfa8958451663696ebecd2c978fe0122b926417 +Subproject commit d0c113735c2297f5d4eec8bd6a8660ee4a9bee2e From abd396d5f565161ab6b97223c418fae2c973d476 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 18:13:47 +0200 Subject: [PATCH 52/70] Improve `CommandRequest.observe(...)` and related API --- client-js/main/client/client-request.js | 74 +++++++++++-------- client-js/main/client/client.js | 8 ++ .../test/firebase-client/send-command-test.js | 67 ++++++++--------- .../test/firebase-client/subscribe-test.js | 2 +- 4 files changed, 85 insertions(+), 66 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 694c5411e..c6b2acab0 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -29,6 +29,18 @@ import {AnyPacker} from "./any-packer"; import {Filters} from "./actor-request-factory"; import {Type} from "./typed-message"; +/** + * @typedef EventSubscriptionCallbacks + * + * A pair of callbacks that allow to add an event consumer and cancel the subscription + * respectively. + * + * @property {consumerCallback} subscribe the callback which allows to setup an + * event consumer to use for the subscription + * @property {parameterlessCallback} unsubscribe the callback which allows to cancel the + * subscription + */ + /** * A request from a client to the Spine backend. * @@ -444,23 +456,22 @@ const NOOP_CALLBACK = () => {}; * client.command(logInUser) * .onOk(_logOk) * .onError(_logError) - * .observe(UserLoggedIn.class) - * .observe(UserAlreadyLoggedIn.class) - * .post() - * .then(([{eventEmitted1, unsubscribe1}, {eventEmitted2, unsubscribe2}]) => { - * eventEmitted1.subscribe(_logUserLoggedIn); - * eventEmitted2.subscribe(_logUserAlreadyLoggedIn); - * unsubscribe1(); - * unsubscribe2(); - * }); + * .observe(UserLoggedIn.class, (({subscribe, unsubscribe}) => { + * subscribe(event => _logAndUnsubscribe(event, unsubscribe)); + * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); + * }) + * .observe(UserAlreadyLoggedIn.class, (({subscribe, unsubscribe}) => { + * subscribe(event => _warnAboutAndUnsubscribe(event, unsubscribe)); + * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); + * }) + * .post(); * ``` * - * The `Promise` returned from `post` will be resolved with either a single - * `EventSubscriptionObject` if a single type is observed or with an array of - * `EventSubscriptionObject`s otherwise. + * The `subscribe` callback provided to the consumer allows to configure an event receival process + * while the `unsubscribe` callback allows to cancel the subscription. * - * Please note that the returned subscription objects should be manually unsubscribed with the help - * of `unsubscribe` callback. + * Please note that in the example above we make sure the subscription is cancelled even if the + * specified event type is never received. */ export class CommandRequest extends ClientRequest { @@ -520,41 +531,44 @@ export class CommandRequest extends ClientRequest { * Adds the event type to the list of observed command handling results. * * @param {!Class} eventType a type of the observed events + * @param {!consumerCallback} consumer + * a consumer of the `subscribe` and `unsubscribe` callbacks which are responsible for + * accepting the incoming events and cancelling the subscription respectively * @return {this} self for method chaining */ - observe(eventType) { - this._observedTypes.push(eventType); + observe(eventType, consumer) { + this._observedTypes.push({type: eventType, consumer: consumer}); return this; } /** * Posts the command to the server and subscribes to all observed types. * - * @return {Promise} - * the asynchronously resolved subscription objects for the events specified in - * the `observe` + * @return {Promise} a promise that signals if the command posting was done successfully, + * may be ignored */ post() { const command = this._requestFactory.command().create(this._commandMessage); const onAck = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; const promises = []; - this._observedTypes.forEach(type => { + this._observedTypes.forEach(({type, consumer}) => { const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); const promise = this._client.subscribeToEvent(type) .where(originFilter) - .post(); + .post() + .then(({eventEmitted, unsubscribe}) => { + const subscribe = eventConsumer => { + eventEmitted.subscribe({ + next: eventConsumer + }); + }; + consumer({subscribe, unsubscribe}); + }); promises.push(promise); }); - const subscriptionPromise = promises.length === 1 - ? promises[0] - : Promise.all(promises); - - // noinspection JSValidateTypes - return subscriptionPromise.then((subscriptionObject) => { - this._client.post(command, onAck); - return subscriptionObject; - }); + const subscriptionPromise = Promise.all(promises); + return subscriptionPromise.then(() => this._client.post(command, onAck)); } /** diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index 5ecf244ed..110de91b7 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -37,6 +37,14 @@ import {Observable} from 'rxjs'; * @template the type of value passed to the callback */ +/** + * The callback that accepts a single `Event` as a parameter. + * + * @callback eventConsumer + * + * @param {spine.core.Event} the event that is accepted by the callback + */ + /** * @typedef {Object} EntitySubscriptionObject * diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index 0cf580d46..e300b99a4 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -134,39 +134,35 @@ describe('FirebaseClient command sending', function () { client.command(command) .onError(fail(done)) .onRejection(fail(done)) - .observe(TaskCreated) - .post() - .then(({eventEmitted, unsubscribe}) => { - eventEmitted.subscribe({ - next: event => { - const packedMessage = event.getMessage(); - const taskCreatedType = Type.forClass(TaskCreated); - const message = AnyPacker.unpack(packedMessage).as(taskCreatedType); - const theTaskId = message.getId().getValue(); - assert.equal( - taskId, theTaskId, - `Expected the task ID to be '${taskId}', got '${theTaskId}' instead.` - ); - const theTaskName = message.getName(); - assert.equal( - taskName, theTaskName, - `Expected the task name to be '${taskName}', got '${theTaskName}' - instead.` - ); - const origin = event.getContext().getPastMessage().getMessage(); - const originType = origin.getTypeUrl(); - const createTaskType = Type.forClass(CreateTask); - const expectedOriginType = createTaskType.url().value(); - assert.equal( - expectedOriginType, originType, - `Expected origin to be of type '${expectedOriginType}', got + .observe(TaskCreated, ({subscribe, unsubscribe}) => { + subscribe(event => { + const packedMessage = event.getMessage(); + const taskCreatedType = Type.forClass(TaskCreated); + const message = AnyPacker.unpack(packedMessage).as(taskCreatedType); + const theTaskId = message.getId().getValue(); + assert.equal( + taskId, theTaskId, + `Expected the task ID to be '${taskId}', got '${theTaskId}' instead.` + ); + const theTaskName = message.getName(); + assert.equal( + taskName, theTaskName, + `Expected the task name to be '${taskName}', got '${theTaskName}' instead.` + ); + const origin = event.getContext().getPastMessage().getMessage(); + const originType = origin.getTypeUrl(); + const createTaskType = Type.forClass(CreateTask); + const expectedOriginType = createTaskType.url().value(); + assert.equal( + expectedOriginType, originType, + `Expected origin to be of type '${expectedOriginType}', got '${originType}' instead.` - ); - unsubscribe(); - done(); - } + ); + unsubscribe(); + done(); }); - }); + }) + .post() }); it('fails when trying to observe a malformed event type', done => { @@ -188,12 +184,13 @@ describe('FirebaseClient command sending', function () { .observe(Unknown) .post() .then(() => { - done(new Error('An attempt to observe a malformed event type should not yield ' + - 'results.')); + done(new Error('An attempt to observe a malformed event type did not lead to an ' + + 'error.')); }) - .catch(error => { + .catch(() => { assert.ok(true); done(); - }); + } + ); }); }); diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index dbc00c068..f8fff1ce5 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -309,7 +309,7 @@ describe('FirebaseClient subscription', function () { assert.equal( updatedTaskName, newTaskName, `Expected the new task name to be ${updatedTaskName}, got - ${newTaskName} instead.` + ${newTaskName} instead.` ); unsubscribe(); done(); From 86435be6ae0ea9821fb70d05000a0c18af02456e Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 20:54:06 +0200 Subject: [PATCH 53/70] Bring back the old API Some is now deprecated. Some of the high-level API is not brought back as it has direct analogues in the new API. --- client-js/main/client/client.js | 127 ++++++++++++++++++ client-js/main/client/composite-client.js | 32 +++++ .../test/firebase-client/fetch-test.js | 20 +++ .../test/firebase-client/subscribe-test.js | 32 ++++- 4 files changed, 210 insertions(+), 1 deletion(-) diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index 110de91b7..a00ed653a 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -119,6 +119,54 @@ export class Client { throw new Error('Not implemented in abstract base.'); } + /** + * Executes the given `Query` instance specifying the data to be retrieved from + * Spine server fulfilling a returned promise with an array of received objects. + * + * @param {!spine.client.Query} query a query instance to be executed + * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf + * messages of a given type or with an empty list if no entities matching given query + * were found; rejected with a `SpineError` if error occurs; + * + * @template a Protobuf type of entities being the target of a query + * + * @deprecated Please use {@link Client#read()} instead + */ + execute(query) { + return this.read(query); + } + + /** + * Creates a new {@link QueryFactory} for creating `Query` instances specifying + * the data to be retrieved from Spine server. + * + * @example + * // Build a query for `Task` domain entity, specifying particular IDs. + * newQuery().select(Task) + * .byIds([taskId1, taskId2]) + * .build() + * + * @example + * // Build a query for `Task` domain entity, selecting the instances which assigned to the + * // particular user. + * newQuery().select(Task) + * .where([Filters.eq('assignee', userId)]) + * .build() + * + * To execute the resulting `Query` instance pass it to the {@link Client#execute()}. + * + * Alternatively, the `QueryRequest` API can be used. See {@link Client#select()}. + * + * @return {QueryFactory} a factory for creating queries to the Spine server + * + * @see QueryFactory + * @see QueryBuilder + * @see AbstractTargetBuilder + */ + newQuery() { + throw new Error('Not implemented in abstract base.'); + } + /** * Creates a subscription request that allows to configure and post a new entity subscription. * @@ -170,6 +218,38 @@ export class Client { throw new Error('Not implemented in abstract base.'); } + /** + * Creates a new {@link TopicFactory} for building subscription topics specifying + * the state changes to be observed from Spine server. + * + * @example + * // Build a subscription topic for `UserTasks` domain entity. + * newTopic().select(Task) + * .build() + * + * @example + * // Build a subscription topic for `UserTasks` domain entity, selecting the instances + * // whose task count is greater than 3. + * newTopic().select(UserTasks) + * .where(Filters.gt('tasksCount', 3)) + * .build() + * + * To turn the resulting `Topic` instance into a subscription pass it + * to the {@link Client#subscribe()}. + * + * Alternatively, the `SubscriptionRequest` API can be used. See {@link Client#subscribeTo()}, + * {@link Client#subscribeToEvents()}. + * + * @return {TopicFactory} a factory for creating subscription topics to the Spine server + * + * @see TopicFactory + * @see TopicBuilder + * @see AbstractTargetBuilder + */ + newTopic() { + throw new Error('Not implemented in abstract base.'); + } + /** * Creates a new command request which allows to post a command to the Spine server and * configures the command handling callbacks. @@ -190,4 +270,51 @@ export class Client { post(command, onAck) { throw new Error('Not implemented in abstract base.'); } + + /** + * Sends the provided command to the server. + * + * After sending the command to the server the following scenarios are possible: + * + * - the `acknowledgedCallback` is called if the command is acknowledged for further processing + * - the `errorCallback` is called if sending of the command failed + * + * Invocation of the `acknowledgedCallback` and the `errorCallback` are mutually exclusive. + * + * If the command sending fails, the respective error is passed to the `errorCallback`. This error + * is always the type of `CommandHandlingError`. Its cause can be retrieved by `getCause()` method + * and can be represented with the following types of errors: + * + * - `ConnectionError` – if the connection error occurs; + * - `ClientError` – if the server responds with `4xx` HTTP status code; + * - `ServerError` – if the server responds with `5xx` HTTP status code; + * - `spine.base.Error` – if the command message can't be processed by the server; + * - `SpineError` – if parsing of the response fails; + * + * If the command sending fails due to a command validation error, an error passed to the + * `errorCallback` is the type of `CommandValidationError` (inherited from + * `CommandHandlingError`). The validation error can be retrieved by `validationError()` method. + * + * The occurrence of an error does not guarantee that the command is not accepted by the server + * for further processing. To verify this, call the error `assuresCommandNeglected()` method. + * + * @param {!Message} commandMessage a Protobuf message representing the command + * @param {!parameterlessCallback} acknowledgedCallback + * a no-argument callback invoked if the command is acknowledged + * @param {?consumerCallback} errorCallback + * a callback receiving the errors executed if an error occurred when sending command + * @param {?consumerCallback} rejectionCallback + * a callback executed if the command was rejected by Spine server + * @see CommandHandlingError + * @see CommandValidationError + * + * @deprecated Please use {@link Client#command()} + */ + sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { + this.command(commandMessage) + .onOk(acknowledgedCallback) + .onError(errorCallback) + .onRejection(rejectionCallback) + .post(); + } } diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index 32ad8d80d..fa03d31d5 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -81,6 +81,13 @@ export class CompositeClient extends Client { return this._querying.read(query); } + /** + * @override + */ + newQuery() { + return this._querying.newQuery(); + } + /** * @override */ @@ -109,6 +116,13 @@ export class CompositeClient extends Client { return this._subscribing.subscribeToEvents(topic); } + /** + * @override + */ + newTopic() { + return this._subscribing.newTopic(); + } + /** * @override */ @@ -164,6 +178,15 @@ export class QueryingClient { read(query) { throw new Error('Not implemented in abstract base.'); } + + /** + * Creates a new query factory instance which can be further used for the `Query` creation. + * + * @return {QueryFactory} + */ + newQuery() { + return this._requestFactory.query(); + } } /** @@ -224,6 +247,15 @@ export class SubscribingClient { subscribeToEvents(topic) { throw new Error('Not implemented in abstract base.'); } + + /** + * Returns a new topic factory instance which can be further used for the `Topic` creation. + * + * @return {TopicFactory} + */ + newTopic() { + return this._requestFactory.topic(); + } } const SUBSCRIPTIONS_NOT_SUPPORTED = 'Subscriptions are not supported.'; diff --git a/integration-tests/js-tests/test/firebase-client/fetch-test.js b/integration-tests/js-tests/test/firebase-client/fetch-test.js index b010cecd0..cd622438e 100644 --- a/integration-tests/js-tests/test/firebase-client/fetch-test.js +++ b/integration-tests/js-tests/test/firebase-client/fetch-test.js @@ -154,6 +154,26 @@ describe('FirebaseClient "fetch"', function () { }, fail(done)); }); + it('fetches entities using a manually created `Query`', done => { + const query = client.newQuery() + .select(Task) + .byIds(taskIds) + .build(); + client.read(query) + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, taskIds.length); + taskIds.forEach(taskId => { + const targetObject = + data.find(item => item.getId().getValue() === taskId.getValue()); + assert.ok(targetObject); + }); + + done(); + }) + .catch(() => fail(done)); + }); + it('fails a malformed query', done => { const command = TestEnvironment.createTaskCommand({withPrefix: 'spine-web-test-malformed-query'}); diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index f8fff1ce5..61dcf7b56 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -150,7 +150,7 @@ describe('FirebaseClient subscription', function () { named: INITIAL_TASK_NAME }); const taskId = createCommand.getId(); - const taskIdValue = createCommand.getId().getValue(); + const taskIdValue = taskId.getValue(); let changesCount = 0; client.subscribeTo(Task) @@ -334,6 +334,36 @@ describe('FirebaseClient subscription', function () { .post(); }); + it('subscribes to entities using a manually created `Topic`', done => { + const TASK_NAME = 'Task name'; + + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: TASK_NAME + }); + const taskId = command.getId(); + const topic = client.newTopic() + .select(Task) + .byIds([taskId]) + .build(); + client.subscribe(topic) + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + if (taskId === item.getId()) { + assert.equal( + item.getName(), TASK_NAME, + `Task is named '${item.getName()}', expected '${TASK_NAME}'.` + ); + } + unsubscribe(); + done(); + } + }); + client.command(command).post(); + }); + }); + it('fails for a malformed type', done => { const Unknown = class { static typeUrl() { From 1702c0708c4edd568c4d0b89c88b35655cd818b9 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 21:00:46 +0200 Subject: [PATCH 54/70] Optimize imports --- client-js/main/client/direct-client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client-js/main/client/direct-client.js b/client-js/main/client/direct-client.js index 434ad8f75..c68b3e0ff 100644 --- a/client-js/main/client/direct-client.js +++ b/client-js/main/client/direct-client.js @@ -27,8 +27,8 @@ import {ActorRequestFactory} from './actor-request-factory'; import { CommandingClient, CompositeClient, - QueryingClient, - NoOpSubscribingClient + NoOpSubscribingClient, + QueryingClient } from "./composite-client"; import KnownTypes from "./known-types"; import {AnyPacker} from "./any-packer"; From 9b4d77a5204497156673a1c314ed9ea31a9d7be6 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 21:03:50 +0200 Subject: [PATCH 55/70] Set indent to 2 spaces in all JS files --- client-js/main/client/client-request.js | 803 +++++++++--------- client-js/main/client/composite-client.js | 534 ++++++------ client-js/main/client/field-paths.js | 32 +- client-js/main/client/time-utils.js | 10 +- client-js/test/client/client-factory-test.js | 48 +- client-js/test/client/direct-client-test.js | 26 +- .../test/client/filtering-request-test.js | 8 +- client-js/test/client/http-endpoint-test.js | 85 +- client-js/test/client/time-utils-test.js | 58 +- .../test/direct-client/given/direct-client.js | 16 +- .../js-tests/test/direct-client/query-test.js | 72 +- .../test/firebase-client/fetch-test.js | 332 ++++---- .../firebase-client/given/firebase-client.js | 12 +- .../given/firebase-database.js | 4 +- .../firebase-client/given/test-environment.js | 160 ++-- .../test/firebase-client/query-test.js | 320 +++---- .../test/firebase-client/send-command-test.js | 302 +++---- .../test/firebase-client/subscribe-test.js | 650 +++++++------- .../test/firebase-client/topic-test.js | 362 ++++---- .../test/given/users-test-environment.js | 148 ++-- .../js-tests/test/test-helpers.js | 106 +-- 21 files changed, 2040 insertions(+), 2048 deletions(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index c6b2acab0..ceb1fe42b 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -48,25 +48,25 @@ import {Type} from "./typed-message"; */ class ClientRequest { + /** + * @protected + */ + constructor(client, requestFactory) { + /** + * @type Client + * * @protected */ - constructor(client, requestFactory) { - - /** - * @type Client - * - * @protected - */ - this._client = client; - - /** - * @type ActorRequestFactory - * - * @protected - */ - this._requestFactory = requestFactory; - } + this._client = client; + + /** + * @type ActorRequestFactory + * + * @protected + */ + this._requestFactory = requestFactory; + } } /** @@ -79,115 +79,115 @@ class ClientRequest { */ class FilteringRequest extends ClientRequest { - /** - * @param {!Class} targetType the target type of the request - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - * - * @protected - */ - constructor(targetType, client, actorRequestFactory) { - super(client, actorRequestFactory); - this.targetType = targetType; - } - - /** - * Adds filtering by IDs to the built request. - * - * @param ids {!|Number|String|[]|Number[]|String[]} - * the IDs of interest - * @return {this} self for method chaining - */ - byId(ids) { - ids = FilteringRequest._ensureArray(ids); - this._builder().byIds(ids); - return this._self(); - } - - /** - * Adds filtering by predicates to the built request. - * - * Filters specified in a list are considered to be joined using `AND` operator. - * - * @param {!Filter|CompositeFilter|Filter[]|CompositeFilter[]} predicates the filters - * @return {this} self for method chaining - */ - where(predicates) { - predicates = FilteringRequest._ensureArray(predicates); - this._builder().where(predicates); - return this._self(); - } - - /** - * Applies a field mask to the request results. - * - * The names of the fields must be formatted according to the `google.protobuf.FieldMask` - * specification. - * - * @param {!String|String[]} fieldPaths the fields to include in the mask - * @return {this} self for method chaining - */ - withMask(fieldPaths) { - fieldPaths = FilteringRequest._ensureArray(fieldPaths); - this._builder().withMask(fieldPaths); - return this._self(); - } - - /** - * Returns the builder for messages that store request data. - * - * @return {AbstractTargetBuilder} the builder instance - * - * @protected - */ - _builder() { - if (!this._builderInstance) { - const newBuilderFn = this._newBuilderFn(); - this._builderInstance = newBuilderFn(this._requestFactory); - } - return this._builderInstance; - } - - /** - * Returns the function with which the {@link _builderInstance} can be created. - * - * @abstract - * @return {Function} - * - * @protected - */ - _newBuilderFn() { - throw new Error('Not implemented in abstract base.'); - } - - /** - * @abstract - * @return {this} - * - * @protected - */ - _self() { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Wraps the passed argument into array if it's not an array already. - * - * The `null` values are converted into an empty array. - * - * @return {Array} the passed argument as an array - * - * @private - */ - static _ensureArray(values) { - if (!values) { - return []; - } - if (!(values instanceof Array)) { - return [values] - } - return values; - } + /** + * @param {!Class} targetType the target type of the request + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + * + * @protected + */ + constructor(targetType, client, actorRequestFactory) { + super(client, actorRequestFactory); + this.targetType = targetType; + } + + /** + * Adds filtering by IDs to the built request. + * + * @param ids {!|Number|String|[]|Number[]|String[]} + * the IDs of interest + * @return {this} self for method chaining + */ + byId(ids) { + ids = FilteringRequest._ensureArray(ids); + this._builder().byIds(ids); + return this._self(); + } + + /** + * Adds filtering by predicates to the built request. + * + * Filters specified in a list are considered to be joined using `AND` operator. + * + * @param {!Filter|CompositeFilter|Filter[]|CompositeFilter[]} predicates the filters + * @return {this} self for method chaining + */ + where(predicates) { + predicates = FilteringRequest._ensureArray(predicates); + this._builder().where(predicates); + return this._self(); + } + + /** + * Applies a field mask to the request results. + * + * The names of the fields must be formatted according to the `google.protobuf.FieldMask` + * specification. + * + * @param {!String|String[]} fieldPaths the fields to include in the mask + * @return {this} self for method chaining + */ + withMask(fieldPaths) { + fieldPaths = FilteringRequest._ensureArray(fieldPaths); + this._builder().withMask(fieldPaths); + return this._self(); + } + + /** + * Returns the builder for messages that store request data. + * + * @return {AbstractTargetBuilder} the builder instance + * + * @protected + */ + _builder() { + if (!this._builderInstance) { + const newBuilderFn = this._newBuilderFn(); + this._builderInstance = newBuilderFn(this._requestFactory); + } + return this._builderInstance; + } + + /** + * Returns the function with which the {@link _builderInstance} can be created. + * + * @abstract + * @return {Function} + * + * @protected + */ + _newBuilderFn() { + throw new Error('Not implemented in abstract base.'); + } + + /** + * @abstract + * @return {this} + * + * @protected + */ + _self() { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Wraps the passed argument into array if it's not an array already. + * + * The `null` values are converted into an empty array. + * + * @return {Array} the passed argument as an array + * + * @private + */ + static _ensureArray(values) { + if (!values) { + return []; + } + if (!(values instanceof Array)) { + return [values] + } + return values; + } } /** @@ -216,76 +216,76 @@ class FilteringRequest extends ClientRequest { */ export class QueryRequest extends FilteringRequest { - /** - * @param {!Class} targetType the target type of entities - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - */ - constructor(targetType, client, actorRequestFactory) { - super(targetType, client, actorRequestFactory) - } - - /** - * Sets the sorting order for the retrieved results. - * - * @param {!String} column the column to order by - * @param {!OrderBy.Direction} direction the ascending/descending direction - * @return {this} self for method chaining - */ - orderBy(column, direction) { - if (direction === OrderBy.Direction.ASCENDING) { - this._builder().orderAscendingBy(column); - } else { - this._builder().orderDescendingBy(column); - } - return this._self(); - } - - /** - * Sets the maximum number of returned entities. - * - * Can only be used in conjunction with the {@link #orderBy} condition. - * - * @param {number} count the max number of response entities - * @return {this} self for method chaining - */ - limit(count) { - this._builder().limit(count); - return this._self(); - } - - /** - * Builds a `Query` instance based on currently specified filters. - * - * @return {spine.client.Query} a `Query` instance - */ - query() { - return this._builder().build(); - } - - /** - * Runs the query and obtains the results. - * - * @return {Promise<[]>} the asynchronously resolved query results - */ - run() { - const query = this.query(); - return this._client.read(query); - } - - /** - * @inheritDoc - */ - _newBuilderFn() { - return requestFactory => requestFactory.query().select(this.targetType); - } - - /** - * @inheritDoc - */ - _self() { - return this; - } + /** + * @param {!Class} targetType the target type of entities + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(targetType, client, actorRequestFactory) { + super(targetType, client, actorRequestFactory) + } + + /** + * Sets the sorting order for the retrieved results. + * + * @param {!String} column the column to order by + * @param {!OrderBy.Direction} direction the ascending/descending direction + * @return {this} self for method chaining + */ + orderBy(column, direction) { + if (direction === OrderBy.Direction.ASCENDING) { + this._builder().orderAscendingBy(column); + } else { + this._builder().orderDescendingBy(column); + } + return this._self(); + } + + /** + * Sets the maximum number of returned entities. + * + * Can only be used in conjunction with the {@link #orderBy} condition. + * + * @param {number} count the max number of response entities + * @return {this} self for method chaining + */ + limit(count) { + this._builder().limit(count); + return this._self(); + } + + /** + * Builds a `Query` instance based on currently specified filters. + * + * @return {spine.client.Query} a `Query` instance + */ + query() { + return this._builder().build(); + } + + /** + * Runs the query and obtains the results. + * + * @return {Promise<[]>} the asynchronously resolved query results + */ + run() { + const query = this.query(); + return this._client.read(query); + } + + /** + * @inheritDoc + */ + _newBuilderFn() { + return requestFactory => requestFactory.query().select(this.targetType); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } } /** @@ -296,42 +296,42 @@ export class QueryRequest extends FilteringRequest { */ class SubscribingRequest extends FilteringRequest { - /** - * Builds a `Topic` instance based on the currently specified filters. - * - * @return {spine.client.Topic} a `Topic` instance - */ - topic() { - return this._builder().build(); - } - - /** - * Posts a subscription request and returns the result as `Promise`. - * - * @return {Promise | EventSubscriptionObject>} - * the asynchronously resolved subscription object - */ - post() { - const topic = this.topic(); - return this._subscribe(topic); - } - - /** - * @inheritDoc - */ - _newBuilderFn() { - return requestFactory => requestFactory.topic().select(this.targetType); - } - - /** - * @abstract - * @return {Promise | EventSubscriptionObject>} - * - * @protected - */ - _subscribe(topic) { - throw new Error('Not implemented in abstract base.'); - } + /** + * Builds a `Topic` instance based on the currently specified filters. + * + * @return {spine.client.Topic} a `Topic` instance + */ + topic() { + return this._builder().build(); + } + + /** + * Posts a subscription request and returns the result as `Promise`. + * + * @return {Promise | EventSubscriptionObject>} + * the asynchronously resolved subscription object + */ + post() { + const topic = this.topic(); + return this._subscribe(topic); + } + + /** + * @inheritDoc + */ + _newBuilderFn() { + return requestFactory => requestFactory.topic().select(this.targetType); + } + + /** + * @abstract + * @return {Promise | EventSubscriptionObject>} + * + * @protected + */ + _subscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } } /** @@ -364,30 +364,30 @@ class SubscribingRequest extends FilteringRequest { */ export class SubscriptionRequest extends SubscribingRequest { - /** - * @param {!Class} entityType the target entity type - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - */ - constructor(entityType, client, actorRequestFactory) { - super(entityType, client, actorRequestFactory) - } - - /** - * @inheritDoc - * - * @return {Promise>} - */ - _subscribe(topic) { - return this._client.subscribe(topic); - } - - /** - * @inheritDoc - */ - _self() { - return this; - } + /** + * @param {!Class} entityType the target entity type + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(entityType, client, actorRequestFactory) { + super(entityType, client, actorRequestFactory) + } + + /** + * @inheritDoc + * + * @return {Promise>} + */ + _subscribe(topic) { + return this._client.subscribe(topic); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } } /** @@ -418,30 +418,30 @@ export class SubscriptionRequest extends SubscribingRequest { */ export class EventSubscriptionRequest extends SubscribingRequest { - /** - * @param {!Class} eventType the target event type - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - */ - constructor(eventType, client, actorRequestFactory) { - super(eventType, client, actorRequestFactory) - } - - /** - * @inheritDoc - * - * @return {Promise} - */ - _subscribe(topic) { - return this._client.subscribeToEvents(topic); - } - - /** - * @inheritDoc - */ - _self() { - return this; - } + /** + * @param {!Class} eventType the target event type + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(eventType, client, actorRequestFactory) { + super(eventType, client, actorRequestFactory) + } + + /** + * @inheritDoc + * + * @return {Promise} + */ + _subscribe(topic) { + return this._client.subscribeToEvents(topic); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } } const NOOP_CALLBACK = () => {}; @@ -475,127 +475,126 @@ const NOOP_CALLBACK = () => {}; */ export class CommandRequest extends ClientRequest { - /** - * @param {!Message} commandMessage the command to post - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - */ - constructor(commandMessage, client, actorRequestFactory) { - super(client, actorRequestFactory); - this._commandMessage = commandMessage; - this._onAck = NOOP_CALLBACK; - this._onError = NOOP_CALLBACK; - this._onRejection = NOOP_CALLBACK; - this._observedTypes = []; - } - - /** - * Runs the callback if the command is successfully handled by the Spine server. - * - * @param {!parameterlessCallback} callback the callback to run - * @return {this} self for method chaining - */ - onOk(callback) { - this._onAck = callback; - return this; - } - - /** - * Runs the callback if the command could not be handled by the Spine server due to the - * technical error. - * - * @param {!consumerCallback} callback the callback to run - * @return {this} self for method chaining - */ - onError(callback) { - this._onError = callback; - return this; - } - - /** - * Runs the callback if the server responded with the `rejection` status on a command. - * - * Note that with the current Spine server implementation the command being rejected right away - * is very unlikely. In most cases, the command will be acknowledged with `OK` status and only - * then lead to a business rejection. You can check this scenario using the `observe` method. - * - * @param {!consumerCallback} callback - * @return {this} self for method chaining - */ - onRejection(callback) { - this._onRejection = callback; - return this; - } - - /** - * Adds the event type to the list of observed command handling results. - * - * @param {!Class} eventType a type of the observed events - * @param {!consumerCallback} consumer - * a consumer of the `subscribe` and `unsubscribe` callbacks which are responsible for - * accepting the incoming events and cancelling the subscription respectively - * @return {this} self for method chaining - */ - observe(eventType, consumer) { - this._observedTypes.push({type: eventType, consumer: consumer}); - return this; - } - - /** - * Posts the command to the server and subscribes to all observed types. - * - * @return {Promise} a promise that signals if the command posting was done successfully, - * may be ignored - */ - post() { - const command = this._requestFactory.command().create(this._commandMessage); - const onAck = - {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; - const promises = []; - this._observedTypes.forEach(({type, consumer}) => { - const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); - const promise = this._client.subscribeToEvent(type) - .where(originFilter) - .post() - .then(({eventEmitted, unsubscribe}) => { - const subscribe = eventConsumer => { - eventEmitted.subscribe({ - next: eventConsumer - }); - }; - consumer({subscribe, unsubscribe}); - }); - promises.push(promise); - }); - const subscriptionPromise = Promise.all(promises); - return subscriptionPromise.then(() => this._client.post(command, onAck)); - } - - /** - * @param {!Command} command - * @return {Origin} - * - * @private - */ - _asOrigin(command) { - const result = new Origin(); - - const messageId = new MessageId(); - const commandIdType = Type.forClass(CommandId); - const packedId = AnyPacker.pack(command.getId()).as(commandIdType); - messageId.setId(packedId); - const typeUrl = command.getMessage().getTypeUrl(); - messageId.setTypeUrl(typeUrl); - result.setMessage(messageId); - - let grandOrigin = command.getContext().getOrigin(); - if (!grandOrigin) { - grandOrigin = new Origin(); - } - result.setGrandOrigin(grandOrigin); - - const actorContext = command.getContext().getActorContext(); - result.setActorContext(actorContext); - return result; - } + /** + * @param {!Message} commandMessage the command to post + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(commandMessage, client, actorRequestFactory) { + super(client, actorRequestFactory); + this._commandMessage = commandMessage; + this._onAck = NOOP_CALLBACK; + this._onError = NOOP_CALLBACK; + this._onRejection = NOOP_CALLBACK; + this._observedTypes = []; + } + + /** + * Runs the callback if the command is successfully handled by the Spine server. + * + * @param {!parameterlessCallback} callback the callback to run + * @return {this} self for method chaining + */ + onOk(callback) { + this._onAck = callback; + return this; + } + + /** + * Runs the callback if the command could not be handled by the Spine server due to the + * technical error. + * + * @param {!consumerCallback} callback the callback to run + * @return {this} self for method chaining + */ + onError(callback) { + this._onError = callback; + return this; + } + + /** + * Runs the callback if the server responded with the `rejection` status on a command. + * + * Note that with the current Spine server implementation the command being rejected right away + * is very unlikely. In most cases, the command will be acknowledged with `OK` status and only + * then lead to a business rejection. You can check this scenario using the `observe` method. + * + * @param {!consumerCallback} callback + * @return {this} self for method chaining + */ + onRejection(callback) { + this._onRejection = callback; + return this; + } + + /** + * Adds the event type to the list of observed command handling results. + * + * @param {!Class} eventType a type of the observed events + * @param {!consumerCallback} consumer + * a consumer of the `subscribe` and `unsubscribe` callbacks which are responsible for + * accepting the incoming events and cancelling the subscription respectively + * @return {this} self for method chaining + */ + observe(eventType, consumer) { + this._observedTypes.push({type: eventType, consumer: consumer}); + return this; + } + + /** + * Posts the command to the server and subscribes to all observed types. + * + * @return {Promise} a promise that signals if the command posting was done successfully, + * may be ignored + */ + post() { + const command = this._requestFactory.command().create(this._commandMessage); + const onAck = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; + const promises = []; + this._observedTypes.forEach(({type, consumer}) => { + const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); + const promise = this._client.subscribeToEvent(type) + .where(originFilter) + .post() + .then(({eventEmitted, unsubscribe}) => { + const subscribe = eventConsumer => { + eventEmitted.subscribe({ + next: eventConsumer + }); + }; + consumer({subscribe, unsubscribe}); + }); + promises.push(promise); + }); + const subscriptionPromise = Promise.all(promises); + return subscriptionPromise.then(() => this._client.post(command, onAck)); + } + + /** + * @param {!Command} command + * @return {Origin} + * + * @private + */ + _asOrigin(command) { + const result = new Origin(); + + const messageId = new MessageId(); + const commandIdType = Type.forClass(CommandId); + const packedId = AnyPacker.pack(command.getId()).as(commandIdType); + messageId.setId(packedId); + const typeUrl = command.getMessage().getTypeUrl(); + messageId.setTypeUrl(typeUrl); + result.setMessage(messageId); + + let grandOrigin = command.getContext().getOrigin(); + if (!grandOrigin) { + grandOrigin = new Origin(); + } + result.setGrandOrigin(grandOrigin); + + const actorContext = command.getContext().getActorContext(); + result.setActorContext(actorContext); + return result; + } } diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index fa03d31d5..ed0e3be21 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -26,10 +26,10 @@ import {CommandHandlingError, CommandValidationError, SpineError} from "./errors import {Status} from '../proto/spine/core/response_pb'; import {Client} from "./client"; import { - CommandRequest, - EventSubscriptionRequest, - QueryRequest, - SubscriptionRequest + CommandRequest, + EventSubscriptionRequest, + QueryRequest, + SubscriptionRequest } from "./client-request"; import {TypedMessage} from "./typed-message"; @@ -42,100 +42,100 @@ import {TypedMessage} from "./typed-message"; */ export class CompositeClient extends Client { - constructor(querying, subscribing, commanding) { - super(); - - /** - * @type QueryingClient - * - * @private - */ - this._querying = querying; - - /** - * @type SubscribingClient - * - * @private - */ - this._subscribing = subscribing; - - /** - * @type CommandingClient - * - * @private - */ - this._commanding = commanding; - } - - /** - * @override - */ - select(entityType) { - return this._querying.select(entityType, this); - } - - /** - * @override - */ - read(query) { - return this._querying.read(query); - } + constructor(querying, subscribing, commanding) { + super(); /** - * @override - */ - newQuery() { - return this._querying.newQuery(); - } - - /** - * @override - */ - subscribeTo(entityType) { - return this._subscribing.subscribeTo(entityType, this); - } - - /** - * @override - */ - subscribeToEvent(eventType) { - return this._subscribing.subscribeToEvent(eventType, this); - } - - /** - * @override - */ - subscribe(topic) { - return this._subscribing.subscribe(topic) - } - - /** - * @override - */ - subscribeToEvents(topic) { - return this._subscribing.subscribeToEvents(topic); - } - - /** - * @override + * @type QueryingClient + * + * @private */ - newTopic() { - return this._subscribing.newTopic(); - } + this._querying = querying; /** - * @override + * @type SubscribingClient + * + * @private */ - command(commandMessage) { - return this._commanding.command(commandMessage, this); - } + this._subscribing = subscribing; /** - * @override + * @type CommandingClient + * + * @private */ - post(command, onAck) { - this._commanding.post(command, onAck); - } + this._commanding = commanding; + } + + /** + * @override + */ + select(entityType) { + return this._querying.select(entityType, this); + } + + /** + * @override + */ + read(query) { + return this._querying.read(query); + } + + /** + * @override + */ + newQuery() { + return this._querying.newQuery(); + } + + /** + * @override + */ + subscribeTo(entityType) { + return this._subscribing.subscribeTo(entityType, this); + } + + /** + * @override + */ + subscribeToEvent(eventType) { + return this._subscribing.subscribeToEvent(eventType, this); + } + + /** + * @override + */ + subscribe(topic) { + return this._subscribing.subscribe(topic) + } + + /** + * @override + */ + subscribeToEvents(topic) { + return this._subscribing.subscribeToEvents(topic); + } + + /** + * @override + */ + newTopic() { + return this._subscribing.newTopic(); + } + + /** + * @override + */ + command(commandMessage) { + return this._commanding.command(commandMessage, this); + } + + /** + * @override + */ + post(command, onAck) { + this._commanding.post(command, onAck); + } } /** @@ -145,48 +145,48 @@ export class CompositeClient extends Client { */ export class QueryingClient { - /** - * @param {!ActorRequestFactory} actorRequestFactory - * a request factory to build requests to Spine server - */ - constructor(actorRequestFactory) { - this._requestFactory = actorRequestFactory; - } - - /** - * Creates a new query request. - * - * @param {!Class} entityType the target entity type - * @param {!Client} client the client which initiated the request - * @return {QueryRequest} a new query request - */ - select(entityType, client) { - return new QueryRequest(entityType, client, this._requestFactory); - } - - /** - * Executes the given `Query` instance specifying the data to be retrieved from - * Spine server fulfilling a returned promise with an array of received objects. - * - * @param {!spine.client.Query} query a query instance to be executed - * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf - * messages of a given type or with an empty list if no entities matching given query - * were found; rejected with a `SpineError` if error occurs - * - * @template a Protobuf type of entities being the target of a query - */ - read(query) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Creates a new query factory instance which can be further used for the `Query` creation. - * - * @return {QueryFactory} - */ - newQuery() { - return this._requestFactory.query(); - } + /** + * @param {!ActorRequestFactory} actorRequestFactory + * a request factory to build requests to Spine server + */ + constructor(actorRequestFactory) { + this._requestFactory = actorRequestFactory; + } + + /** + * Creates a new query request. + * + * @param {!Class} entityType the target entity type + * @param {!Client} client the client which initiated the request + * @return {QueryRequest} a new query request + */ + select(entityType, client) { + return new QueryRequest(entityType, client, this._requestFactory); + } + + /** + * Executes the given `Query` instance specifying the data to be retrieved from + * Spine server fulfilling a returned promise with an array of received objects. + * + * @param {!spine.client.Query} query a query instance to be executed + * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf + * messages of a given type or with an empty list if no entities matching given query + * were found; rejected with a `SpineError` if error occurs + * + * @template a Protobuf type of entities being the target of a query + */ + read(query) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Creates a new query factory instance which can be further used for the `Query` creation. + * + * @return {QueryFactory} + */ + newQuery() { + return this._requestFactory.query(); + } } /** @@ -196,66 +196,66 @@ export class QueryingClient { */ export class SubscribingClient { - /** - * @param {!ActorRequestFactory} actorRequestFactory - * a request factory to build requests to Spine server - */ - constructor(actorRequestFactory) { - this._requestFactory = actorRequestFactory; - } - - /** - * Creates a new subscription request. - * - * @param {!Class} type the target entity type - * @param {!Client} client the client that initiated the request - * @return {SubscriptionRequest} a new subscription request - */ - subscribeTo(type, client) { - return new SubscriptionRequest(type, client, this._requestFactory); - } - - /** - * Subscribes to a given topic which targets an entity type. - * - * @param {!spine.client.Topic} topic a topic to subscribe to - * @return {Promise>} a subscription object - * - * @template a Protobuf type of entities being the target of a subscription - */ - subscribe(topic) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Creates a new event subscription request. - * - * @param {!Class} type the target event type - * @param {!Client} client the client that initiated the request - * @return {EventSubscriptionRequest} a new event subscription request - */ - subscribeToEvent(type, client) { - return new EventSubscriptionRequest(type, client, this._requestFactory); - } - - /** - * Subscribes to the given topic which targets an event type. - * - * @param {!spine.client.Topic} topic a topic to subscribe to - * @return {Promise} a subscription object - */ - subscribeToEvents(topic) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Returns a new topic factory instance which can be further used for the `Topic` creation. - * - * @return {TopicFactory} - */ - newTopic() { - return this._requestFactory.topic(); - } + /** + * @param {!ActorRequestFactory} actorRequestFactory + * a request factory to build requests to Spine server + */ + constructor(actorRequestFactory) { + this._requestFactory = actorRequestFactory; + } + + /** + * Creates a new subscription request. + * + * @param {!Class} type the target entity type + * @param {!Client} client the client that initiated the request + * @return {SubscriptionRequest} a new subscription request + */ + subscribeTo(type, client) { + return new SubscriptionRequest(type, client, this._requestFactory); + } + + /** + * Subscribes to a given topic which targets an entity type. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise>} a subscription object + * + * @template a Protobuf type of entities being the target of a subscription + */ + subscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Creates a new event subscription request. + * + * @param {!Class} type the target event type + * @param {!Client} client the client that initiated the request + * @return {EventSubscriptionRequest} a new event subscription request + */ + subscribeToEvent(type, client) { + return new EventSubscriptionRequest(type, client, this._requestFactory); + } + + /** + * Subscribes to the given topic which targets an event type. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise} a subscription object + */ + subscribeToEvents(topic) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Returns a new topic factory instance which can be further used for the `Topic` creation. + * + * @return {TopicFactory} + */ + newTopic() { + return this._requestFactory.topic(); + } } const SUBSCRIPTIONS_NOT_SUPPORTED = 'Subscriptions are not supported.'; @@ -265,27 +265,27 @@ const SUBSCRIPTIONS_NOT_SUPPORTED = 'Subscriptions are not supported.'; */ export class NoOpSubscribingClient extends SubscribingClient { - constructor(actorRequestFactory) { - super(actorRequestFactory) - } - - /** - * Always throws an error. - * - * @override - */ - subscribe(topic) { - throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); - } - - /** - * Always throws an error. - * - * @override - */ - subscribeToEvents(topic) { - throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); - } + constructor(actorRequestFactory) { + super(actorRequestFactory) + } + + /** + * Always throws an error. + * + * @override + */ + subscribe(topic) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } + + /** + * Always throws an error. + * + * @override + */ + subscribeToEvents(topic) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } } const _statusType = Status.typeUrl(); @@ -297,60 +297,60 @@ const _statusType = Status.typeUrl(); */ export class CommandingClient { - constructor(endpoint, requestFactory) { - this._requestFactory = requestFactory; - this._endpoint = endpoint; - } - - /** - * Creates a new command request. - * - * @param {!Message} commandMessage the command to send to the server - * @param {!Client} client the client which initiated the request - * @return {CommandRequest} a new command request - */ - command(commandMessage, client) { - return new CommandRequest(commandMessage, client, this._requestFactory); - } - - /** - * Posts a given command to the Spine server. - * - * @param {!spine.core.Command} command a Command sent to Spine server - * @param {!AckCallback} onAck a command acknowledgement callback - */ - post(command, onAck) { - const cmd = TypedMessage.of(command); - this._endpoint.command(cmd) - .then(ack => this._onAck(ack, onAck)) - .catch(error => { - onAck.onError(new CommandHandlingError(error.message, error)); - }); - } - - _onAck(ack, onAck) { - const responseStatus = ack.status; - const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); - const responseStatusCase = responseStatusProto.getStatusCase(); - - switch (responseStatusCase) { - case Status.StatusCase.OK: - onAck.onOk(); - break; - case Status.StatusCase.ERROR: - const error = responseStatusProto.getError(); - const message = error.getMessage(); - onAck.onError(error.hasValidationError() - ? new CommandValidationError(message, error) - : new CommandHandlingError(message, error)); - break; - case Status.StatusCase.REJECTION: - onAck.onRejection(responseStatusProto.getRejection()); - break; - default: - onAck.onError( - new SpineError(`Unknown response status case ${responseStatusCase}`) - ); - } + constructor(endpoint, requestFactory) { + this._requestFactory = requestFactory; + this._endpoint = endpoint; + } + + /** + * Creates a new command request. + * + * @param {!Message} commandMessage the command to send to the server + * @param {!Client} client the client which initiated the request + * @return {CommandRequest} a new command request + */ + command(commandMessage, client) { + return new CommandRequest(commandMessage, client, this._requestFactory); + } + + /** + * Posts a given command to the Spine server. + * + * @param {!spine.core.Command} command a Command sent to Spine server + * @param {!AckCallback} onAck a command acknowledgement callback + */ + post(command, onAck) { + const cmd = TypedMessage.of(command); + this._endpoint.command(cmd) + .then(ack => this._onAck(ack, onAck)) + .catch(error => { + onAck.onError(new CommandHandlingError(error.message, error)); + }); + } + + _onAck(ack, onAck) { + const responseStatus = ack.status; + const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); + const responseStatusCase = responseStatusProto.getStatusCase(); + + switch (responseStatusCase) { + case Status.StatusCase.OK: + onAck.onOk(); + break; + case Status.StatusCase.ERROR: + const error = responseStatusProto.getError(); + const message = error.getMessage(); + onAck.onError(error.hasValidationError() + ? new CommandValidationError(message, error) + : new CommandHandlingError(message, error)); + break; + case Status.StatusCase.REJECTION: + onAck.onRejection(responseStatusProto.getRejection()); + break; + default: + onAck.onError( + new SpineError(`Unknown response status case ${responseStatusCase}`) + ); } + } } diff --git a/client-js/main/client/field-paths.js b/client-js/main/client/field-paths.js index c101344a0..563d80aaf 100644 --- a/client-js/main/client/field-paths.js +++ b/client-js/main/client/field-paths.js @@ -27,22 +27,22 @@ import {FieldPath} from "../proto/spine/base/field_path_pb"; */ export class FieldPaths { - constructor() { - throw new Error('Instantiating utility FieldPaths class.'); - } + constructor() { + throw new Error('Instantiating utility FieldPaths class.'); + } - /** - * Creates a new `FieldPath` from the given string. - * - * String examples: "owner.userId", "employeeCount". - */ - static parse(stringPath) { - if (!stringPath) { - throw new Error('Constructing FieldPath from the invalid string'); - } - const fieldPath = new FieldPath(); - const pathElements = stringPath.split('.'); - fieldPath.setFieldNameList(pathElements); - return fieldPath; + /** + * Creates a new `FieldPath` from the given string. + * + * String examples: "owner.userId", "employee_count". + */ + static parse(stringPath) { + if (!stringPath) { + throw new Error('Constructing FieldPath from the invalid string'); } + const fieldPath = new FieldPath(); + const pathElements = stringPath.split('.'); + fieldPath.setFieldNameList(pathElements); + return fieldPath; + } } diff --git a/client-js/main/client/time-utils.js b/client-js/main/client/time-utils.js index e4cc6bd0e..1a1a07998 100644 --- a/client-js/main/client/time-utils.js +++ b/client-js/main/client/time-utils.js @@ -76,12 +76,12 @@ export function convertDateToTimestamp(date) { if (isNaN(date.getTime())) { throw new Error(errorMessage(`The given "${date}" is invalid.`)); } - const millis = date.getTime(); + const millis = date.getTime(); - const timestamp = new Timestamp(); - timestamp.setSeconds(Math.trunc(millis / 1000)); - timestamp.setNanos((millis % 1000) * 1000000); - return timestamp; + const timestamp = new Timestamp(); + timestamp.setSeconds(Math.trunc(millis / 1000)); + timestamp.setNanos((millis % 1000) * 1000000); + return timestamp; } diff --git a/client-js/test/client/client-factory-test.js b/client-js/test/client/client-factory-test.js index 737af52e0..4051690da 100644 --- a/client-js/test/client/client-factory-test.js +++ b/client-js/test/client/client-factory-test.js @@ -31,29 +31,29 @@ class TestClient extends Client {} describe('Client factory should', () => { - it('create composite client', done => { - const endpoint = 'example.com'; - const userId = new UserId(); - userId.value = 'me'; - const client = init({ - protoIndexFiles: [types, testTypes], - forQueries: { - endpointUrl: `${endpoint}/q/`, - actorProvider: new ActorProvider(userId) - }, - forSubscriptions: { - endpointUrl: `${endpoint}/q/`, - actorProvider: new ActorProvider(userId), - firebaseDatabase: "mock database" - }, - forCommands: { - endpointUrl: `${endpoint}/c/`, - actorProvider: new ActorProvider(userId), - implementation: new TestClient() - } - }); - assert.ok(client instanceof CompositeClient); - assert.ok(client._commanding instanceof TestClient); - done(); + it('create composite client', done => { + const endpoint = 'example.com'; + const userId = new UserId(); + userId.value = 'me'; + const client = init({ + protoIndexFiles: [types, testTypes], + forQueries: { + endpointUrl: `${endpoint}/q/`, + actorProvider: new ActorProvider(userId) + }, + forSubscriptions: { + endpointUrl: `${endpoint}/q/`, + actorProvider: new ActorProvider(userId), + firebaseDatabase: "mock database" + }, + forCommands: { + endpointUrl: `${endpoint}/c/`, + actorProvider: new ActorProvider(userId), + implementation: new TestClient() + } }); + assert.ok(client instanceof CompositeClient); + assert.ok(client._commanding instanceof TestClient); + done(); + }); }); diff --git a/client-js/test/client/direct-client-test.js b/client-js/test/client/direct-client-test.js index 970e9fd55..b3ca0c250 100644 --- a/client-js/test/client/direct-client-test.js +++ b/client-js/test/client/direct-client-test.js @@ -27,18 +27,18 @@ import {ActorProvider} from "../../main"; describe('Direct client should', () => { - it('not support subscriptions', done => { - const client = DirectClientFactory.createClient({ - endpointUrl: 'example.org', - protoIndexFiles: [types], - actorProvider: ActorProvider.ANONYMOUS - }); - try { - client.subscribeTo(TaskCreated).post(); - assert.fail(); - } catch (e) { - assert.ok(e instanceof Error); - done(); - } + it('not support subscriptions', done => { + const client = DirectClientFactory.createClient({ + endpointUrl: 'example.org', + protoIndexFiles: [types], + actorProvider: ActorProvider.ANONYMOUS }); + try { + client.subscribeTo(TaskCreated).post(); + assert.fail(); + } catch (e) { + assert.ok(e instanceof Error); + done(); + } + }); }); diff --git a/client-js/test/client/filtering-request-test.js b/client-js/test/client/filtering-request-test.js index ca70e1f4e..84290dc63 100644 --- a/client-js/test/client/filtering-request-test.js +++ b/client-js/test/client/filtering-request-test.js @@ -164,8 +164,8 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel ); const targetFilter = filters[0]; assert.equal( - filter, targetFilter, - `Unexpected filter value '${targetFilter}', expected: '${filter}'.` + filter, targetFilter, + `Unexpected filter value '${targetFilter}', expected: '${filter}'.` ); done(); }); @@ -215,8 +215,8 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel const fieldMask = getFieldMask(result); const pathList = fieldMask.getPathsList(); assert.equal( - fields, pathList, - `Unexpected list of fields in the field mask: '${pathList}', expected: '${fields}'.` + fields, pathList, + `Unexpected list of fields in the field mask: '${pathList}', expected: '${fields}'.` ); done(); }); diff --git a/client-js/test/client/http-endpoint-test.js b/client-js/test/client/http-endpoint-test.js index c57ba69b1..cbb4da658 100644 --- a/client-js/test/client/http-endpoint-test.js +++ b/client-js/test/client/http-endpoint-test.js @@ -25,12 +25,7 @@ import {HttpEndpoint} from '@lib/client/http-endpoint'; import {HttpClient} from '@lib/client/http-client'; import {TypedMessage} from '@lib/client/typed-message'; import {CreateTask} from '@testProto/spine/test/js/commands_pb'; -import { - SpineError, - ConnectionError, - ClientError, - ServerError -} from '@lib/client/errors'; +import {ClientError, ConnectionError, ServerError, SpineError} from '@lib/client/errors'; import {Duration} from '@lib/client/time-utils'; import {fail} from './test-helpers'; @@ -68,24 +63,24 @@ class Given { static response() { return this._mockResponse() - .withStatus(Given.HTTP_RESPONSE.STATUS.OK) - .withBodyContent(Given.HTTP_RESPONSE.BODY_CONTENT); + .withStatus(Given.HTTP_RESPONSE.STATUS.OK) + .withBodyContent(Given.HTTP_RESPONSE.BODY_CONTENT); } static responseWithClientError() { return this._mockResponse() - .withStatus(Given.HTTP_RESPONSE.STATUS.CLIENT_ERROR); + .withStatus(Given.HTTP_RESPONSE.STATUS.CLIENT_ERROR); } static responseWithServerError() { return this._mockResponse() - .withStatus(Given.HTTP_RESPONSE.STATUS.SERVER_ERROR); + .withStatus(Given.HTTP_RESPONSE.STATUS.SERVER_ERROR); } static responseWithMalformedBody() { return this._mockResponse() - .withStatus(Given.HTTP_RESPONSE.STATUS.OK) - .withMalformedBodyContent(); + .withStatus(Given.HTTP_RESPONSE.STATUS.OK) + .withMalformedBodyContent(); } static _mockResponse() { @@ -131,11 +126,11 @@ describe('HttpEndpoint.command', function () { httpClientBehavior.resolves(Given.response()); sendCommand() - .then(responseParsedValue => { - assert.equal(responseParsedValue, Given.HTTP_RESPONSE.BODY_CONTENT); - done(); - }) - .catch(fail(done, 'A message sending failed when it was expected to complete.')); + .then(responseParsedValue => { + assert.equal(responseParsedValue, Given.HTTP_RESPONSE.BODY_CONTENT); + done(); + }) + .catch(fail(done, 'A message sending failed when it was expected to complete.')); }); it('rejects with `SpineError` when response body parsing fails', done => { @@ -143,26 +138,26 @@ describe('HttpEndpoint.command', function () { httpClientBehavior.resolves(malformedResponse); sendCommand() - .then(fail(done, 'A message sending was completed when it was expected to fail.')) - .catch(error => { - assert.ok(error instanceof SpineError); - assert.ok(error.getCause() instanceof Error); - assert.equal(error.message, 'Failed to parse response JSON'); - done(); - }); + .then(fail(done, 'A message sending was completed when it was expected to fail.')) + .catch(error => { + assert.ok(error instanceof SpineError); + assert.ok(error.getCause() instanceof Error); + assert.equal(error.message, 'Failed to parse response JSON'); + done(); + }); }); it('rejects with `ConnectionError` when message sending fails', done => { httpClientBehavior.rejects(Given.CONNECTION_ERROR); sendCommand() - .then(fail(done, 'A message sending was completed when it was expected to fail.')) - .catch(error => { - assert.ok(error instanceof ConnectionError); - assert.ok(error.getCause() instanceof Error); - assert.equal(error.message, Given.CONNECTION_ERROR.message); - done(); - }); + .then(fail(done, 'A message sending was completed when it was expected to fail.')) + .catch(error => { + assert.ok(error instanceof ConnectionError); + assert.ok(error.getCause() instanceof Error); + assert.equal(error.message, Given.CONNECTION_ERROR.message); + done(); + }); }); it('rejects with `ClientError` when response with status 400 received', done => { @@ -170,13 +165,13 @@ describe('HttpEndpoint.command', function () { httpClientBehavior.resolves(responseWithClientError); sendCommand() - .then(fail(done, 'A message sending was completed when it was expected to fail.')) - .catch(error => { - assert.ok(error instanceof ClientError); - assert.equal(error.message, MOCK_RESPONSE_STATUS_TEXT); - assert.equal(error.getCause(), responseWithClientError); - done(); - }); + .then(fail(done, 'A message sending was completed when it was expected to fail.')) + .catch(error => { + assert.ok(error instanceof ClientError); + assert.equal(error.message, MOCK_RESPONSE_STATUS_TEXT); + assert.equal(error.getCause(), responseWithClientError); + done(); + }); }); it('rejects with `ServerError` when response with status 500 received', done => { @@ -184,12 +179,12 @@ describe('HttpEndpoint.command', function () { httpClientBehavior.resolves(responseWithServerError); sendCommand() - .then(fail(done, 'A message sending was completed when it was expected to fail.')) - .catch(error => { - assert.ok(error instanceof ServerError); - assert.equal(error.message, MOCK_RESPONSE_STATUS_TEXT); - assert.equal(error.getCause(), responseWithServerError); - done(); - }); + .then(fail(done, 'A message sending was completed when it was expected to fail.')) + .catch(error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, MOCK_RESPONSE_STATUS_TEXT); + assert.equal(error.getCause(), responseWithServerError); + done(); + }); }); }); diff --git a/client-js/test/client/time-utils-test.js b/client-js/test/client/time-utils-test.js index 48ad81a04..02344f754 100644 --- a/client-js/test/client/time-utils-test.js +++ b/client-js/test/client/time-utils-test.js @@ -23,33 +23,33 @@ import {convertDateToTimestamp} from '@lib/client/time-utils'; describe(`"convertDateToTimestamp" function`, () => { - const errorPrefix = 'Cannot convert to Timestamp.'; - - it('throws a respective error when non-Date value passed', () => { - const nonDateValue = 'today at 14 pm'; - const expectedErrorMessage = - `${errorPrefix} The given "${nonDateValue}" isn't of Date type.`; - assert.throws(() => convertDateToTimestamp(nonDateValue), null, expectedErrorMessage); - }); - - it('throws a respective error when invalid Date value passed', () => { - const invalidDate = new Date('the day when I get rich'); - const expectedErrorMessage = - `${errorPrefix} The given "${invalidDate}" is invalid.`; - - assert.throws(() => convertDateToTimestamp(invalidDate), null, expectedErrorMessage); - }); - - it('converts to Timestamp correctly', () => { - const now = new Date(Date.now()); - const timestamp = convertDateToTimestamp(now); - - const actualSeconds = timestamp.getSeconds(); - const actualNanos = timestamp.getNanos(); - const expectedSeconds = Math.trunc(now.getTime() / 1000); - const expectedNanos = (now.getTime() % 1000) * 1000000; - - assert.equal(actualSeconds, expectedSeconds); - assert.equal(actualNanos, expectedNanos); - }); + const errorPrefix = 'Cannot convert to Timestamp.'; + + it('throws a respective error when non-Date value passed', () => { + const nonDateValue = 'today at 14 pm'; + const expectedErrorMessage = + `${errorPrefix} The given "${nonDateValue}" isn't of Date type.`; + assert.throws(() => convertDateToTimestamp(nonDateValue), null, expectedErrorMessage); + }); + + it('throws a respective error when invalid Date value passed', () => { + const invalidDate = new Date('the day when I get rich'); + const expectedErrorMessage = + `${errorPrefix} The given "${invalidDate}" is invalid.`; + + assert.throws(() => convertDateToTimestamp(invalidDate), null, expectedErrorMessage); + }); + + it('converts to Timestamp correctly', () => { + const now = new Date(Date.now()); + const timestamp = convertDateToTimestamp(now); + + const actualSeconds = timestamp.getSeconds(); + const actualNanos = timestamp.getNanos(); + const expectedSeconds = Math.trunc(now.getTime() / 1000); + const expectedNanos = (now.getTime() % 1000) * 1000000; + + assert.equal(actualSeconds, expectedSeconds); + assert.equal(actualNanos, expectedNanos); + }); }); diff --git a/integration-tests/js-tests/test/direct-client/given/direct-client.js b/integration-tests/js-tests/test/direct-client/given/direct-client.js index e75a0943b..3d81c3cae 100644 --- a/integration-tests/js-tests/test/direct-client/given/direct-client.js +++ b/integration-tests/js-tests/test/direct-client/given/direct-client.js @@ -29,14 +29,14 @@ import {ActorProvider} from '@lib/client/actor-request-factory'; * See `integration-tests/README.MD` for details. */ export function initClient() { - return spineWeb.init({ - protoIndexFiles: [testProtobuf], - endpointUrl: 'http://localhost:8080', - actorProvider: new ActorProvider(), - routing: { - query: '/direct-query' - } - }); + return spineWeb.init({ + protoIndexFiles: [testProtobuf], + endpointUrl: 'http://localhost:8080', + actorProvider: new ActorProvider(), + routing: { + query: '/direct-query' + } + }); } /** diff --git a/integration-tests/js-tests/test/direct-client/query-test.js b/integration-tests/js-tests/test/direct-client/query-test.js index bf20c5019..410e912bf 100644 --- a/integration-tests/js-tests/test/direct-client/query-test.js +++ b/integration-tests/js-tests/test/direct-client/query-test.js @@ -26,45 +26,45 @@ import {UserTasks} from '@testProto/spine/web/test/given/user_tasks_pb'; describe('DirectClient executes query built', function () { - let users; + let users; - /** - * Prepares environment, where four users have one, two, three, and four tasks - * assigned respectively. - */ - before(function (done) { - // Big timeout allows to complete environment setup. - this.timeout(10 * 1000); + /** + * Prepares environment, where four users have one, two, three, and four tasks + * assigned respectively. + */ + before(function (done) { + // Big timeout allows to complete environment setup. + this.timeout(10 * 1000); - users = [ - TestEnvironment.newUser('query-tester1'), - TestEnvironment.newUser('query-tester2'), - TestEnvironment.newUser('query-tester3'), - TestEnvironment.newUser('query-tester4') - ]; + users = [ + TestEnvironment.newUser('query-tester1'), + TestEnvironment.newUser('query-tester2'), + TestEnvironment.newUser('query-tester3'), + TestEnvironment.newUser('query-tester4') + ]; - const createTasksPromises = []; - users.forEach((user, index) => { - const tasksCount = index + 1; - const promise = TestEnvironment.createTaskFor(user, tasksCount, client); - createTasksPromises.push(promise); - }); - Promise.all(createTasksPromises) - .then(() => { - // Gives time for the model state to be updated - setTimeout(done, 500); - }); + const createTasksPromises = []; + users.forEach((user, index) => { + const tasksCount = index + 1; + const promise = TestEnvironment.createTaskFor(user, tasksCount, client); + createTasksPromises.push(promise); }); + Promise.all(createTasksPromises) + .then(() => { + // Gives time for the model state to be updated + setTimeout(done, 500); + }); + }); - it(`by IDs and returns correct values`, done => { - const ids = users.map(user => user.id); - client.select(UserTasks) - .byId(ids) - .run() - .then(userTasksList => { - assert.ok(ensureUserTasks(userTasksList, users)); - done(); - }) - .catch(fail(done)); - }); + it(`by IDs and returns correct values`, done => { + const ids = users.map(user => user.id); + client.select(UserTasks) + .byId(ids) + .run() + .then(userTasksList => { + assert.ok(ensureUserTasks(userTasksList, users)); + done(); + }) + .catch(fail(done)); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/fetch-test.js b/integration-tests/js-tests/test/firebase-client/fetch-test.js index cd622438e..37161f3fd 100644 --- a/integration-tests/js-tests/test/firebase-client/fetch-test.js +++ b/integration-tests/js-tests/test/firebase-client/fetch-test.js @@ -27,173 +27,171 @@ import {Project} from '@testProto/spine/web/test/given/project_pb'; import {client} from './given/firebase-client'; describe('FirebaseClient "fetch"', function () { - let taskIds; - - /** - * Prepares the environment for `FirebaseClient#fetch()` tests where - * two tasks are created. - */ - before(function (done) { - // Big timeout allows complete environment setup. - this.timeout(20 * 1000); - - const createTaskCommands = [ - TestEnvironment.createTaskCommand({withPrefix: 'spine-web-fetch-test-task-1'}), - TestEnvironment.createTaskCommand({withPrefix: 'spine-web-fetch-test-task-2'}), - ]; - - taskIds = createTaskCommands.map(command => command.getId()); - - const createTasksPromises = []; - createTaskCommands.forEach(command => { - let reportTaskCreated; - const promise = new Promise(resolve => reportTaskCreated = resolve); - createTasksPromises.push(promise); - - client.command(command) - .onOk(() => reportTaskCreated()) - .onError(fail(done)) - .onRejection(fail(done)) - .post(); - }); - - Promise.all(createTasksPromises) - .then(() => { - // Gives time for the model state to be updated - setTimeout(done, 500); - }); - }); - - it('returns all values of a type', done => { - client.select(Task) - .run() - .then(data => { - taskIds.forEach(taskId => { - const targetObject = data.find( - item => item.getId().getValue() === taskId.getValue() - ); - assert.ok(targetObject); - }); - - done(); - }, fail(done)); - }); - - it('returns the correct value by a single ID', done => { - const id = taskIds[0]; - client.select(Task) - .byId(id) - .run() - .then(data => { - assert.ok(Array.isArray(data)); - assert.equal(data.length, 1); - const item = data[0]; - assert.ok(item.getId().getValue() === id.getValue()); - done(); - }, fail(done)); + let taskIds; + + /** + * Prepares the environment for `FirebaseClient#fetch()` tests where + * two tasks are created. + */ + before(function (done) { + // Big timeout allows complete environment setup. + this.timeout(20 * 1000); + + const createTaskCommands = [ + TestEnvironment.createTaskCommand({withPrefix: 'spine-web-fetch-test-task-1'}), + TestEnvironment.createTaskCommand({withPrefix: 'spine-web-fetch-test-task-2'}), + ]; + + taskIds = createTaskCommands.map(command => command.getId()); + + const createTasksPromises = []; + createTaskCommands.forEach(command => { + let reportTaskCreated; + const promise = new Promise(resolve => reportTaskCreated = resolve); + createTasksPromises.push(promise); + + client.command(command) + .onOk(() => reportTaskCreated()) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); }); - it('ignores `byId` parameter when an empty list is specified', done => { - client.select(Task) - .byId([]) - .run() - .then(data => { - assert.ok(Array.isArray(data)); - assert.ok(data.length >= taskIds.length); - done(); - }, fail(done)); - }); - - it('ignores `byId` parameter when a `null` value is specified', done => { - client.select(Task) - .byId(null) - .run() - .then(data => { - assert.ok(Array.isArray(data)); - assert.ok(data.length >= taskIds.length); - done(); - }, fail(done)); - }); - - it('returns empty list when fetches entity by a single ID that is missing', done => { - const taskId = TestEnvironment.taskId({}); - - client.select(Task) - .byId(taskId) - .run() - .then(data => { - assert.ok(Array.isArray(data)); - assert.ok(data.length === 0); - done(); - }, fail(done)); - }); - - it('returns correct values by multiple IDs', done => { - client.select(Task) - .byId(taskIds) - .run() - .then(data => { - assert.ok(Array.isArray(data)); - assert.equal(data.length, taskIds.length); - taskIds.forEach(taskId => { - const targetObject = - data.find(item => item.getId().getValue() === taskId.getValue()); - assert.ok(targetObject); - }); - - done(); - }, fail(done)); - }); - - it('retrieves an empty list for an entity type that does not get instantiated', done => { - client.select(Project) - .run() - .then(data => { - assert.ok(data.length === 0); - done(); - }, fail(done)); - }); - - it('fetches entities using a manually created `Query`', done => { - const query = client.newQuery() - .select(Task) - .byIds(taskIds) - .build(); - client.read(query) - .then(data => { - assert.ok(Array.isArray(data)); - assert.equal(data.length, taskIds.length); - taskIds.forEach(taskId => { - const targetObject = - data.find(item => item.getId().getValue() === taskId.getValue()); - assert.ok(targetObject); - }); - - done(); - }) - .catch(() => fail(done)); - }); - - it('fails a malformed query', done => { - const command = - TestEnvironment.createTaskCommand({withPrefix: 'spine-web-test-malformed-query'}); - - const Unknown = class { - static typeUrl() { - return 'spine.web/fails.malformed.type' - } - }; - const selectAndCheckFailed = () => client.select(Unknown) - .run() - .then(fail(done), error => { - assert.ok(error instanceof ServerError); - assert.equal(error.message, 'Server Error'); - done(); - }); - client.command(command) - .onOk(selectAndCheckFailed) - .onError(fail(done)) - .onRejection(fail(done)) - .post(); - }); + Promise.all(createTasksPromises) + .then(() => { + // Gives time for the model state to be updated + setTimeout(done, 500); + }); + }); + + it('returns all values of a type', done => { + client.select(Task) + .run() + .then(data => { + taskIds.forEach(taskId => { + const targetObject = data.find( + item => item.getId().getValue() === taskId.getValue() + ); + assert.ok(targetObject); + }); + + done(); + }, fail(done)); + }); + + it('returns the correct value by a single ID', done => { + const id = taskIds[0]; + client.select(Task) + .byId(id) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, 1); + const item = data[0]; + assert.ok(item.getId().getValue() === id.getValue()); + done(); + }, fail(done)); + }); + + it('ignores `byId` parameter when an empty list is specified', done => { + client.select(Task) + .byId([]) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.ok(data.length >= taskIds.length); + done(); + }, fail(done)); + }); + + it('ignores `byId` parameter when a `null` value is specified', done => { + client.select(Task) + .byId(null) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.ok(data.length >= taskIds.length); + done(); + }, fail(done)); + }); + + it('returns empty list when fetches entity by a single ID that is missing', done => { + const taskId = TestEnvironment.taskId({}); + + client.select(Task) + .byId(taskId) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.ok(data.length === 0); + done(); + }, fail(done)); + }); + + it('returns correct values by multiple IDs', done => { + client.select(Task) + .byId(taskIds) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, taskIds.length); + taskIds.forEach(taskId => { + const targetObject = data.find(item => item.getId().getValue() === taskId.getValue()); + assert.ok(targetObject); + }); + + done(); + }, fail(done)); + }); + + it('retrieves an empty list for an entity type that does not get instantiated', done => { + client.select(Project) + .run() + .then(data => { + assert.ok(data.length === 0); + done(); + }, fail(done)); + }); + + it('fetches entities using a manually created `Query`', done => { + const query = client.newQuery() + .select(Task) + .byIds(taskIds) + .build(); + client.read(query) + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, taskIds.length); + taskIds.forEach(taskId => { + const targetObject = data.find(item => item.getId().getValue() === taskId.getValue()); + assert.ok(targetObject); + }); + + done(); + }) + .catch(() => fail(done)); + }); + + it('fails a malformed query', done => { + const command = + TestEnvironment.createTaskCommand({withPrefix: 'spine-web-test-malformed-query'}); + + const Unknown = class { + static typeUrl() { + return 'spine.web/fails.malformed.type' + } + }; + const selectAndCheckFailed = () => client.select(Unknown) + .run() + .then(fail(done), error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server Error'); + done(); + }); + client.command(command) + .onOk(selectAndCheckFailed) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/given/firebase-client.js b/integration-tests/js-tests/test/firebase-client/given/firebase-client.js index 8bcd94499..10548707f 100644 --- a/integration-tests/js-tests/test/firebase-client/given/firebase-client.js +++ b/integration-tests/js-tests/test/firebase-client/given/firebase-client.js @@ -33,12 +33,12 @@ import {ActorProvider} from '@lib/client/actor-request-factory'; * @return {FirebaseClient} the Firebase client instance */ export function initClient(endpointUrl = 'http://localhost:8080') { - return spineWeb.init({ - protoIndexFiles: [testProtobuf], - endpointUrl: endpointUrl, - firebaseDatabase: firebaseDatabase, - actorProvider: new ActorProvider() - }); + return spineWeb.init({ + protoIndexFiles: [testProtobuf], + endpointUrl: endpointUrl, + firebaseDatabase: firebaseDatabase, + actorProvider: new ActorProvider() + }); } /** diff --git a/integration-tests/js-tests/test/firebase-client/given/firebase-database.js b/integration-tests/js-tests/test/firebase-client/given/firebase-database.js index 4f33eaac9..3d07f7693 100644 --- a/integration-tests/js-tests/test/firebase-client/given/firebase-database.js +++ b/integration-tests/js-tests/test/firebase-client/given/firebase-database.js @@ -25,8 +25,8 @@ import firebase from 'firebase'; * see `integration-tests/README.MD`. */ const config = { - databaseURL: 'https://spine-dev.firebaseio.com/', - authDomain: 'https://spine-dev.firebaseio.com/' + databaseURL: 'https://spine-dev.firebaseio.com/', + authDomain: 'https://spine-dev.firebaseio.com/' }; /** diff --git a/integration-tests/js-tests/test/firebase-client/given/test-environment.js b/integration-tests/js-tests/test/firebase-client/given/test-environment.js index 89fb345ce..188faf5b6 100644 --- a/integration-tests/js-tests/test/firebase-client/given/test-environment.js +++ b/integration-tests/js-tests/test/firebase-client/given/test-environment.js @@ -37,90 +37,90 @@ import {UserId} from '@testProto/spine/core/user_id_pb'; */ export default class TestEnvironment { - constructor() { - throw new Error('A utility `TestEnvironment` class cannot be instantiated.'); + constructor() { + throw new Error('A utility `TestEnvironment` class cannot be instantiated.'); + } + + /** + * @param {{ + * withId?: String, + * withPrefix?: String, + * named?: String, + * describedAs: String, + * assignedTo: UserId + * }} + * + * @return {CreateTask} + */ + static createTaskCommand({ + withId: id, + withPrefix: idPrefix, + named: name, + describedAs: description, + assignedTo: userId + }) { + const taskId = this.taskId({value: id, withPrefix: idPrefix}); + + name = typeof name === 'undefined' ? this.DEFAULT_TASK_NAME : name; + description = typeof description === 'undefined' + ? this.DEFAULT_TASK_DESCRIPTION + : description; + + const command = new CreateTask(); + command.setId(taskId); + command.setName(name); + command.setDescription(description); + + if (!!userId) { + command.setAssignee(userId); } - /** - * @param {{ - * withId?: String, - * withPrefix?: String, - * named?: String, - * describedAs: String, - * assignedTo: UserId - * }} - * - * @return {CreateTask} - */ - static createTaskCommand({ - withId: id, - withPrefix: idPrefix, - named: name, - describedAs: description, - assignedTo: userId - }) { - const taskId = this.taskId({value: id, withPrefix: idPrefix}); - - name = typeof name === 'undefined' ? this.DEFAULT_TASK_NAME : name; - description = typeof description === 'undefined' - ? this.DEFAULT_TASK_DESCRIPTION - : description; - - const command = new CreateTask(); - command.setId(taskId); - command.setName(name); - command.setDescription(description); - - if (!!userId) { - command.setAssignee(userId); - } - - return command; + return command; + } + + /** + * @param {{ + * withId!: String, + * to!: String + * }} + * + * @return {RenameTask} + */ + static renameTaskCommand({withId: id, to: newName}) { + const taskId = this.taskId({value: id}); + + const command = new RenameTask(); + command.setId(taskId); + command.setName(newName); + + return command; + + } + + /** + * @param value + * @param withPrefix + * + * @return {TaskId} + */ + static taskId({value, withPrefix: prefix}) { + if (typeof value === 'undefined') { + value = uuid.v4(); } - - /** - * @param {{ - * withId!: String, - * to!: String - * }} - * - * @return {RenameTask} - */ - static renameTaskCommand({withId: id, to: newName}) { - const taskId = this.taskId({value: id}); - - const command = new RenameTask(); - command.setId(taskId); - command.setName(newName); - - return command; - - } - - /** - * @param value - * @param withPrefix - * - * @return {TaskId} - */ - static taskId({value, withPrefix: prefix}) { - if (typeof value === 'undefined') { - value = uuid.v4(); - } - if (typeof prefix !== 'undefined') { - value = `${prefix}-${value}`; - } - const taskId = new TaskId(); - taskId.setValue(value); - return taskId; - } - - /** - * A function that does nothing. - */ - static noop() { - // Do nothing. + if (typeof prefix !== 'undefined') { + value = `${prefix}-${value}`; } + const taskId = new TaskId(); + taskId.setValue(value); + return taskId; + } + + /** + * A function that does nothing. + */ + static noop() { + // Do nothing. + } } TestEnvironment.DEFAULT_TASK_NAME = 'Get to Mount Doom'; diff --git a/integration-tests/js-tests/test/firebase-client/query-test.js b/integration-tests/js-tests/test/firebase-client/query-test.js index 504ea72be..8d9b5513f 100644 --- a/integration-tests/js-tests/test/firebase-client/query-test.js +++ b/integration-tests/js-tests/test/firebase-client/query-test.js @@ -39,168 +39,168 @@ import {UserTasks} from '@testProto/spine/web/test/given/user_tasks_pb'; */ describe('FirebaseClient executes query built', function () { - let users; - - function toUserIds(users) { - return users.map(user => user.id); - } - - /** - * Prepares environment, where four users have one, two, three, and four tasks - * assigned respectively. - */ - before(function(done) { - // Big timeout allows complete environment setup. - this.timeout(10 * 1000); - - users = [ - TestEnvironment.newUser('query-tester1'), - TestEnvironment.newUser('query-tester2'), - TestEnvironment.newUser('query-tester3'), - TestEnvironment.newUser('query-tester4') - ]; - - const createTasksPromises = []; - users.forEach((user, index) => { - const tasksCount = index + 1; - const promise = TestEnvironment.createTaskFor(user, tasksCount, client); - createTasksPromises.push(promise); - }); - Promise.all(createTasksPromises) - .then(() => { - // Gives time for the model state to be updated - setTimeout(done, 500); - }); - }); - - /** - * @type {QueryTest[]} - */ - const tests = [ - { - message: 'by ID', - ids: () => toUserIds(users.slice(0, 1)), - expectedUsers: () => users.slice(0, 1) - }, - { - message: 'by IDs', - ids: () => toUserIds(users.slice(0, 2)), - expectedUsers: () => users.slice(0, 2) - }, - { - message: 'by missing ID', - ids: () => [ - TestEnvironment.userId('user-without-tasks-assigned') - ], - expectedUsers: () => users.slice(0, 2) - }, - { - message: 'with `eq` filter', - filters: [ - Filters.eq('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length === 3) - }, - { - message: 'with `lt` filter', - filters: [ - Filters.lt('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length < 3) - }, - { - message: 'with `gt` filter', - filters: [ - Filters.gt('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length > 3) - }, - { - message: 'with `le` filter', - filters: [ - Filters.le('task_count', TypedMessage.int32(3)) - ], - expectedUsers: () => users.filter(user => user.tasks.length <= 3) - }, - { - message: 'with `ge` filter', - filters: [ - Filters.ge('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length >= 3) - }, - { - message: 'with several filters applied to the same column', - filters: [ - Filters.gt('task_count', 1), - Filters.lt('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length > 1 && user.tasks.length < 3) - }, - { - message: 'with several filters applied to different column', - filters: [ - Filters.gt('task_count', 1), - Filters.lt('is_overloaded', new BoolValue([true])) - ], - expectedUsers: () => users.filter(user => user.tasks.length > 1) - }, - { - message: 'with inappropriate filter', - filters: [ - Filters.ge('task_count', 100) - ], - expectedUsers: () => [] - } + let users; + + function toUserIds(users) { + return users.map(user => user.id); + } + + /** + * Prepares environment, where four users have one, two, three, and four tasks + * assigned respectively. + */ + before(function (done) { + // Big timeout allows complete environment setup. + this.timeout(10 * 1000); + + users = [ + TestEnvironment.newUser('query-tester1'), + TestEnvironment.newUser('query-tester2'), + TestEnvironment.newUser('query-tester3'), + TestEnvironment.newUser('query-tester4') ]; - tests.forEach(test => { - it(`${test.message} and returns correct values`, done => { - const ids = test.ids ? test.ids() : toUserIds(users); - const filters = test.filters; - - client.select(UserTasks) - .byId(ids) - .where(filters) - .run() - .then(userTasksList => { - assert.ok(ensureUserTasks(userTasksList, test.expectedUsers())); - done(); - }) - .catch(() => fail(done)); - }); + const createTasksPromises = []; + users.forEach((user, index) => { + const tasksCount = index + 1; + const promise = TestEnvironment.createTaskFor(user, tasksCount, client); + createTasksPromises.push(promise); }); - - it('with `Date`-based filter and returns correct values', (done) => { - const userIds = toUserIds(users); - - client.select(UserTasks) - .byId(userIds) - .run() - .then(data => { - assert.ok(Array.isArray(data)); - assert.equal(data.length, userIds.length); - - const firstUserTasks = data[0]; - - const lastUpdatedTimestamp = firstUserTasks.getLastUpdated(); - const seconds = lastUpdatedTimestamp.getSeconds(); - const nanos = lastUpdatedTimestamp.getNanos(); - const millis = seconds * 1000 + nanos / 1000000; - const whenFirstUserGotTask = new Date(millis); - - client.select(UserTasks) - .where(Filters.eq('last_updated', whenFirstUserGotTask)) - .run() - .then(userTasksList => { - assert.ok(Array.isArray(userTasksList)); - assert.equal(userTasksList.length, 1); - - const actualUserId = userTasksList[0].getId(); - assert.equal(actualUserId.getValue(), firstUserTasks.getId().getValue()); - done(); - }) - .catch(() => fail(done)); - }); + Promise.all(createTasksPromises) + .then(() => { + // Gives time for the model state to be updated + setTimeout(done, 500); + }); + }); + + /** + * @type {QueryTest[]} + */ + const tests = [ + { + message: 'by ID', + ids: () => toUserIds(users.slice(0, 1)), + expectedUsers: () => users.slice(0, 1) + }, + { + message: 'by IDs', + ids: () => toUserIds(users.slice(0, 2)), + expectedUsers: () => users.slice(0, 2) + }, + { + message: 'by missing ID', + ids: () => [ + TestEnvironment.userId('user-without-tasks-assigned') + ], + expectedUsers: () => users.slice(0, 2) + }, + { + message: 'with `eq` filter', + filters: [ + Filters.eq('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length === 3) + }, + { + message: 'with `lt` filter', + filters: [ + Filters.lt('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length < 3) + }, + { + message: 'with `gt` filter', + filters: [ + Filters.gt('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length > 3) + }, + { + message: 'with `le` filter', + filters: [ + Filters.le('task_count', TypedMessage.int32(3)) + ], + expectedUsers: () => users.filter(user => user.tasks.length <= 3) + }, + { + message: 'with `ge` filter', + filters: [ + Filters.ge('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length >= 3) + }, + { + message: 'with several filters applied to the same column', + filters: [ + Filters.gt('task_count', 1), + Filters.lt('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length > 1 && user.tasks.length < 3) + }, + { + message: 'with several filters applied to different column', + filters: [ + Filters.gt('task_count', 1), + Filters.lt('is_overloaded', new BoolValue([true])) + ], + expectedUsers: () => users.filter(user => user.tasks.length > 1) + }, + { + message: 'with inappropriate filter', + filters: [ + Filters.ge('task_count', 100) + ], + expectedUsers: () => [] + } + ]; + + tests.forEach(test => { + it(`${test.message} and returns correct values`, done => { + const ids = test.ids ? test.ids() : toUserIds(users); + const filters = test.filters; + + client.select(UserTasks) + .byId(ids) + .where(filters) + .run() + .then(userTasksList => { + assert.ok(ensureUserTasks(userTasksList, test.expectedUsers())); + done(); + }) + .catch(() => fail(done)); }); + }); + + it('with `Date`-based filter and returns correct values', (done) => { + const userIds = toUserIds(users); + + client.select(UserTasks) + .byId(userIds) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, userIds.length); + + const firstUserTasks = data[0]; + + const lastUpdatedTimestamp = firstUserTasks.getLastUpdated(); + const seconds = lastUpdatedTimestamp.getSeconds(); + const nanos = lastUpdatedTimestamp.getNanos(); + const millis = seconds * 1000 + nanos / 1000000; + const whenFirstUserGotTask = new Date(millis); + + client.select(UserTasks) + .where(Filters.eq('last_updated', whenFirstUserGotTask)) + .run() + .then(userTasksList => { + assert.ok(Array.isArray(userTasksList)); + assert.equal(userTasksList.length, 1); + + const actualUserId = userTasksList[0].getId(); + assert.equal(actualUserId.getValue(), firstUserTasks.getId().getValue()); + done(); + }) + .catch(() => fail(done)); + }); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index e300b99a4..39ad4fcb6 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -31,166 +31,166 @@ import {Type} from '@lib/client/typed-message'; describe('FirebaseClient command sending', function () { - // Big timeout allows to receive model state changes during tests. - this.timeout(15000); - - it('completes with success', done => { - - const command = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-send-command', - named: 'Implement Spine Web JS client tests', - describedAs: 'Spine Web need integration tests' - }); - - const taskId = command.getId(); - - const fetchAndCheck = () => { - client.select(Task) - .byId(taskId) - .run() - .then(data => { - assert.equal(data.length, 1); - const item = data[0]; - assert.equal(item.getId().getValue(), taskId); - assert.equal(item.getName(), command.getName()); - assert.equal(item.getDescription(), command.getDescription()); - - done(); - - }, fail(done)); - }; - - client.command(command) - .onOk(fetchAndCheck) - .onError(fail(done)) - .onRejection(fail(done)) - .post(); - }); + // Big timeout allows to receive model state changes during tests. + this.timeout(15000); - it('fails when wrong server endpoint specified', done => { - const fakeBaseUrl = 'https://malformed-server-endpoint.com'; - const malformedBackendClient = initClient(fakeBaseUrl); - const command = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-send-command', - named: 'Implement Spine Web JS client tests', - describedAs: 'Spine Web need integration tests' - }); - - const checkError = error => { - try { - assert.ok(error instanceof CommandHandlingError); - assert.ok(error.message.startsWith(`request to ${fakeBaseUrl}/command failed`)); - const connectionError = error.getCause(); - assert.ok(connectionError instanceof ConnectionError); - done(); - } catch (e) { - fail(done, e.message) - } - }; - malformedBackendClient.command(command) - .onOk(fail(done, 'A command was acknowledged when it was expected to fail.')) - .onError(checkError) - .onRejection(fail(done, 'A command was rejected when an error was expected.')) - .post(); + it('completes with success', done => { + + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: 'Implement Spine Web JS client tests', + describedAs: 'Spine Web need integration tests' }); - it('fails with `CommandValidationError` for invalid command message', done => { - const command = TestEnvironment.createTaskCommand({withId: null}); - - const checkError = error => { - try { - assert.ok(error instanceof CommandValidationError); - assert.ok(error.validationError()); - // TODO:2019-06-05:yegor.udovchenko: Find the reason of failing assertion - // assert.ok(error.assuresCommandNeglected()); - - const cause = error.getCause(); - assert.ok(cause); - assert.equal(cause.getCode(), 2); - assert.equal(cause.getType(), 'spine.core.CommandValidationError'); - done(); - } catch (e) { - fail(done, e.message) - } - }; + const taskId = command.getId(); + + const fetchAndCheck = () => { + client.select(Task) + .byId(taskId) + .run() + .then(data => { + assert.equal(data.length, 1); + const item = data[0]; + assert.equal(item.getId().getValue(), taskId); + assert.equal(item.getName(), command.getName()); + assert.equal(item.getDescription(), command.getDescription()); + + done(); + + }, fail(done)); + }; + + client.command(command) + .onOk(fetchAndCheck) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); + }); + + it('fails when wrong server endpoint specified', done => { + const fakeBaseUrl = 'https://malformed-server-endpoint.com'; + const malformedBackendClient = initClient(fakeBaseUrl); + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: 'Implement Spine Web JS client tests', + describedAs: 'Spine Web need integration tests' + }); - client.command(command) - .onOk(fail(done, 'A command was acknowledged when it was expected to fail.')) - .onError(checkError) - .onRejection(fail(done, 'A command was rejected when an error was expected.')) - .post(); + const checkError = error => { + try { + assert.ok(error instanceof CommandHandlingError); + assert.ok(error.message.startsWith(`request to ${fakeBaseUrl}/command failed`)); + const connectionError = error.getCause(); + assert.ok(connectionError instanceof ConnectionError); + done(); + } catch (e) { + fail(done, e.message) + } + }; + malformedBackendClient.command(command) + .onOk(fail(done, 'A command was acknowledged when it was expected to fail.')) + .onError(checkError) + .onRejection(fail(done, 'A command was rejected when an error was expected.')) + .post(); + }); + + it('fails with `CommandValidationError` for invalid command message', done => { + const command = TestEnvironment.createTaskCommand({withId: null}); + + const checkError = error => { + try { + assert.ok(error instanceof CommandValidationError); + assert.ok(error.validationError()); + // TODO:2019-06-05:yegor.udovchenko: Find the reason of failing assertion + // assert.ok(error.assuresCommandNeglected()); + + const cause = error.getCause(); + assert.ok(cause); + assert.equal(cause.getCode(), 2); + assert.equal(cause.getType(), 'spine.core.CommandValidationError'); + done(); + } catch (e) { + fail(done, e.message) + } + }; + + client.command(command) + .onOk(fail(done, 'A command was acknowledged when it was expected to fail.')) + .onError(checkError) + .onRejection(fail(done, 'A command was rejected when an error was expected.')) + .post(); + }); + + it('allows to observe the produced events of a given type', done => { + const taskName = 'Implement Spine Web JS client tests'; + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: taskName, + describedAs: 'Spine Web need integration tests' }); - it('allows to observe the produced events of a given type', done => { - const taskName = 'Implement Spine Web JS client tests'; - const command = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-send-command', - named: taskName, - describedAs: 'Spine Web need integration tests' - }); - - const taskId = command.getId(); - - client.command(command) - .onError(fail(done)) - .onRejection(fail(done)) - .observe(TaskCreated, ({subscribe, unsubscribe}) => { - subscribe(event => { - const packedMessage = event.getMessage(); - const taskCreatedType = Type.forClass(TaskCreated); - const message = AnyPacker.unpack(packedMessage).as(taskCreatedType); - const theTaskId = message.getId().getValue(); - assert.equal( - taskId, theTaskId, - `Expected the task ID to be '${taskId}', got '${theTaskId}' instead.` - ); - const theTaskName = message.getName(); - assert.equal( - taskName, theTaskName, - `Expected the task name to be '${taskName}', got '${theTaskName}' instead.` - ); - const origin = event.getContext().getPastMessage().getMessage(); - const originType = origin.getTypeUrl(); - const createTaskType = Type.forClass(CreateTask); - const expectedOriginType = createTaskType.url().value(); - assert.equal( - expectedOriginType, originType, - `Expected origin to be of type '${expectedOriginType}', got + const taskId = command.getId(); + + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .observe(TaskCreated, ({subscribe, unsubscribe}) => { + subscribe(event => { + const packedMessage = event.getMessage(); + const taskCreatedType = Type.forClass(TaskCreated); + const message = AnyPacker.unpack(packedMessage).as(taskCreatedType); + const theTaskId = message.getId().getValue(); + assert.equal( + taskId, theTaskId, + `Expected the task ID to be '${taskId}', got '${theTaskId}' instead.` + ); + const theTaskName = message.getName(); + assert.equal( + taskName, theTaskName, + `Expected the task name to be '${taskName}', got '${theTaskName}' instead.` + ); + const origin = event.getContext().getPastMessage().getMessage(); + const originType = origin.getTypeUrl(); + const createTaskType = Type.forClass(CreateTask); + const expectedOriginType = createTaskType.url().value(); + assert.equal( + expectedOriginType, originType, + `Expected origin to be of type '${expectedOriginType}', got '${originType}' instead.` - ); - unsubscribe(); - done(); - }); - }) - .post() + ); + unsubscribe(); + done(); + }); + }) + .post() + }); + + it('fails when trying to observe a malformed event type', done => { + const Unknown = class { + static typeUrl() { + return 'spine.web/fails.malformed.type' + } + }; + + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: 'Implement Spine Web JS client tests', + describedAs: 'Spine Web need integration tests' }); - it('fails when trying to observe a malformed event type', done => { - const Unknown = class { - static typeUrl() { - return 'spine.web/fails.malformed.type' - } - }; - - const command = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-send-command', - named: 'Implement Spine Web JS client tests', - describedAs: 'Spine Web need integration tests' - }); - - client.command(command) - .onError(fail(done)) - .onRejection(fail(done)) - .observe(Unknown) - .post() - .then(() => { - done(new Error('An attempt to observe a malformed event type did not lead to an ' + - 'error.')); - }) - .catch(() => { - assert.ok(true); - done(); + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .observe(Unknown) + .post() + .then(() => { + done(new Error('An attempt to observe a malformed event type did not lead to an ' + + 'error.')); + }) + .catch(() => { + assert.ok(true); + done(); } ); - }); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index 61dcf7b56..69bb9c0a5 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -30,355 +30,355 @@ import {Type} from '@lib/client/typed-message'; describe('FirebaseClient subscription', function () { - // Big timeout allows to receive model state changes during tests. - this.timeout(120 * 1000); + // Big timeout allows to receive model state changes during tests. + this.timeout(120 * 1000); - it('retrieves new entities', done => { - const names = ['Task #1', 'Task #2', 'Task #3']; - const newTasksCount = names.length; - let receivedCount = 0; + it('retrieves new entities', done => { + const names = ['Task #1', 'Task #2', 'Task #3']; + const newTasksCount = names.length; + let receivedCount = 0; - const commands = names.map(name => TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: name - })); - const taskIds = commands.map(command => command.getId().getValue()); + const commands = names.map(name => TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: name + })); + const taskIds = commands.map(command => command.getId().getValue()); - client.subscribeTo(Task) - .post() - .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - itemAdded.subscribe({ - next: task => { - const id = task.getId().getValue(); - console.log(`Retrieved task '${id}'`); - if (taskIds.includes(id)) { - receivedCount++; - if (receivedCount === newTasksCount) { - unsubscribe(); - done(); - } - } - } - }); - itemRemoved.subscribe({ - next: fail(done, - 'Unexpected entity remove during entity create subscription test.') - }); - itemChanged.subscribe({ - next: fail(done, - 'Unexpected entity change during entity create subscription test.') - }); - commands.forEach(command => { - client.command(command) - .onError(fail(done)) - .onRejection(fail(done)) - .post(); - }); - }) - .catch(fail(done)); - }); + client.subscribeTo(Task) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: task => { + const id = task.getId().getValue(); + console.log(`Retrieved task '${id}'`); + if (taskIds.includes(id)) { + receivedCount++; + if (receivedCount === newTasksCount) { + unsubscribe(); + done(); + } + } + } + }); + itemRemoved.subscribe({ + next: fail(done, + 'Unexpected entity remove during entity create subscription test.') + }); + itemChanged.subscribe({ + next: fail(done, + 'Unexpected entity change during entity create subscription test.') + }); + commands.forEach(command => { + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); + }); + }) + .catch(fail(done)); + }); - it('retrieves updates when subscribed by type', done => { - const INITIAL_TASK_NAME = "Task to test entity updates"; - const UPDATED_TASK_NAME = "RENAMED Task to test entity updates"; - let taskId; - client.subscribeTo(Task) - .post() - .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - itemAdded.subscribe({ - next: item => { - const id = item.getId().getValue(); - console.log(`Retrieved new task '${id}'.`); - if (taskId === id) { - assert.equal( - INITIAL_TASK_NAME, item.getName(), - `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}"` - ); - const renameCommand = TestEnvironment.renameTaskCommand({ - withId: taskId, - to: UPDATED_TASK_NAME - }); - client.command(renameCommand) - .onOk(() => console.log(`Task '${taskId}' renamed.`)) - .onError(fail(done, 'Unexpected error while renaming a task.')) - .onRejection(fail(done, - 'Unexpected rejection while renaming a task.')) - .post(); - } - } + it('retrieves updates when subscribed by type', done => { + const INITIAL_TASK_NAME = "Task to test entity updates"; + const UPDATED_TASK_NAME = "RENAMED Task to test entity updates"; + let taskId; + client.subscribeTo(Task) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log(`Retrieved new task '${id}'.`); + if (taskId === id) { + assert.equal( + INITIAL_TASK_NAME, item.getName(), + `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}"` + ); + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskId, + to: UPDATED_TASK_NAME }); - itemRemoved.subscribe({ - next: fail(done, 'Task was removed in a test of entity changes subscription.') - }); - itemChanged.subscribe({ - next: item => { - const id = item.getId().getValue(); - if (taskId === id) { - assert.equal( - item.getName(), UPDATED_TASK_NAME, - `Task is named "${item.getName()}", expected "${UPDATED_TASK_NAME}"` - ); - console.log(`Got task changes for '${id}'.`); - unsubscribe(); - done(); - } - } - }); - // Create task. - const createCommand = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: INITIAL_TASK_NAME - }); - taskId = createCommand.getId().getValue(); - - client.command(createCommand) - .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) - .onError(fail(done, 'Unexpected error while creating a task.')) - .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + client.command(renameCommand) + .onOk(() => console.log(`Task '${taskId}' renamed.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, + 'Unexpected rejection while renaming a task.')) .post(); - }) - .catch(fail(done)); - }); - - it('retrieves updates by ID', done => { - const expectedChangesCount = 2; - const INITIAL_TASK_NAME = 'Initial task name'; - const UPDATED_NAMES = ['Renamed once', 'Renamed twice']; - - const createCommand = TestEnvironment.createTaskCommand({ + } + } + }); + itemRemoved.subscribe({ + next: fail(done, 'Task was removed in a test of entity changes subscription.') + }); + itemChanged.subscribe({ + next: item => { + const id = item.getId().getValue(); + if (taskId === id) { + assert.equal( + item.getName(), UPDATED_TASK_NAME, + `Task is named "${item.getName()}", expected "${UPDATED_TASK_NAME}"` + ); + console.log(`Got task changes for '${id}'.`); + unsubscribe(); + done(); + } + } + }); + // Create task. + const createCommand = TestEnvironment.createTaskCommand({ withPrefix: 'spine-web-test-subscribe', named: INITIAL_TASK_NAME - }); - const taskId = createCommand.getId(); - const taskIdValue = taskId.getValue(); + }); + taskId = createCommand.getId().getValue(); - let changesCount = 0; - client.subscribeTo(Task) - .byId(taskId) - .post() - .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - itemAdded.subscribe({ - next: item => { - const id = item.getId().getValue(); - console.log(`Retrieved new task '${id}'.`); - if (taskIdValue === id) { - assert.equal( - item.getName(), INITIAL_TASK_NAME, - `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}"` - ); - } - const renameCommand = TestEnvironment.renameTaskCommand({ - withId: taskIdValue, - to: UPDATED_NAMES[0] - }); - client.command(renameCommand) - .onOk(() => console.log( - `Task '${taskIdValue}' renamed for the first time.`)) - .onError(fail(done, 'Unexpected error while renaming a task.')) - .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) - .post(); - } - }); - itemRemoved.subscribe({ - next: fail(done, 'Task was removed in a test of entity changes subscription.') - }); - itemChanged.subscribe({ - next: item => { - const id = item.getId().getValue(); - if (taskIdValue === id) { - console.log(`Got task changes for ${id}.`); - assert.equal(item.getName(), UPDATED_NAMES[changesCount]); - changesCount++; - if (changesCount === expectedChangesCount) { - unsubscribe(); - done(); - } else { - const renameCommand = TestEnvironment.renameTaskCommand({ - withId: taskIdValue, - to: UPDATED_NAMES[1] - }); - client.command(renameCommand) - .onOk(() => console.log( - `Task '${taskIdValue}' renamed for the second time.`)) - .onError(fail(done, - 'Unexpected error while renaming a task.')) - .onRejection(fail(done, - 'Unexpected rejection while renaming a task.')) - .post(); - } - } - } - }); - client.command(createCommand) - .onOk(() => console.log(`Task '${taskIdValue}' created.`)) - .onError(fail(done, 'Unexpected error while creating a task.')) - .onRejection(fail(done, 'Unexpected rejection while creating a task.')) - .post(); - }) - .catch(fail(done)); + client.command(createCommand) + .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + }) + .catch(fail(done)); + }); + + it('retrieves updates by ID', done => { + const expectedChangesCount = 2; + const INITIAL_TASK_NAME = 'Initial task name'; + const UPDATED_NAMES = ['Renamed once', 'Renamed twice']; + + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: INITIAL_TASK_NAME }); + const taskId = createCommand.getId(); + const taskIdValue = taskId.getValue(); - it('is notified when the entity no longer matches the subscription criteria', done => { - const initialTaskName = 'Initial task name'; - const nameAfterRenamed = 'Renamed task'; + let changesCount = 0; + client.subscribeTo(Task) + .byId(taskId) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log(`Retrieved new task '${id}'.`); + if (taskIdValue === id) { + assert.equal( + item.getName(), INITIAL_TASK_NAME, + `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}"` + ); + } + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskIdValue, + to: UPDATED_NAMES[0] + }); + client.command(renameCommand) + .onOk(() => console.log( + `Task '${taskIdValue}' renamed for the first time.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) + .post(); + } + }); + itemRemoved.subscribe({ + next: fail(done, 'Task was removed in a test of entity changes subscription.') + }); + itemChanged.subscribe({ + next: item => { + const id = item.getId().getValue(); + if (taskIdValue === id) { + console.log(`Got task changes for ${id}.`); + assert.equal(item.getName(), UPDATED_NAMES[changesCount]); + changesCount++; + if (changesCount === expectedChangesCount) { + unsubscribe(); + done(); + } else { + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskIdValue, + to: UPDATED_NAMES[1] + }); + client.command(renameCommand) + .onOk(() => console.log( + `Task '${taskIdValue}' renamed for the second time.`)) + .onError(fail(done, + 'Unexpected error while renaming a task.')) + .onRejection(fail(done, + 'Unexpected rejection while renaming a task.')) + .post(); + } + } + } + }); + client.command(createCommand) + .onOk(() => console.log(`Task '${taskIdValue}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + }) + .catch(fail(done)); + }); - const createCommand = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: initialTaskName - }); - const taskIdValue = createCommand.getId().getValue(); + it('is notified when the entity no longer matches the subscription criteria', done => { + const initialTaskName = 'Initial task name'; + const nameAfterRenamed = 'Renamed task'; + + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: initialTaskName + }); + const taskIdValue = createCommand.getId().getValue(); - client.subscribeTo(Task) - .where(Filters.eq("name", initialTaskName)) - .post() - .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - itemAdded.subscribe({ - next: item => { - const id = item.getId().getValue(); - console.log(`Retrieved new task '${id}'.`); - if (taskIdValue === id) { - assert.equal( - initialTaskName, item.getName(), - `Task is named "${item.getName()}", + client.subscribeTo(Task) + .where(Filters.eq("name", initialTaskName)) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log(`Retrieved new task '${id}'.`); + if (taskIdValue === id) { + assert.equal( + initialTaskName, item.getName(), + `Task is named "${item.getName()}", expected "${initialTaskName}".` - ); - } - const renameCommand = TestEnvironment.renameTaskCommand({ - withId: taskIdValue, - to: nameAfterRenamed - }); - client.command(renameCommand) - .onOk(() => console.log( - `Task '${taskIdValue}' is renamed to '${nameAfterRenamed}'.`)) - .onError(fail(done, 'Unexpected error while renaming a task.')) - .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) - .post(); - } - }); - itemRemoved.subscribe({ - next: item => { - const id = item.getId().getValue(); - console.log('Task removed'); - assert.equal( - taskIdValue, id, - `A wrong Task item is removed, expected the task with ID + ); + } + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskIdValue, + to: nameAfterRenamed + }); + client.command(renameCommand) + .onOk(() => console.log( + `Task '${taskIdValue}' is renamed to '${nameAfterRenamed}'.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) + .post(); + } + }); + itemRemoved.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log('Task removed'); + assert.equal( + taskIdValue, id, + `A wrong Task item is removed, expected the task with ID "${taskIdValue}", received the task with ID "${id}".` - ); - unsubscribe(); - done(); - } - }); - itemChanged.subscribe({ - next: fail(done, 'The `itemChanged` call is unexpected within this test.') - }); - client.command(createCommand) - .onOk(() => console.log(`Task '${taskIdValue}' created.`)) - .onError(fail(done, 'Unexpected error while creating a task.')) - .onRejection(fail(done, 'Unexpected rejection while creating a task.')) - .post(); - }) - .catch(fail(done)); - }); + ); + unsubscribe(); + done(); + } + }); + itemChanged.subscribe({ + next: fail(done, 'The `itemChanged` call is unexpected within this test.') + }); + client.command(createCommand) + .onOk(() => console.log(`Task '${taskIdValue}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + }) + .catch(fail(done)); + }); - it('retrieves event updates', done => { - const initialTaskName = "The initial task name"; - const updatedTaskName = "Renamed task"; + it('retrieves event updates', done => { + const initialTaskName = "The initial task name"; + const updatedTaskName = "Renamed task"; - let taskId; - const createCommand = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: initialTaskName - }); - taskId = createCommand.getId().getValue(); + let taskId; + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: initialTaskName + }); + taskId = createCommand.getId().getValue(); - client.subscribeToEvent(TaskRenamed) - .where([Filters.eq("id.value", taskId), Filters.eq("name", updatedTaskName)]) - .post() - .then(({eventEmitted, unsubscribe}) => { - eventEmitted.subscribe({ - next: event => { - const packedMessage = event.getMessage(); - const taskRenamedType = Type.forClass(TaskRenamed); - const message = AnyPacker.unpack(packedMessage).as(taskRenamedType); - const theTaskId = message.getId().getValue(); - assert.equal( - taskId, theTaskId, - `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` - ); - const newTaskName = message.getName(); - assert.equal( - updatedTaskName, newTaskName, - `Expected the new task name to be ${updatedTaskName}, got + client.subscribeToEvent(TaskRenamed) + .where([Filters.eq("id.value", taskId), Filters.eq("name", updatedTaskName)]) + .post() + .then(({eventEmitted, unsubscribe}) => { + eventEmitted.subscribe({ + next: event => { + const packedMessage = event.getMessage(); + const taskRenamedType = Type.forClass(TaskRenamed); + const message = AnyPacker.unpack(packedMessage).as(taskRenamedType); + const theTaskId = message.getId().getValue(); + assert.equal( + taskId, theTaskId, + `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` + ); + const newTaskName = message.getName(); + assert.equal( + updatedTaskName, newTaskName, + `Expected the new task name to be ${updatedTaskName}, got ${newTaskName} instead.` - ); - unsubscribe(); - done(); - } - }); - }); - client.command(createCommand) - .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) - .onError(fail(done, 'Unexpected error while creating a task.')) - .onRejection(fail(done, 'Unexpected rejection while creating a task.')) - .post(); - - const renameCommand = TestEnvironment.renameTaskCommand({ - withId: taskId, - to: updatedTaskName + ); + unsubscribe(); + done(); + } + }); }); - client.command(renameCommand) - .onOk(() => console.log(`Task '${taskId}' renamed.`)) - .onError(fail(done, 'Unexpected error while renaming a task.')) - .onRejection(fail(done, - 'Unexpected rejection while renaming a task.')) - .post(); + client.command(createCommand) + .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskId, + to: updatedTaskName }); + client.command(renameCommand) + .onOk(() => console.log(`Task '${taskId}' renamed.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, + 'Unexpected rejection while renaming a task.')) + .post(); + }); - it('subscribes to entities using a manually created `Topic`', done => { - const TASK_NAME = 'Task name'; + it('subscribes to entities using a manually created `Topic`', done => { + const TASK_NAME = 'Task name'; - const command = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: TASK_NAME - }); - const taskId = command.getId(); - const topic = client.newTopic() - .select(Task) - .byIds([taskId]) - .build(); - client.subscribe(topic) - .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - itemAdded.subscribe({ - next: item => { - if (taskId === item.getId()) { - assert.equal( - item.getName(), TASK_NAME, - `Task is named '${item.getName()}', expected '${TASK_NAME}'.` - ); - } - unsubscribe(); - done(); - } - }); - client.command(command).post(); - }); + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: TASK_NAME }); - - it('fails for a malformed type', done => { - const Unknown = class { - static typeUrl() { - return 'spine.web/fails.malformed.type' + const taskId = command.getId(); + const topic = client.newTopic() + .select(Task) + .byIds([taskId]) + .build(); + client.subscribe(topic) + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + if (taskId === item.getId()) { + assert.equal( + item.getName(), TASK_NAME, + `Task is named '${item.getName()}', expected '${TASK_NAME}'.` + ); + } + unsubscribe(); + done(); } - }; + }); + client.command(command).post(); + }); + }); - client.subscribeTo(Unknown) - .post() - .then(() => { - done(new Error('A malformed subscription should not yield results.')); - }) - .catch(error => { - assert.ok(true); - done(); - }); - }); + it('fails for a malformed type', done => { + const Unknown = class { + static typeUrl() { + return 'spine.web/fails.malformed.type' + } + }; + + client.subscribeTo(Unknown) + .post() + .then(() => { + done(new Error('A malformed subscription should not yield results.')); + }) + .catch(error => { + assert.ok(true); + done(); + }); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/topic-test.js b/integration-tests/js-tests/test/firebase-client/topic-test.js index 7abc9be12..57a087c67 100644 --- a/integration-tests/js-tests/test/firebase-client/topic-test.js +++ b/integration-tests/js-tests/test/firebase-client/topic-test.js @@ -50,213 +50,213 @@ import {UserTasks} from '@testProto/spine/web/test/given/user_tasks_pb'; */ class UserTasksFlow { - constructor(userTasksList$) { - this.userTasksList$ = userTasksList$; - this._states = []; - this._transitions = []; - } + constructor(userTasksList$) { + this.userTasksList$ = userTasksList$; + this._states = []; + this._transitions = []; + } + + static for(userTasksList$) { + return new UserTasksFlow(userTasksList$); + } - static for(userTasksList$) { - return new UserTasksFlow(userTasksList$); + /** + * Adds a transition that does nothing if the next state is expected to be received + * without any actions. This allows to handle such cases: + * ``` + * ... + * .waitFor([]) // The NoOp transition will be performed when this state matches + * .waitFor([ + * { id: user1.id, tasksCount: 1 } + * ]) + * ... + * ``` + * @private + */ + static _equalize(states, transitions) { + if (states.length === transitions.length) { + return; + } + if (states.length === transitions.length + 1) { + transitions.push(UserTasksFlow.doNothing); + return; } - /** - * Adds a transition that does nothing if the next state is expected to be received - * without any actions. This allows to handle such cases: - * ``` - * ... - * .waitFor([]) // The NoOp transition will be performed when this state matches - * .waitFor([ - * { id: user1.id, tasksCount: 1 } - * ]) - * ... - * ``` - * @private - */ - static _equalize(states, transitions) { - if (states.length === transitions.length) { - return; - } - if (states.length === transitions.length + 1) { - transitions.push(UserTasksFlow.doNothing); - return; - } + throw new Error('The consistency of the flow is broken. Ensure each action is' + + 'followed by the next expected state.'); + } - throw new Error('The consistency of the flow is broken. Ensure each action is' + - 'followed by the next expected state.'); - } + static doNothing() { + // NoOp + } - static doNothing() { - // NoOp - } + waitFor(state) { + UserTasksFlow._equalize(this._states, this._transitions); + this._states.push(state); + return this; + } - waitFor(state) { - UserTasksFlow._equalize(this._states, this._transitions); - this._states.push(state); - return this; - } + then(perform) { + this._transitions.push(perform); + return this; + } - then(perform) { - this._transitions.push(perform); - return this; - } + /** + * Starts to observe the user tasks list and perform actions when the + * list state matches the next expected state. + * + * @return {Promise} a promise to be resolved when all expected states were + * received and all followed + */ + start() { + return new Promise((resolve, reject) => { + if (!this._states.length) { + reject('The list of states must not be empty.'); + } + UserTasksFlow._equalize(this._states, this._transitions); + const subscriptionEnd$ = new Subject(); + this.userTasksList$ + .pipe(takeUntil(subscriptionEnd$)) + .subscribe(userTasksList => { + const nextState = this._states[0]; - /** - * Starts to observe the user tasks list and perform actions when the - * list state matches the next expected state. - * - * @return {Promise} a promise to be resolved when all expected states were - * received and all followed - */ - start() { - return new Promise((resolve, reject) => { - if (!this._states.length) { - reject('The list of states must not be empty.'); + const nextStateMatches = ensureUserTasksCount(userTasksList, nextState); + try { + this._transitIf(nextStateMatches); + } catch (e) { + reject(e); } - UserTasksFlow._equalize(this._states, this._transitions); - const subscriptionEnd$ = new Subject(); - this.userTasksList$ - .pipe(takeUntil(subscriptionEnd$)) - .subscribe(userTasksList => { - const nextState = this._states[0]; - - const nextStateMatches = ensureUserTasksCount(userTasksList, nextState); - try { - this._transitIf(nextStateMatches); - } catch (e) { - reject(e); - } - if (this._hasNoMoreStates()) { - subscriptionEnd$.next(); - subscriptionEnd$.complete(); - resolve(); - } - }); - }); - } + if (this._hasNoMoreStates()) { + subscriptionEnd$.next(); + subscriptionEnd$.complete(); + resolve(); + } + }); + }); + } - _transitIf(stateMatches) { - if (stateMatches) { - this._states.shift(); - const performTransition = this._transitions.shift(); - performTransition(); - } + _transitIf(stateMatches) { + if (stateMatches) { + this._states.shift(); + const performTransition = this._transitions.shift(); + performTransition(); } + } - _hasNoMoreStates() { - return !this._states.length; - } + _hasNoMoreStates() { + return !this._states.length; + } } describe('FirebaseClient subscribes to topic', function () { - // Big timeout allows to receive model state changes during tests. - this.timeout(120 * 1000); - const compareUserTasks = (userTasks1, userTasks2) => - userTasks1.getId().getValue() === userTasks2.getId().getValue(); + // Big timeout allows to receive model state changes during tests. + this.timeout(120 * 1000); + const compareUserTasks = (userTasks1, userTasks2) => + userTasks1.getId().getValue() === userTasks2.getId().getValue(); - let user1; - let user2; - let teardownSubscription = () => { - }; + let user1; + let user2; + let teardownSubscription = () => { + }; - /** - * Prepares environment where two users have two tasks assigned each. - */ - beforeEach((done) => { - user1 = TestEnvironment.newUser('topic-tester-1'); - user2 = TestEnvironment.newUser('topic-tester-2'); - teardownSubscription = () => {}; + /** + * Prepares environment where two users have two tasks assigned each. + */ + beforeEach((done) => { + user1 = TestEnvironment.newUser('topic-tester-1'); + user2 = TestEnvironment.newUser('topic-tester-2'); + teardownSubscription = () => {}; - const createTasksPromises = []; - [user1, user2].forEach(user => { - const promise = TestEnvironment.createTaskFor(user, 2, client); - createTasksPromises.push(promise); - }); - Promise.all(createTasksPromises) - .then(() => done()); + const createTasksPromises = []; + [user1, user2].forEach(user => { + const promise = TestEnvironment.createTaskFor(user, 2, client); + createTasksPromises.push(promise); }); + Promise.all(createTasksPromises) + .then(() => done()); + }); - afterEach(() => { - teardownSubscription(); - }); + afterEach(() => { + teardownSubscription(); + }); - it('built by IDs and retrieves correct data', (done) => { - client.subscribeTo(UserTasks) - .byId([user1.id, user2.id]) - .post() - .then(subscription => { - teardownSubscription = subscription.unsubscribe; - const userTasksList$ = toListObservable(subscription, compareUserTasks); - const userTasksFlow = UserTasksFlow.for(userTasksList$); + it('built by IDs and retrieves correct data', (done) => { + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .post() + .then(subscription => { + teardownSubscription = subscription.unsubscribe; + const userTasksList$ = toListObservable(subscription, compareUserTasks); + const userTasksFlow = UserTasksFlow.for(userTasksList$); - userTasksFlow - .waitFor([ - { id: user1.id, tasksCount: 2 }, - { id: user2.id, tasksCount: 2 } - ]) - .start() - .then(done) - .catch(e => fail(done, e)()) - }) - }); + userTasksFlow + .waitFor([ + {id: user1.id, tasksCount: 2}, + {id: user2.id, tasksCount: 2} + ]) + .start() + .then(done) + .catch(e => fail(done, e)()) + }) + }); - it('built by IDs and filters and retrieves correct data', (done) => { - client.subscribeTo(UserTasks) - .byId([user1.id, user2.id]) - .where(Filters.eq('task_count', 2)) - .post() - .then(subscription => { - teardownSubscription = subscription.unsubscribe; - const userTasksList$ = toListObservable(subscription, compareUserTasks); - const userTasksFlow = UserTasksFlow.for(userTasksList$); + it('built by IDs and filters and retrieves correct data', (done) => { + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .where(Filters.eq('task_count', 2)) + .post() + .then(subscription => { + teardownSubscription = subscription.unsubscribe; + const userTasksList$ = toListObservable(subscription, compareUserTasks); + const userTasksFlow = UserTasksFlow.for(userTasksList$); - userTasksFlow - .waitFor([ - { id: user1.id, tasksCount: 2 }, - { id: user2.id, tasksCount: 2 } - ]) - .start() - .then(done) - .catch(e => fail(done, e)()) - }) - }); + userTasksFlow + .waitFor([ + {id: user1.id, tasksCount: 2}, + {id: user2.id, tasksCount: 2} + ]) + .start() + .then(done) + .catch(e => fail(done, e)()) + }) + }); - it('built by IDs and filters and updates data correctly when state changes', (done) => { - // TODO:2019-08-01:dmytro.dashenkov: Re-enable the test when columns filtering is implemented. - done(); + it('built by IDs and filters and updates data correctly when state changes', (done) => { + // TODO:2019-08-01:dmytro.dashenkov: Re-enable the test when columns filtering is implemented. + done(); - client.subscribeTo(UserTasks) - .byId([user1.id, user2.id]) - .where(Filters.ge('task_count', 2)) - .post() - .then(subscription => { - teardownSubscription = subscription.unsubscribe; - const userTasksList$ = toListObservable(subscription, compareUserTasks); - const userTasksFlow = UserTasksFlow.for(userTasksList$); + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .where(Filters.ge('task_count', 2)) + .post() + .then(subscription => { + teardownSubscription = subscription.unsubscribe; + const userTasksList$ = toListObservable(subscription, compareUserTasks); + const userTasksFlow = UserTasksFlow.for(userTasksList$); - userTasksFlow - .waitFor([ - { id: user1.id, tasksCount: 2 }, - { id: user2.id, tasksCount: 2 } - ]) - .then(() => { - const taskToReassign = user1.tasks[0]; - TestEnvironment.reassignTask(taskToReassign, user2.id, client); - }) - .waitFor([ - { id: user2.id, tasksCount: 3 } - ]) - .then(() => { - const taskToReassign = user1.tasks[1]; - TestEnvironment.reassignTask(taskToReassign, user2.id, client); - }) - .waitFor([ - { id: user2.id, tasksCount: 4 } - ]) - .start() - .then(done) - .catch(e => fail(done, e)()) - }); - }); + userTasksFlow + .waitFor([ + {id: user1.id, tasksCount: 2}, + {id: user2.id, tasksCount: 2} + ]) + .then(() => { + const taskToReassign = user1.tasks[0]; + TestEnvironment.reassignTask(taskToReassign, user2.id, client); + }) + .waitFor([ + {id: user2.id, tasksCount: 3} + ]) + .then(() => { + const taskToReassign = user1.tasks[1]; + TestEnvironment.reassignTask(taskToReassign, user2.id, client); + }) + .waitFor([ + {id: user2.id, tasksCount: 4} + ]) + .start() + .then(done) + .catch(e => fail(done, e)()) + }); + }); }); diff --git a/integration-tests/js-tests/test/given/users-test-environment.js b/integration-tests/js-tests/test/given/users-test-environment.js index 7e42d2117..1a39b74bd 100644 --- a/integration-tests/js-tests/test/given/users-test-environment.js +++ b/integration-tests/js-tests/test/given/users-test-environment.js @@ -30,86 +30,86 @@ import TestEnvironment from '../firebase-client/given/test-environment'; */ export class UserTasksTestEnvironment extends TestEnvironment { - /** - * Creates requested amount of tasks assigned to the given user. - * - * @param {User} user a user to create tasks for - * @param {number} taskCount an amount of tasks to be created and assigned - * @param {Client} client a Spine client to send commands - * @return {Promise} a promise to be resolved when all `CreateTask` commands acknowledged; - * rejected if an error occurs; - */ - static createTaskFor(user, taskCount, client) { - const createTaskPromises = []; - for (let i = 0; i < taskCount; i++) { - const command = super.createTaskCommand({ - named: `task#${i + 1}-for-${user.name}`, - assignedTo: user.id - }); - const taskId = command.getId(); + /** + * Creates requested amount of tasks assigned to the given user. + * + * @param {User} user a user to create tasks for + * @param {number} taskCount an amount of tasks to be created and assigned + * @param {Client} client a Spine client to send commands + * @return {Promise} a promise to be resolved when all `CreateTask` commands acknowledged; + * rejected if an error occurs; + */ + static createTaskFor(user, taskCount, client) { + const createTaskPromises = []; + for (let i = 0; i < taskCount; i++) { + const command = super.createTaskCommand({ + named: `task#${i + 1}-for-${user.name}`, + assignedTo: user.id + }); + const taskId = command.getId(); - let createTaskAcknowledged; - let createTaskFailed; + let createTaskAcknowledged; + let createTaskFailed; - const promise = new Promise((resolve, reject) => { - createTaskAcknowledged = resolve; - createTaskFailed = reject; - }); - createTaskPromises.push(promise); - client.command(command) - .onOk(() => { - user.tasks.push(taskId); - createTaskAcknowledged(); - }) - .onError(createTaskFailed) - .onRejection(createTaskFailed) - .post(); - } - - return Promise.all(createTaskPromises); + const promise = new Promise((resolve, reject) => { + createTaskAcknowledged = resolve; + createTaskFailed = reject; + }); + createTaskPromises.push(promise); + client.command(command) + .onOk(() => { + user.tasks.push(taskId); + createTaskAcknowledged(); + }) + .onError(createTaskFailed) + .onRejection(createTaskFailed) + .post(); } - /** - * Sends a command to reassign the given task to the given user. - * - * @param {!TaskId} taskId - * @param {!UserId} newAssignee - * @param {!Client} client - * @return {Promise} - */ - static reassignTask(taskId, newAssignee, client) { - return new Promise((resolve, reject) => { - const command = new ReassignTask(); - command.setId(taskId); - command.setNewAssignee(newAssignee); + return Promise.all(createTaskPromises); + } - client.command(command) - .onOk(resolve) - .onError(reject) - .onRejection(reject) - .post(); - }) - } + /** + * Sends a command to reassign the given task to the given user. + * + * @param {!TaskId} taskId + * @param {!UserId} newAssignee + * @param {!Client} client + * @return {Promise} + */ + static reassignTask(taskId, newAssignee, client) { + return new Promise((resolve, reject) => { + const command = new ReassignTask(); + command.setId(taskId); + command.setNewAssignee(newAssignee); - /** - * @param {?String} withPrefix - * @return {UserId} - */ - static userId(withPrefix) { - const id = new UserId(); - id.setValue(`${withPrefix ? withPrefix : 'ANONYMOUS'}-${uuid.v4()}`); - return id; - } + client.command(command) + .onOk(resolve) + .onError(reject) + .onRejection(reject) + .post(); + }) + } + + /** + * @param {?String} withPrefix + * @return {UserId} + */ + static userId(withPrefix) { + const id = new UserId(); + id.setValue(`${withPrefix ? withPrefix : 'ANONYMOUS'}-${uuid.v4()}`); + return id; + } - /** - * @param {?String} withName - * @return {User} - */ - static newUser(withName) { - return { - name: withName, - id: UserTasksTestEnvironment.userId(withName), - tasks: [] - } + /** + * @param {?String} withName + * @return {User} + */ + static newUser(withName) { + return { + name: withName, + id: UserTasksTestEnvironment.userId(withName), + tasks: [] } + } } diff --git a/integration-tests/js-tests/test/test-helpers.js b/integration-tests/js-tests/test/test-helpers.js index 6dd484db3..f051b9447 100644 --- a/integration-tests/js-tests/test/test-helpers.js +++ b/integration-tests/js-tests/test/test-helpers.js @@ -50,9 +50,9 @@ import {BehaviorSubject, Observable} from 'rxjs'; export function fail(done, message = '') { return cause => { if (message) { - done(new Error(`Test failed. Cause: ${message}`)); + done(new Error(`Test failed. Cause: ${message}`)); } else { - done(new Error(`Test failed. Cause: ${cause ? cause : 'not identified'}`)); + done(new Error(`Test failed. Cause: ${cause ? cause : 'not identified'}`)); } }; } @@ -64,8 +64,8 @@ export function fail(done, message = '') { * @param {UserId[]} expected */ export function ensureUserIds(actual, expected) { - return arraysEqualDeep(actual, expected, (userId1, userId2) => - userId1.getValue() === userId2.getValue()); + return arraysEqualDeep(actual, expected, (userId1, userId2) => + userId1.getValue() === userId2.getValue()); } /** @@ -78,9 +78,9 @@ export function ensureUserIds(actual, expected) { * }[]} expectedUsers */ export function ensureUserTasks(actualUserTasks, expectedUsers) { - const actualUserIds = actualUserTasks.map(userTasks => userTasks.getId()); - const expectedUserIds = expectedUsers.map(user => user.id); - return ensureUserIds(actualUserIds, expectedUserIds); + const actualUserIds = actualUserTasks.map(userTasks => userTasks.getId()); + const expectedUserIds = expectedUsers.map(user => user.id); + return ensureUserIds(actualUserIds, expectedUserIds); } /** @@ -94,12 +94,12 @@ export function ensureUserTasks(actualUserTasks, expectedUsers) { * }[]} expectedUsers */ export function ensureUserTasksCount(actualUserTasks, expectedUsers) { - return arraysEqualDeep(actualUserTasks, - expectedUsers, - (userTasks, expected) => - userTasks.getId().getValue() === expected.id.getValue() - && userTasks.getTasksList().length === expected.tasksCount - ); + return arraysEqualDeep(actualUserTasks, + expectedUsers, + (userTasks, expected) => + userTasks.getId().getValue() === expected.id.getValue() + && userTasks.getTasksList().length === expected.tasksCount + ); } /** @@ -112,19 +112,19 @@ export function ensureUserTasksCount(actualUserTasks, expectedUsers) { * @return {boolean} `true` if arrays are equal; `false` otherwise; */ function arraysEqualDeep(arr1, arr2, compare) { - if (!Array.isArray(arr1) || !Array.isArray(arr2)) { - throw new Error('Unable to compare equality of non-array objects.'); - } + if (!Array.isArray(arr1) || !Array.isArray(arr2)) { + throw new Error('Unable to compare equality of non-array objects.'); + } - if (arr1.length === 0 && arr2.length === 0) { - return true; - } + if (arr1.length === 0 && arr2.length === 0) { + return true; + } - const intersection = arr1.filter(value1 => { - return arr2.findIndex(value2 => compare(value1, value2)) > -1; - }); + const intersection = arr1.filter(value1 => { + return arr2.findIndex(value2 => compare(value1, value2)) > -1; + }); - return intersection.length === arr1.length + return intersection.length === arr1.length } /** @@ -139,41 +139,41 @@ function arraysEqualDeep(arr1, arr2, compare) { * @template a class of a subscription target entities */ export function toListObservable(subscription, compare) { - const list$ = new BehaviorSubject([]); - const {itemAdded, itemChanged, itemRemoved} = subscription; + const list$ = new BehaviorSubject([]); + const {itemAdded, itemChanged, itemRemoved} = subscription; - itemAdded.subscribe({ - next: addedItem => { - const currentList = list$.getValue(); - list$.next([...currentList, addedItem]); - } - }); + itemAdded.subscribe({ + next: addedItem => { + const currentList = list$.getValue(); + list$.next([...currentList, addedItem]); + } + }); - itemChanged.subscribe({ - next: changedItem => { - const currentList = list$.getValue(); - const changedItemIndex =_indexOf(changedItem, currentList, compare); - const updatedList = currentList.slice(); - updatedList[changedItemIndex] = changedItem; - list$.next(updatedList); - } - }); + itemChanged.subscribe({ + next: changedItem => { + const currentList = list$.getValue(); + const changedItemIndex = _indexOf(changedItem, currentList, compare); + const updatedList = currentList.slice(); + updatedList[changedItemIndex] = changedItem; + list$.next(updatedList); + } + }); - itemRemoved.subscribe({ - next: removedItem => { - const currentList = list$.getValue(); - const removedItemIndex = _indexOf(removedItem, currentList, compare); - const updatedList = [ - ...currentList.slice(0, removedItemIndex), - ...currentList.slice(removedItemIndex + 1) - ]; - list$.next(updatedList); - } - }); + itemRemoved.subscribe({ + next: removedItem => { + const currentList = list$.getValue(); + const removedItemIndex = _indexOf(removedItem, currentList, compare); + const updatedList = [ + ...currentList.slice(0, removedItemIndex), + ...currentList.slice(removedItemIndex + 1) + ]; + list$.next(updatedList); + } + }); - return list$.asObservable(); + return list$.asObservable(); } function _indexOf(item, items, compare) { - return items.findIndex(value => compare(value, item)); + return items.findIndex(value => compare(value, item)); } From 8dcbd7367eb61b19d171decab7def1ca4487912b Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 21:10:09 +0200 Subject: [PATCH 56/70] Remove a redundant empty line --- client-js/test/client/filtering-request-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client-js/test/client/filtering-request-test.js b/client-js/test/client/filtering-request-test.js index 84290dc63..d497bc052 100644 --- a/client-js/test/client/filtering-request-test.js +++ b/client-js/test/client/filtering-request-test.js @@ -18,7 +18,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - import assert from 'assert'; import uuid from 'uuid'; import {ActorProvider, ActorRequestFactory, Filters} from '@lib/client/actor-request-factory'; From f00421a67362026f254729b98f2d51a681cf8a32 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 21:14:22 +0200 Subject: [PATCH 57/70] Add a comment about why `Function.identity()` can't be used --- .../java/io/spine/web/firebase/subscription/UpdatePayload.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java index e1ffd0d8e..2480130df 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java @@ -162,6 +162,9 @@ private static void addChildOrNull(NodeValue node, String id, Message message) { /** * An identity {@link Event}-to-{@link Message} conversion. + * + *

The standard {@link Function#identity()} can't be applied because it requires having the + * exact same type as input and output. */ private static Function identity() { return event -> event; From 1e979c14689e3276467515e12a6a046e512b0f44 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 21:17:34 +0200 Subject: [PATCH 58/70] Revert the increased timeout of a test --- .../js-tests/test/firebase-client/send-command-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index 39ad4fcb6..f9b1dc031 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -32,7 +32,7 @@ import {Type} from '@lib/client/typed-message'; describe('FirebaseClient command sending', function () { // Big timeout allows to receive model state changes during tests. - this.timeout(15000); + this.timeout(5000); it('completes with success', done => { From 08a997a0fe4f7af9fcc9b808cf3be3e8da4c745f Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 21:29:12 +0200 Subject: [PATCH 59/70] Remove the no longer relevant todo --- .../js-tests/test/firebase-client/send-command-test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index f9b1dc031..c84268155 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -101,8 +101,7 @@ describe('FirebaseClient command sending', function () { try { assert.ok(error instanceof CommandValidationError); assert.ok(error.validationError()); - // TODO:2019-06-05:yegor.udovchenko: Find the reason of failing assertion - // assert.ok(error.assuresCommandNeglected()); + assert.ok(error.assuresCommandNeglected()); const cause = error.getCause(); assert.ok(cause); From ceaa4826d87f9784fc62a203b32d4ef129920396 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 22:07:46 +0200 Subject: [PATCH 60/70] Fix string formatting in tests --- .../test/client/filtering-request-test.js | 4 ++-- .../test/firebase-client/subscribe-test.js | 21 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/client-js/test/client/filtering-request-test.js b/client-js/test/client/filtering-request-test.js index d497bc052..765d6420e 100644 --- a/client-js/test/client/filtering-request-test.js +++ b/client-js/test/client/filtering-request-test.js @@ -152,8 +152,8 @@ export function filteringRequestTest(newRequest, buildResult, getTarget, getFiel const compositeFiltersLength = compositeFilters.length; assert.equal( 1, compositeFiltersLength, - `Expected the composite filter list to contain a single filter, the actual - length: '${compositeFiltersLength}'.` + `Expected the composite filter list to contain a single filter, the actual length: + '${compositeFiltersLength}'.` ); const filters = compositeFilters[0].getFilterList(); const length = filters.length; diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index 69bb9c0a5..d11e34b2b 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -50,7 +50,7 @@ describe('FirebaseClient subscription', function () { itemAdded.subscribe({ next: task => { const id = task.getId().getValue(); - console.log(`Retrieved task '${id}'`); + console.log(`Retrieved task '${id}'.`); if (taskIds.includes(id)) { receivedCount++; if (receivedCount === newTasksCount) { @@ -92,7 +92,7 @@ describe('FirebaseClient subscription', function () { if (taskId === id) { assert.equal( INITIAL_TASK_NAME, item.getName(), - `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}"` + `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}".` ); const renameCommand = TestEnvironment.renameTaskCommand({ withId: taskId, @@ -116,7 +116,7 @@ describe('FirebaseClient subscription', function () { if (taskId === id) { assert.equal( item.getName(), UPDATED_TASK_NAME, - `Task is named "${item.getName()}", expected "${UPDATED_TASK_NAME}"` + `Task is named "${item.getName()}", expected "${UPDATED_TASK_NAME}".` ); console.log(`Got task changes for '${id}'.`); unsubscribe(); @@ -164,7 +164,7 @@ describe('FirebaseClient subscription', function () { if (taskIdValue === id) { assert.equal( item.getName(), INITIAL_TASK_NAME, - `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}"` + `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}".` ); } const renameCommand = TestEnvironment.renameTaskCommand({ @@ -239,8 +239,7 @@ describe('FirebaseClient subscription', function () { if (taskIdValue === id) { assert.equal( initialTaskName, item.getName(), - `Task is named "${item.getName()}", - expected "${initialTaskName}".` + `Task is named "${item.getName()}", expected "${initialTaskName}".` ); } const renameCommand = TestEnvironment.renameTaskCommand({ @@ -261,8 +260,8 @@ describe('FirebaseClient subscription', function () { console.log('Task removed'); assert.equal( taskIdValue, id, - `A wrong Task item is removed, expected the task with ID - "${taskIdValue}", received the task with ID "${id}".` + `A wrong Task item is removed, expected the task with ID "${taskIdValue}", + received the task with ID "${id}".` ); unsubscribe(); done(); @@ -308,8 +307,7 @@ describe('FirebaseClient subscription', function () { const newTaskName = message.getName(); assert.equal( updatedTaskName, newTaskName, - `Expected the new task name to be ${updatedTaskName}, got - ${newTaskName} instead.` + `Expected the new task name to be ${updatedTaskName}, got ${newTaskName} instead.` ); unsubscribe(); done(); @@ -329,8 +327,7 @@ describe('FirebaseClient subscription', function () { client.command(renameCommand) .onOk(() => console.log(`Task '${taskId}' renamed.`)) .onError(fail(done, 'Unexpected error while renaming a task.')) - .onRejection(fail(done, - 'Unexpected rejection while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) .post(); }); From 0791b154280f641aa4bdf9baa03b023233f51c55 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 22:09:06 +0200 Subject: [PATCH 61/70] Fix more formatting --- .../test/firebase-client/subscribe-test.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index d11e34b2b..da8f81527 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -61,12 +61,10 @@ describe('FirebaseClient subscription', function () { } }); itemRemoved.subscribe({ - next: fail(done, - 'Unexpected entity remove during entity create subscription test.') + next: fail(done, 'Unexpected entity remove during entity create subscription test.') }); itemChanged.subscribe({ - next: fail(done, - 'Unexpected entity change during entity create subscription test.') + next: fail(done, 'Unexpected entity change during entity create subscription test.') }); commands.forEach(command => { client.command(command) @@ -101,8 +99,7 @@ describe('FirebaseClient subscription', function () { client.command(renameCommand) .onOk(() => console.log(`Task '${taskId}' renamed.`)) .onError(fail(done, 'Unexpected error while renaming a task.')) - .onRejection(fail(done, - 'Unexpected rejection while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) .post(); } } @@ -172,8 +169,7 @@ describe('FirebaseClient subscription', function () { to: UPDATED_NAMES[0] }); client.command(renameCommand) - .onOk(() => console.log( - `Task '${taskIdValue}' renamed for the first time.`)) + .onOk(() => console.log(`Task '${taskIdValue}' renamed for the first time.`)) .onError(fail(done, 'Unexpected error while renaming a task.')) .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) .post(); @@ -198,8 +194,7 @@ describe('FirebaseClient subscription', function () { to: UPDATED_NAMES[1] }); client.command(renameCommand) - .onOk(() => console.log( - `Task '${taskIdValue}' renamed for the second time.`)) + .onOk(() => console.log(`Task '${taskIdValue}' renamed for the second time.`)) .onError(fail(done, 'Unexpected error while renaming a task.')) .onRejection(fail(done, @@ -247,8 +242,8 @@ describe('FirebaseClient subscription', function () { to: nameAfterRenamed }); client.command(renameCommand) - .onOk(() => console.log( - `Task '${taskIdValue}' is renamed to '${nameAfterRenamed}'.`)) + .onOk(() => + console.log(`Task '${taskIdValue}' is renamed to '${nameAfterRenamed}'.`)) .onError(fail(done, 'Unexpected error while renaming a task.')) .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) .post(); From ccb7588baadfed54e6411769227f9b8b5c0983c4 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 22:10:40 +0200 Subject: [PATCH 62/70] Fix a comment formatting --- integration-tests/js-tests/test/firebase-client/topic-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/js-tests/test/firebase-client/topic-test.js b/integration-tests/js-tests/test/firebase-client/topic-test.js index 57a087c67..85ee31b0d 100644 --- a/integration-tests/js-tests/test/firebase-client/topic-test.js +++ b/integration-tests/js-tests/test/firebase-client/topic-test.js @@ -87,7 +87,7 @@ class UserTasksFlow { } static doNothing() { - // NoOp + // NO-OP. } waitFor(state) { From 01b28cb076d8565c11b4e0d8e96b259923774061 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 22:16:28 +0200 Subject: [PATCH 63/70] Re-enable test which is now working --- integration-tests/js-tests/test/firebase-client/topic-test.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/integration-tests/js-tests/test/firebase-client/topic-test.js b/integration-tests/js-tests/test/firebase-client/topic-test.js index 85ee31b0d..2fac2fa4b 100644 --- a/integration-tests/js-tests/test/firebase-client/topic-test.js +++ b/integration-tests/js-tests/test/firebase-client/topic-test.js @@ -223,9 +223,6 @@ describe('FirebaseClient subscribes to topic', function () { }); it('built by IDs and filters and updates data correctly when state changes', (done) => { - // TODO:2019-08-01:dmytro.dashenkov: Re-enable the test when columns filtering is implemented. - done(); - client.subscribeTo(UserTasks) .byId([user1.id, user2.id]) .where(Filters.ge('task_count', 2)) From 7a672b0a6769335719997eb7f45055ab89eb3867 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Wed, 11 Dec 2019 22:39:47 +0200 Subject: [PATCH 64/70] Fix the example code --- client-js/main/client/client-request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index ceb1fe42b..4393c129b 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -456,7 +456,7 @@ const NOOP_CALLBACK = () => {}; * client.command(logInUser) * .onOk(_logOk) * .onError(_logError) - * .observe(UserLoggedIn.class, (({subscribe, unsubscribe}) => { + * .observe(UserLoggedIn.class, ({subscribe, unsubscribe}) => { * subscribe(event => _logAndUnsubscribe(event, unsubscribe)); * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); * }) From 31cf59283cd6b1e43071f45faa5961e138b40dd7 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Thu, 12 Dec 2019 13:51:23 +0200 Subject: [PATCH 65/70] Split `client-request` into several JS files --- client-js/main/client/client-request.js | 553 +----------------- client-js/main/client/command-request.js | 181 ++++++ client-js/main/client/composite-client.js | 9 +- client-js/main/client/filtering-request.js | 142 +++++ client-js/main/client/query-request.js | 120 ++++ client-js/main/client/subscribing-request.js | 189 ++++++ client-js/test/client/query-request-test.js | 2 +- .../test/client/subscription-request-test.js | 2 +- 8 files changed, 638 insertions(+), 560 deletions(-) create mode 100644 client-js/main/client/command-request.js create mode 100644 client-js/main/client/filtering-request.js create mode 100644 client-js/main/client/query-request.js create mode 100644 client-js/main/client/subscribing-request.js diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js index 4393c129b..dd9d74ddb 100644 --- a/client-js/main/client/client-request.js +++ b/client-js/main/client/client-request.js @@ -20,33 +20,12 @@ "use strict"; -import {Message} from 'google-protobuf'; -import {CompositeFilter, Filter} from '../proto/spine/client/filters_pb'; -import {OrderBy} from '../proto/spine/client/query_pb'; -import {Command, CommandId} from '../proto/spine/core/command_pb'; -import {MessageId, Origin} from '../proto/spine/core/diagnostics_pb'; -import {AnyPacker} from "./any-packer"; -import {Filters} from "./actor-request-factory"; -import {Type} from "./typed-message"; - -/** - * @typedef EventSubscriptionCallbacks - * - * A pair of callbacks that allow to add an event consumer and cancel the subscription - * respectively. - * - * @property {consumerCallback} subscribe the callback which allows to setup an - * event consumer to use for the subscription - * @property {parameterlessCallback} unsubscribe the callback which allows to cancel the - * subscription - */ - /** * A request from a client to the Spine backend. * * @abstract */ -class ClientRequest { +export class ClientRequest { /** * @protected @@ -68,533 +47,3 @@ class ClientRequest { this._requestFactory = requestFactory; } } - -/** - * An abstract base for client requests that filter messages by certain criteria. - * - * @abstract - * - * @template the type of the builder wrapped by this request - * @template the type of the messages that store the request data - */ -class FilteringRequest extends ClientRequest { - - /** - * @param {!Class} targetType the target type of the request - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - * - * @protected - */ - constructor(targetType, client, actorRequestFactory) { - super(client, actorRequestFactory); - this.targetType = targetType; - } - - /** - * Adds filtering by IDs to the built request. - * - * @param ids {!|Number|String|[]|Number[]|String[]} - * the IDs of interest - * @return {this} self for method chaining - */ - byId(ids) { - ids = FilteringRequest._ensureArray(ids); - this._builder().byIds(ids); - return this._self(); - } - - /** - * Adds filtering by predicates to the built request. - * - * Filters specified in a list are considered to be joined using `AND` operator. - * - * @param {!Filter|CompositeFilter|Filter[]|CompositeFilter[]} predicates the filters - * @return {this} self for method chaining - */ - where(predicates) { - predicates = FilteringRequest._ensureArray(predicates); - this._builder().where(predicates); - return this._self(); - } - - /** - * Applies a field mask to the request results. - * - * The names of the fields must be formatted according to the `google.protobuf.FieldMask` - * specification. - * - * @param {!String|String[]} fieldPaths the fields to include in the mask - * @return {this} self for method chaining - */ - withMask(fieldPaths) { - fieldPaths = FilteringRequest._ensureArray(fieldPaths); - this._builder().withMask(fieldPaths); - return this._self(); - } - - /** - * Returns the builder for messages that store request data. - * - * @return {AbstractTargetBuilder} the builder instance - * - * @protected - */ - _builder() { - if (!this._builderInstance) { - const newBuilderFn = this._newBuilderFn(); - this._builderInstance = newBuilderFn(this._requestFactory); - } - return this._builderInstance; - } - - /** - * Returns the function with which the {@link _builderInstance} can be created. - * - * @abstract - * @return {Function} - * - * @protected - */ - _newBuilderFn() { - throw new Error('Not implemented in abstract base.'); - } - - /** - * @abstract - * @return {this} - * - * @protected - */ - _self() { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Wraps the passed argument into array if it's not an array already. - * - * The `null` values are converted into an empty array. - * - * @return {Array} the passed argument as an array - * - * @private - */ - static _ensureArray(values) { - if (!values) { - return []; - } - if (!(values instanceof Array)) { - return [values] - } - return values; - } -} - -/** - * A request to retrieve entities of the given type. - * - * Allows to post the query data to the Spine backend and receive the entity states as `Promise`. - * - * A usage example: - * ``` - * const customers = - * client.select(Customer.class) - * .byId(westCoastCustomerIds()) - * .withMask("name", "address", "email") - * .where([Filters.eq("type", "permanent"), - * Filters.eq("discount_percent", 10), - * Filters.eq("company_size", Company.Size.SMALL)]) - * .orderBy("name", OrderBy.Direction.ASCENDING) - * .limit(20) - * .run(); // The returned type is `Promise`. - * ``` - * - * All of the called filtering methods are optional. If none of them are specified, all entities - * of type will be retrieved. - * - * @template the query target type - */ -export class QueryRequest extends FilteringRequest { - - /** - * @param {!Class} targetType the target type of entities - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - */ - constructor(targetType, client, actorRequestFactory) { - super(targetType, client, actorRequestFactory) - } - - /** - * Sets the sorting order for the retrieved results. - * - * @param {!String} column the column to order by - * @param {!OrderBy.Direction} direction the ascending/descending direction - * @return {this} self for method chaining - */ - orderBy(column, direction) { - if (direction === OrderBy.Direction.ASCENDING) { - this._builder().orderAscendingBy(column); - } else { - this._builder().orderDescendingBy(column); - } - return this._self(); - } - - /** - * Sets the maximum number of returned entities. - * - * Can only be used in conjunction with the {@link #orderBy} condition. - * - * @param {number} count the max number of response entities - * @return {this} self for method chaining - */ - limit(count) { - this._builder().limit(count); - return this._self(); - } - - /** - * Builds a `Query` instance based on currently specified filters. - * - * @return {spine.client.Query} a `Query` instance - */ - query() { - return this._builder().build(); - } - - /** - * Runs the query and obtains the results. - * - * @return {Promise<[]>} the asynchronously resolved query results - */ - run() { - const query = this.query(); - return this._client.read(query); - } - - /** - * @inheritDoc - */ - _newBuilderFn() { - return requestFactory => requestFactory.query().select(this.targetType); - } - - /** - * @inheritDoc - */ - _self() { - return this; - } -} - -/** - * An abstract base for requests that subscribe to messages of a certain type. - * - * @abstract - * @template the target type of messages, for events the type is always `spine.core.Event` - */ -class SubscribingRequest extends FilteringRequest { - - /** - * Builds a `Topic` instance based on the currently specified filters. - * - * @return {spine.client.Topic} a `Topic` instance - */ - topic() { - return this._builder().build(); - } - - /** - * Posts a subscription request and returns the result as `Promise`. - * - * @return {Promise | EventSubscriptionObject>} - * the asynchronously resolved subscription object - */ - post() { - const topic = this.topic(); - return this._subscribe(topic); - } - - /** - * @inheritDoc - */ - _newBuilderFn() { - return requestFactory => requestFactory.topic().select(this.targetType); - } - - /** - * @abstract - * @return {Promise | EventSubscriptionObject>} - * - * @protected - */ - _subscribe(topic) { - throw new Error('Not implemented in abstract base.'); - } -} - -/** - * A request to subscribe to updates of entity states of a certain type. - * - * Allows to obtain the `EntitySubscriptionObject` which exposes the entity changes in a form of - * callbacks which can be subscribed to. - * - * A usage example: - * ``` - * client.subscribeTo(Task.class) - * .where(Filters.eq("status", Task.Status.ACTIVE)) - * // Additional filtering can be done here. - * .post() - * .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - * itemAdded.subscribe(_addDisplayedTask); - * itemChanged.subscribe(_changeDisplayedTask); - * itemRemoved.subscribe(_removeDisplayedTask); - * }); - * ``` - * - * If the entity matched the subscription criteria at one point, but stopped to do so, the - * `itemRemoved` callback will be triggered for it. The callback will contain the last entity state - * that matched the subscription. - * - * Please note that the subscription object should be manually unsubscribed when it's no longer - * needed to receive the updates. This can be done with the help of `unsubscribe` callback. - * - * @template the target entity type - */ -export class SubscriptionRequest extends SubscribingRequest { - - /** - * @param {!Class} entityType the target entity type - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - */ - constructor(entityType, client, actorRequestFactory) { - super(entityType, client, actorRequestFactory) - } - - /** - * @inheritDoc - * - * @return {Promise>} - */ - _subscribe(topic) { - return this._client.subscribe(topic); - } - - /** - * @inheritDoc - */ - _self() { - return this; - } -} - -/** - * A request to subscribe to events of a certain type. - * - * Allows to obtain the `EventSubscriptionObject` which reflects the events that happened in the - * system and match the subscription criteria. - * - * A usage example: - * ``` - * client.subscribeToEvent(TaskCreated.class) - * .where([Filters.eq("task_priority", Task.Priority.HIGH), - * Filters.eq("context.past_message.actor_context.actor", userId)]) - * .post() - * .then(({eventEmitted, unsubscribe}) => { - * eventEmitted.subscribe(_logEvent); - * }); - * ``` - * - * The fields specified to the `where` filters should either be a part of the event message or - * have a `context.` prefix and address one of the fields of the `EventContext` type. - * - * The `eventEmitted` observable reflects all events that occurred in the system and match the - * subscription criteria, in a form of `spine.core.Event`. - * - * Please note that the subscription object should be manually unsubscribed when it's no longer - * needed to receive the updates. This can be done with the help of `unsubscribe` callback. - */ -export class EventSubscriptionRequest extends SubscribingRequest { - - /** - * @param {!Class} eventType the target event type - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - */ - constructor(eventType, client, actorRequestFactory) { - super(eventType, client, actorRequestFactory) - } - - /** - * @inheritDoc - * - * @return {Promise} - */ - _subscribe(topic) { - return this._client.subscribeToEvents(topic); - } - - /** - * @inheritDoc - */ - _self() { - return this; - } -} - -const NOOP_CALLBACK = () => {}; - -/** - * A request to post a command to the Spine backend. - * - * Optionally allows to subscribe to events that are the immediate results of handling the command. - * - * A usage example: - * ``` - * client.command(logInUser) - * .onOk(_logOk) - * .onError(_logError) - * .observe(UserLoggedIn.class, ({subscribe, unsubscribe}) => { - * subscribe(event => _logAndUnsubscribe(event, unsubscribe)); - * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); - * }) - * .observe(UserAlreadyLoggedIn.class, (({subscribe, unsubscribe}) => { - * subscribe(event => _warnAboutAndUnsubscribe(event, unsubscribe)); - * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); - * }) - * .post(); - * ``` - * - * The `subscribe` callback provided to the consumer allows to configure an event receival process - * while the `unsubscribe` callback allows to cancel the subscription. - * - * Please note that in the example above we make sure the subscription is cancelled even if the - * specified event type is never received. - */ -export class CommandRequest extends ClientRequest { - - /** - * @param {!Message} commandMessage the command to post - * @param {!Client} client the client which initiated the request - * @param {!ActorRequestFactory} actorRequestFactory the request factory - */ - constructor(commandMessage, client, actorRequestFactory) { - super(client, actorRequestFactory); - this._commandMessage = commandMessage; - this._onAck = NOOP_CALLBACK; - this._onError = NOOP_CALLBACK; - this._onRejection = NOOP_CALLBACK; - this._observedTypes = []; - } - - /** - * Runs the callback if the command is successfully handled by the Spine server. - * - * @param {!parameterlessCallback} callback the callback to run - * @return {this} self for method chaining - */ - onOk(callback) { - this._onAck = callback; - return this; - } - - /** - * Runs the callback if the command could not be handled by the Spine server due to the - * technical error. - * - * @param {!consumerCallback} callback the callback to run - * @return {this} self for method chaining - */ - onError(callback) { - this._onError = callback; - return this; - } - - /** - * Runs the callback if the server responded with the `rejection` status on a command. - * - * Note that with the current Spine server implementation the command being rejected right away - * is very unlikely. In most cases, the command will be acknowledged with `OK` status and only - * then lead to a business rejection. You can check this scenario using the `observe` method. - * - * @param {!consumerCallback} callback - * @return {this} self for method chaining - */ - onRejection(callback) { - this._onRejection = callback; - return this; - } - - /** - * Adds the event type to the list of observed command handling results. - * - * @param {!Class} eventType a type of the observed events - * @param {!consumerCallback} consumer - * a consumer of the `subscribe` and `unsubscribe` callbacks which are responsible for - * accepting the incoming events and cancelling the subscription respectively - * @return {this} self for method chaining - */ - observe(eventType, consumer) { - this._observedTypes.push({type: eventType, consumer: consumer}); - return this; - } - - /** - * Posts the command to the server and subscribes to all observed types. - * - * @return {Promise} a promise that signals if the command posting was done successfully, - * may be ignored - */ - post() { - const command = this._requestFactory.command().create(this._commandMessage); - const onAck = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; - const promises = []; - this._observedTypes.forEach(({type, consumer}) => { - const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); - const promise = this._client.subscribeToEvent(type) - .where(originFilter) - .post() - .then(({eventEmitted, unsubscribe}) => { - const subscribe = eventConsumer => { - eventEmitted.subscribe({ - next: eventConsumer - }); - }; - consumer({subscribe, unsubscribe}); - }); - promises.push(promise); - }); - const subscriptionPromise = Promise.all(promises); - return subscriptionPromise.then(() => this._client.post(command, onAck)); - } - - /** - * @param {!Command} command - * @return {Origin} - * - * @private - */ - _asOrigin(command) { - const result = new Origin(); - - const messageId = new MessageId(); - const commandIdType = Type.forClass(CommandId); - const packedId = AnyPacker.pack(command.getId()).as(commandIdType); - messageId.setId(packedId); - const typeUrl = command.getMessage().getTypeUrl(); - messageId.setTypeUrl(typeUrl); - result.setMessage(messageId); - - let grandOrigin = command.getContext().getOrigin(); - if (!grandOrigin) { - grandOrigin = new Origin(); - } - result.setGrandOrigin(grandOrigin); - - const actorContext = command.getContext().getActorContext(); - result.setActorContext(actorContext); - return result; - } -} diff --git a/client-js/main/client/command-request.js b/client-js/main/client/command-request.js new file mode 100644 index 000000000..d6b255901 --- /dev/null +++ b/client-js/main/client/command-request.js @@ -0,0 +1,181 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {Filters} from "./actor-request-factory"; +import {MessageId, Origin} from "../proto/spine/core/diagnostics_pb"; +import {Type} from "./typed-message"; +import {CommandId} from "../proto/spine/core/command_pb"; +import {AnyPacker} from "./any-packer"; +import {ClientRequest} from "./client-request"; + +const NOOP_CALLBACK = () => {}; + +/** + * A request to post a command to the Spine backend. + * + * Optionally allows to subscribe to events that are the immediate results of handling the command. + * + * A usage example: + * ``` + * client.command(logInUser) + * .onOk(_logOk) + * .onError(_logError) + * .observe(UserLoggedIn.class, ({subscribe, unsubscribe}) => { + * subscribe(event => _logAndUnsubscribe(event, unsubscribe)); + * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); + * }) + * .observe(UserAlreadyLoggedIn.class, (({subscribe, unsubscribe}) => { + * subscribe(event => _warnAboutAndUnsubscribe(event, unsubscribe)); + * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); + * }) + * .post(); + * ``` + * + * The `subscribe` callback provided to the consumer allows to configure an event receival process + * while the `unsubscribe` callback allows to cancel the subscription. + * + * Please note that in the example above we make sure the subscription is cancelled even if the + * specified event type is never received. + */ +export class CommandRequest extends ClientRequest { + + /** + * @param {!Message} commandMessage the command to post + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(commandMessage, client, actorRequestFactory) { + super(client, actorRequestFactory); + this._commandMessage = commandMessage; + this._onAck = NOOP_CALLBACK; + this._onError = NOOP_CALLBACK; + this._onRejection = NOOP_CALLBACK; + this._observedTypes = []; + } + + /** + * Runs the callback if the command is successfully handled by the Spine server. + * + * @param {!parameterlessCallback} callback the callback to run + * @return {this} self for method chaining + */ + onOk(callback) { + this._onAck = callback; + return this; + } + + /** + * Runs the callback if the command could not be handled by the Spine server due to the + * technical error. + * + * @param {!consumerCallback} callback the callback to run + * @return {this} self for method chaining + */ + onError(callback) { + this._onError = callback; + return this; + } + + /** + * Runs the callback if the server responded with the `rejection` status on a command. + * + * Note that with the current Spine server implementation it's rare for the command to be + * rejected right away. In most cases, the command will be acknowledged with the `OK` status and + * only then lead to a business rejection. You can check this scenario using the `observe` method. + * + * @param {!consumerCallback} callback + * @return {this} self for method chaining + */ + onRejection(callback) { + this._onRejection = callback; + return this; + } + + /** + * Adds the event type to the list of observed command handling results. + * + * @param {!Class} eventType a type of the observed events + * @param {!consumerCallback} consumer + * a consumer of the `subscribe` and `unsubscribe` callbacks which are responsible for + * accepting the incoming events and cancelling the subscription respectively + * @return {this} self for method chaining + */ + observe(eventType, consumer) { + this._observedTypes.push({type: eventType, consumer: consumer}); + return this; + } + + /** + * Posts the command to the server and subscribes to all observed types. + * + * @return {Promise} a promise that signals if the command posting was done successfully, + * may be ignored + */ + post() { + const command = this._requestFactory.command().create(this._commandMessage); + const onAck = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; + const promises = []; + this._observedTypes.forEach(({type, consumer}) => { + const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); + const promise = this._client.subscribeToEvent(type) + .where(originFilter) + .post() + .then(({eventEmitted, unsubscribe}) => { + const subscribe = eventConsumer => { + eventEmitted.subscribe({ + next: eventConsumer + }); + }; + consumer({subscribe, unsubscribe}); + }); + promises.push(promise); + }); + const subscriptionPromise = Promise.all(promises); + return subscriptionPromise.then(() => this._client.post(command, onAck)); + } + + /** + * @param {!Command} command + * @return {Origin} + * + * @private + */ + _asOrigin(command) { + const result = new Origin(); + + const messageId = new MessageId(); + const commandIdType = Type.forClass(CommandId); + const packedId = AnyPacker.pack(command.getId()).as(commandIdType); + messageId.setId(packedId); + const typeUrl = command.getMessage().getTypeUrl(); + messageId.setTypeUrl(typeUrl); + result.setMessage(messageId); + + let grandOrigin = command.getContext().getOrigin(); + if (!grandOrigin) { + grandOrigin = new Origin(); + } + result.setGrandOrigin(grandOrigin); + + const actorContext = command.getContext().getActorContext(); + result.setActorContext(actorContext); + return result; + } +} diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index ed0e3be21..6e06ae963 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -25,13 +25,10 @@ import ObjectToProto from "./object-to-proto"; import {CommandHandlingError, CommandValidationError, SpineError} from "./errors"; import {Status} from '../proto/spine/core/response_pb'; import {Client} from "./client"; -import { - CommandRequest, - EventSubscriptionRequest, - QueryRequest, - SubscriptionRequest -} from "./client-request"; import {TypedMessage} from "./typed-message"; +import {QueryRequest} from "./query-request"; +import {EventSubscriptionRequest, SubscriptionRequest} from "./subscribing-request"; +import {CommandRequest} from "./command-request"; /** * A {@link Client} that delegates requests to case-specific client implementations. diff --git a/client-js/main/client/filtering-request.js b/client-js/main/client/filtering-request.js new file mode 100644 index 000000000..d154b9426 --- /dev/null +++ b/client-js/main/client/filtering-request.js @@ -0,0 +1,142 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {ClientRequest} from "./client-request"; + +/** + * An abstract base for client requests that filter messages by certain criteria. + * + * @abstract + * + * @template the type of the builder wrapped by this request + * @template the type of the messages that store the request data + */ +export class FilteringRequest extends ClientRequest { + + /** + * @param {!Class} targetType the target type of the request + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + * + * @protected + */ + constructor(targetType, client, actorRequestFactory) { + super(client, actorRequestFactory); + this.targetType = targetType; + } + + /** + * Adds filtering by IDs to the built request. + * + * @param ids {!|Number|String|[]|Number[]|String[]} + * the IDs of interest + * @return {this} self for method chaining + */ + byId(ids) { + ids = FilteringRequest._ensureArray(ids); + this._builder().byIds(ids); + return this._self(); + } + + /** + * Adds filtering by predicates to the built request. + * + * Filters specified in a list are considered to be joined using `AND` operator. + * + * @param {!Filter|CompositeFilter|Filter[]|CompositeFilter[]} predicates the filters + * @return {this} self for method chaining + */ + where(predicates) { + predicates = FilteringRequest._ensureArray(predicates); + this._builder().where(predicates); + return this._self(); + } + + /** + * Applies a field mask to the request results. + * + * The names of the fields must be formatted according to the `google.protobuf.FieldMask` + * specification. + * + * @param {!String|String[]} fieldPaths the fields to include in the mask + * @return {this} self for method chaining + */ + withMask(fieldPaths) { + fieldPaths = FilteringRequest._ensureArray(fieldPaths); + this._builder().withMask(fieldPaths); + return this._self(); + } + + /** + * Returns the builder for messages that store request data. + * + * @return {AbstractTargetBuilder} the builder instance + * + * @protected + */ + _builder() { + if (!this._builderInstance) { + const newBuilderFn = this._newBuilderFn(); + this._builderInstance = newBuilderFn(this._requestFactory); + } + return this._builderInstance; + } + + /** + * Returns the function with which the {@link _builderInstance} can be created. + * + * @abstract + * @return {Function} + * + * @protected + */ + _newBuilderFn() { + throw new Error('Not implemented in abstract base.'); + } + + /** + * @abstract + * @return {this} + * + * @protected + */ + _self() { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Wraps the passed argument into array if it's not an array already. + * + * The `null` values are converted into an empty array. + * + * @return {Array} the passed argument as an array + * + * @private + */ + static _ensureArray(values) { + if (!values) { + return []; + } + if (!(values instanceof Array)) { + return [values] + } + return values; + } +} diff --git a/client-js/main/client/query-request.js b/client-js/main/client/query-request.js new file mode 100644 index 000000000..94f77aa83 --- /dev/null +++ b/client-js/main/client/query-request.js @@ -0,0 +1,120 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {OrderBy} from "../proto/spine/client/query_pb"; +import {FilteringRequest} from "./filtering-request"; + +/** + * A request to retrieve entities of the given type. + * + * Allows to post the query data to the Spine backend and receive the entity states as `Promise`. + * + * A usage example: + * ``` + * const customers = + * client.select(Customer.class) + * .byId(westCoastCustomerIds()) + * .withMask("name", "address", "email") + * .where([Filters.eq("type", "permanent"), + * Filters.eq("discount_percent", 10), + * Filters.eq("company_size", Company.Size.SMALL)]) + * .orderBy("name", OrderBy.Direction.ASCENDING) + * .limit(20) + * .run(); // The returned type is `Promise`. + * ``` + * + * All of the called filtering methods are optional. If none of them are specified, all entities + * of type will be retrieved. + * + * @template the query target type + */ +export class QueryRequest extends FilteringRequest { + + /** + * @param {!Class} targetType the target type of entities + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(targetType, client, actorRequestFactory) { + super(targetType, client, actorRequestFactory) + } + + /** + * Sets the sorting order for the retrieved results. + * + * @param {!String} column the column to order by + * @param {!OrderBy.Direction} direction the ascending/descending direction + * @return {this} self for method chaining + */ + orderBy(column, direction) { + if (direction === OrderBy.Direction.ASCENDING) { + this._builder().orderAscendingBy(column); + } else { + this._builder().orderDescendingBy(column); + } + return this._self(); + } + + /** + * Sets the maximum number of returned entities. + * + * Can only be used in conjunction with the {@link #orderBy} condition. + * + * @param {number} count the max number of response entities + * @return {this} self for method chaining + */ + limit(count) { + this._builder().limit(count); + return this._self(); + } + + /** + * Builds a `Query` instance based on currently specified filters. + * + * @return {spine.client.Query} a `Query` instance + */ + query() { + return this._builder().build(); + } + + /** + * Runs the query and obtains the results. + * + * @return {Promise<[]>} the asynchronously resolved query results + */ + run() { + const query = this.query(); + return this._client.read(query); + } + + /** + * @inheritDoc + */ + _newBuilderFn() { + return requestFactory => requestFactory.query().select(this.targetType); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } +} diff --git a/client-js/main/client/subscribing-request.js b/client-js/main/client/subscribing-request.js new file mode 100644 index 000000000..eaf09457f --- /dev/null +++ b/client-js/main/client/subscribing-request.js @@ -0,0 +1,189 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {FilteringRequest} from "./filtering-request"; + +/** + * @typedef EventSubscriptionCallbacks + * + * A pair of callbacks that allow to add an event consumer to the subscription and to cancel it + * respectively. + * + * @property {consumerCallback} subscribe the callback which allows to setup an + * event consumer to use for the subscription + * @property {parameterlessCallback} unsubscribe the callback which allows to cancel the + * subscription + */ + +/** + * An abstract base for requests that subscribe to messages of a certain type. + * + * @abstract + * @template the target type of messages, for events the type is always `spine.core.Event` + */ +class SubscribingRequest extends FilteringRequest { + + /** + * Builds a `Topic` instance based on the currently specified filters. + * + * @return {spine.client.Topic} a `Topic` instance + */ + topic() { + return this._builder().build(); + } + + /** + * Posts a subscription request and returns the result as `Promise`. + * + * @return {Promise | EventSubscriptionObject>} + * the asynchronously resolved subscription object + */ + post() { + const topic = this.topic(); + return this._subscribe(topic); + } + + /** + * @inheritDoc + */ + _newBuilderFn() { + return requestFactory => requestFactory.topic().select(this.targetType); + } + + /** + * @abstract + * @return {Promise | EventSubscriptionObject>} + * + * @protected + */ + _subscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } +} + +/** + * A request to subscribe to updates of entity states of a certain type. + * + * Allows to obtain the `EntitySubscriptionObject` which exposes the entity changes in a form of + * callbacks which can be subscribed to. + * + * A usage example: + * ``` + * client.subscribeTo(Task.class) + * .where(Filters.eq("status", Task.Status.ACTIVE)) + * // Additional filtering can be done here. + * .post() + * .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + * itemAdded.subscribe(_addDisplayedTask); + * itemChanged.subscribe(_changeDisplayedTask); + * itemRemoved.subscribe(_removeDisplayedTask); + * }); + * ``` + * + * If the entity matched the subscription criteria at one point, but stopped to do so, the + * `itemRemoved` callback will be triggered for it. The callback will contain the last entity state + * that matched the subscription. + * + * Please note that the subscription object should be manually unsubscribed when it's no longer + * needed to receive the updates. This can be done with the help of `unsubscribe` callback. + * + * @template the target entity type + */ +export class SubscriptionRequest extends SubscribingRequest { + + /** + * @param {!Class} entityType the target entity type + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(entityType, client, actorRequestFactory) { + super(entityType, client, actorRequestFactory) + } + + /** + * @inheritDoc + * + * @return {Promise>} + */ + _subscribe(topic) { + return this._client.subscribe(topic); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } +} + +/** + * A request to subscribe to events of a certain type. + * + * Allows to obtain the `EventSubscriptionObject` which reflects the events that happened in the + * system and match the subscription criteria. + * + * A usage example: + * ``` + * client.subscribeToEvent(TaskCreated.class) + * .where([Filters.eq("task_priority", Task.Priority.HIGH), + * Filters.eq("context.past_message.actor_context.actor", userId)]) + * .post() + * .then(({eventEmitted, unsubscribe}) => { + * eventEmitted.subscribe(_logEvent); + * }); + * ``` + * + * The fields specified to the `where` filters should either be a part of the event message or + * have a `context.` prefix and address one of the fields of the `EventContext` type. + * + * The `eventEmitted` observable reflects all events that occurred in the system and match the + * subscription criteria, in a form of `spine.core.Event`. + * + * Please note that the subscription object should be manually unsubscribed when it's no longer + * needed to receive the updates. This can be done with the help of `unsubscribe` callback. + */ +export class EventSubscriptionRequest extends SubscribingRequest { + + /** + * @param {!Class} eventType the target event type + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(eventType, client, actorRequestFactory) { + super(eventType, client, actorRequestFactory) + } + + /** + * @inheritDoc + * + * @return {Promise} + */ + _subscribe(topic) { + return this._client.subscribeToEvents(topic); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } +} diff --git a/client-js/test/client/query-request-test.js b/client-js/test/client/query-request-test.js index a497ed18b..88d08057e 100644 --- a/client-js/test/client/query-request-test.js +++ b/client-js/test/client/query-request-test.js @@ -19,7 +19,7 @@ */ import assert from 'assert'; -import {QueryRequest} from "@lib/client/client-request"; +import {QueryRequest} from "@lib/client/query-request"; import {Duration} from "@lib/client/time-utils"; import {OrderBy} from '@proto/spine/client/query_pb'; import {filteringRequestTest, Given} from "./filtering-request-test"; diff --git a/client-js/test/client/subscription-request-test.js b/client-js/test/client/subscription-request-test.js index 4028fda57..5e1ae0cee 100644 --- a/client-js/test/client/subscription-request-test.js +++ b/client-js/test/client/subscription-request-test.js @@ -18,7 +18,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import {SubscriptionRequest} from "@lib/client/client-request"; +import {SubscriptionRequest} from "@lib/client/subscribing-request"; import {Duration} from "@lib/client/time-utils"; import {filteringRequestTest} from "./filtering-request-test"; From c4e2b8c0d5e179d8de11a30cb5611d1f647c7f13 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmin Date: Thu, 12 Dec 2019 13:52:08 +0200 Subject: [PATCH 66/70] Reword doc in client-js/main/client/client.js Co-Authored-By: Dmytro Dashenkov --- client-js/main/client/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index a00ed653a..ca63f6947 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -229,7 +229,7 @@ export class Client { * * @example * // Build a subscription topic for `UserTasks` domain entity, selecting the instances - * // whose task count is greater than 3. + * // with over 3 tasks. * newTopic().select(UserTasks) * .where(Filters.gt('tasksCount', 3)) * .build() From eb3e50b979d2f7ede33c6780bfb902ab4bb3af69 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmin Date: Thu, 12 Dec 2019 13:52:32 +0200 Subject: [PATCH 67/70] Fix doc formatting in firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java Co-Authored-By: Dmytro Dashenkov --- .../java/io/spine/web/firebase/subscription/UpdatePayload.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java index 2480130df..32d055141 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java @@ -145,7 +145,7 @@ NodeValue asNodeValue() { } /** - * Adds an {@code } key-value pair as a child of the given node. + * Adds an {@code ID -> message} key-value pair as a child of the given node. * *

If the message is {@link Empty}, adds a {@code null} child under the given key, * effectively deleting it from the database. From 78b19c9abbaa977bb482c0987e3b68ad84a66dc9 Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Thu, 12 Dec 2019 14:10:13 +0200 Subject: [PATCH 68/70] Split the `composite-client` into several JS files --- client-js/main/client/client-factory.js | 4 +- client-js/main/client/commanding-client.js | 92 ++++++++ client-js/main/client/composite-client.js | 225 -------------------- client-js/main/client/direct-client.js | 14 +- client-js/main/client/firebase-client.js | 14 +- client-js/main/client/querying-client.js | 72 +++++++ client-js/main/client/subscribing-client.js | 120 +++++++++++ 7 files changed, 298 insertions(+), 243 deletions(-) create mode 100644 client-js/main/client/commanding-client.js create mode 100644 client-js/main/client/querying-client.js create mode 100644 client-js/main/client/subscribing-client.js diff --git a/client-js/main/client/client-factory.js b/client-js/main/client/client-factory.js index 6034adf41..92baeee10 100644 --- a/client-js/main/client/client-factory.js +++ b/client-js/main/client/client-factory.js @@ -20,11 +20,11 @@ "use strict"; +import {ActorRequestFactory} from "./actor-request-factory"; import {Client} from './client'; +import {CommandingClient} from "./commanding-client"; import {HttpClient} from "./http-client"; import {HttpEndpoint} from "./http-endpoint"; -import {ActorRequestFactory} from "./actor-request-factory"; -import {CommandingClient} from "./composite-client"; /** * @typedef {Object} ClientOptions a type of object for initialization of Spine client diff --git a/client-js/main/client/commanding-client.js b/client-js/main/client/commanding-client.js new file mode 100644 index 000000000..36ea0259e --- /dev/null +++ b/client-js/main/client/commanding-client.js @@ -0,0 +1,92 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {Status} from "../proto/spine/core/response_pb"; +import {CommandRequest} from "./command-request"; +import {CommandHandlingError, CommandValidationError, SpineError} from "./errors"; +import ObjectToProto from "./object-to-proto"; +import {TypedMessage} from "./typed-message"; + +const _statusType = Status.typeUrl(); + +/** + * A client which posts commands. + * + * This class has a default implementation. Override it to change the behaviour. + */ +export class CommandingClient { + + constructor(endpoint, requestFactory) { + this._requestFactory = requestFactory; + this._endpoint = endpoint; + } + + /** + * Creates a new command request. + * + * @param {!Message} commandMessage the command to send to the server + * @param {!Client} client the client which initiated the request + * @return {CommandRequest} a new command request + */ + command(commandMessage, client) { + return new CommandRequest(commandMessage, client, this._requestFactory); + } + + /** + * Posts a given command to the Spine server. + * + * @param {!spine.core.Command} command a Command sent to Spine server + * @param {!AckCallback} onAck a command acknowledgement callback + */ + post(command, onAck) { + const cmd = TypedMessage.of(command); + this._endpoint.command(cmd) + .then(ack => this._onAck(ack, onAck)) + .catch(error => { + onAck.onError(new CommandHandlingError(error.message, error)); + }); + } + + _onAck(ack, onAck) { + const responseStatus = ack.status; + const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); + const responseStatusCase = responseStatusProto.getStatusCase(); + + switch (responseStatusCase) { + case Status.StatusCase.OK: + onAck.onOk(); + break; + case Status.StatusCase.ERROR: + const error = responseStatusProto.getError(); + const message = error.getMessage(); + onAck.onError(error.hasValidationError() + ? new CommandValidationError(message, error) + : new CommandHandlingError(message, error)); + break; + case Status.StatusCase.REJECTION: + onAck.onRejection(responseStatusProto.getRejection()); + break; + default: + onAck.onError( + new SpineError(`Unknown response status case ${responseStatusCase}`) + ); + } + } +} diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index 6e06ae963..4cf9d6218 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -20,15 +20,7 @@ "use strict"; -import {Message} from 'google-protobuf'; -import ObjectToProto from "./object-to-proto"; -import {CommandHandlingError, CommandValidationError, SpineError} from "./errors"; -import {Status} from '../proto/spine/core/response_pb'; import {Client} from "./client"; -import {TypedMessage} from "./typed-message"; -import {QueryRequest} from "./query-request"; -import {EventSubscriptionRequest, SubscriptionRequest} from "./subscribing-request"; -import {CommandRequest} from "./command-request"; /** * A {@link Client} that delegates requests to case-specific client implementations. @@ -134,220 +126,3 @@ export class CompositeClient extends Client { this._commanding.post(command, onAck); } } - -/** - * A client which performs entity state queries. - * - * @abstract - */ -export class QueryingClient { - - /** - * @param {!ActorRequestFactory} actorRequestFactory - * a request factory to build requests to Spine server - */ - constructor(actorRequestFactory) { - this._requestFactory = actorRequestFactory; - } - - /** - * Creates a new query request. - * - * @param {!Class} entityType the target entity type - * @param {!Client} client the client which initiated the request - * @return {QueryRequest} a new query request - */ - select(entityType, client) { - return new QueryRequest(entityType, client, this._requestFactory); - } - - /** - * Executes the given `Query` instance specifying the data to be retrieved from - * Spine server fulfilling a returned promise with an array of received objects. - * - * @param {!spine.client.Query} query a query instance to be executed - * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf - * messages of a given type or with an empty list if no entities matching given query - * were found; rejected with a `SpineError` if error occurs - * - * @template a Protobuf type of entities being the target of a query - */ - read(query) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Creates a new query factory instance which can be further used for the `Query` creation. - * - * @return {QueryFactory} - */ - newQuery() { - return this._requestFactory.query(); - } -} - -/** - * A client which manages entity state and event subscriptions. - * - * @abstract - */ -export class SubscribingClient { - - /** - * @param {!ActorRequestFactory} actorRequestFactory - * a request factory to build requests to Spine server - */ - constructor(actorRequestFactory) { - this._requestFactory = actorRequestFactory; - } - - /** - * Creates a new subscription request. - * - * @param {!Class} type the target entity type - * @param {!Client} client the client that initiated the request - * @return {SubscriptionRequest} a new subscription request - */ - subscribeTo(type, client) { - return new SubscriptionRequest(type, client, this._requestFactory); - } - - /** - * Subscribes to a given topic which targets an entity type. - * - * @param {!spine.client.Topic} topic a topic to subscribe to - * @return {Promise>} a subscription object - * - * @template a Protobuf type of entities being the target of a subscription - */ - subscribe(topic) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Creates a new event subscription request. - * - * @param {!Class} type the target event type - * @param {!Client} client the client that initiated the request - * @return {EventSubscriptionRequest} a new event subscription request - */ - subscribeToEvent(type, client) { - return new EventSubscriptionRequest(type, client, this._requestFactory); - } - - /** - * Subscribes to the given topic which targets an event type. - * - * @param {!spine.client.Topic} topic a topic to subscribe to - * @return {Promise} a subscription object - */ - subscribeToEvents(topic) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Returns a new topic factory instance which can be further used for the `Topic` creation. - * - * @return {TopicFactory} - */ - newTopic() { - return this._requestFactory.topic(); - } -} - -const SUBSCRIPTIONS_NOT_SUPPORTED = 'Subscriptions are not supported.'; - -/** - * A {@link SubscribingClient} which does not create subscriptions. - */ -export class NoOpSubscribingClient extends SubscribingClient { - - constructor(actorRequestFactory) { - super(actorRequestFactory) - } - - /** - * Always throws an error. - * - * @override - */ - subscribe(topic) { - throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); - } - - /** - * Always throws an error. - * - * @override - */ - subscribeToEvents(topic) { - throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); - } -} - -const _statusType = Status.typeUrl(); - -/** - * A client which posts commands. - * - * This class has a default implementation. Override it to change the behaviour. - */ -export class CommandingClient { - - constructor(endpoint, requestFactory) { - this._requestFactory = requestFactory; - this._endpoint = endpoint; - } - - /** - * Creates a new command request. - * - * @param {!Message} commandMessage the command to send to the server - * @param {!Client} client the client which initiated the request - * @return {CommandRequest} a new command request - */ - command(commandMessage, client) { - return new CommandRequest(commandMessage, client, this._requestFactory); - } - - /** - * Posts a given command to the Spine server. - * - * @param {!spine.core.Command} command a Command sent to Spine server - * @param {!AckCallback} onAck a command acknowledgement callback - */ - post(command, onAck) { - const cmd = TypedMessage.of(command); - this._endpoint.command(cmd) - .then(ack => this._onAck(ack, onAck)) - .catch(error => { - onAck.onError(new CommandHandlingError(error.message, error)); - }); - } - - _onAck(ack, onAck) { - const responseStatus = ack.status; - const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); - const responseStatusCase = responseStatusProto.getStatusCase(); - - switch (responseStatusCase) { - case Status.StatusCase.OK: - onAck.onOk(); - break; - case Status.StatusCase.ERROR: - const error = responseStatusProto.getError(); - const message = error.getMessage(); - onAck.onError(error.hasValidationError() - ? new CommandValidationError(message, error) - : new CommandHandlingError(message, error)); - break; - case Status.StatusCase.REJECTION: - onAck.onRejection(responseStatusProto.getRejection()); - break; - default: - onAck.onError( - new SpineError(`Unknown response status case ${responseStatusCase}`) - ); - } - } -} diff --git a/client-js/main/client/direct-client.js b/client-js/main/client/direct-client.js index c68b3e0ff..aa1e53b19 100644 --- a/client-js/main/client/direct-client.js +++ b/client-js/main/client/direct-client.js @@ -20,18 +20,16 @@ "use strict"; +import {AnyPacker} from "./any-packer"; +import {ActorRequestFactory} from './actor-request-factory'; import {AbstractClientFactory} from './client-factory'; +import {CompositeClient} from "./composite-client"; +import {CommandingClient} from "./commanding-client"; import {HttpClient} from './http-client'; import {HttpEndpoint} from './http-endpoint'; -import {ActorRequestFactory} from './actor-request-factory'; -import { - CommandingClient, - CompositeClient, - NoOpSubscribingClient, - QueryingClient -} from "./composite-client"; import KnownTypes from "./known-types"; -import {AnyPacker} from "./any-packer"; +import {QueryingClient} from "./querying-client"; +import {NoOpSubscribingClient} from "./subscribing-client"; import {Type} from "./typed-message"; import TypeParsers from "./parser/type-parsers"; diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 5c6b88475..532195ad5 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -25,19 +25,17 @@ import { Subscription as SubscriptionObject, SubscriptionId } from '../proto/spine/client/subscription_pb'; +import {ActorRequestFactory} from './actor-request-factory'; import {AbstractClientFactory} from './client-factory'; -import ObjectToProto from './object-to-proto'; +import {CommandingClient} from "./commanding-client"; +import {CompositeClient} from "./composite-client"; import {HttpClient} from './http-client'; import {HttpEndpoint} from './http-endpoint'; import {FirebaseDatabaseClient} from './firebase-database-client'; -import {ActorRequestFactory} from './actor-request-factory'; import {FirebaseSubscriptionService} from './firebase-subscription-service'; -import { - CommandingClient, - CompositeClient, - QueryingClient, - SubscribingClient -} from "./composite-client"; +import ObjectToProto from './object-to-proto'; +import {QueryingClient} from "./querying-client"; +import {SubscribingClient} from "./subscribing-client"; /** * An abstract base for subscription objects. diff --git a/client-js/main/client/querying-client.js b/client-js/main/client/querying-client.js new file mode 100644 index 000000000..220a35a1c --- /dev/null +++ b/client-js/main/client/querying-client.js @@ -0,0 +1,72 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {QueryRequest} from "./query-request"; + +/** + * A client which performs entity state queries. + * + * @abstract + */ +export class QueryingClient { + + /** + * @param {!ActorRequestFactory} actorRequestFactory + * a request factory to build requests to Spine server + */ + constructor(actorRequestFactory) { + this._requestFactory = actorRequestFactory; + } + + /** + * Creates a new query request. + * + * @param {!Class} entityType the target entity type + * @param {!Client} client the client which initiated the request + * @return {QueryRequest} a new query request + */ + select(entityType, client) { + return new QueryRequest(entityType, client, this._requestFactory); + } + + /** + * Executes the given `Query` instance specifying the data to be retrieved from + * Spine server fulfilling a returned promise with an array of received objects. + * + * @param {!spine.client.Query} query a query instance to be executed + * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf + * messages of a given type or with an empty list if no entities matching given query + * were found; rejected with a `SpineError` if error occurs + * + * @template a Protobuf type of entities being the target of a query + */ + read(query) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Creates a new query factory instance which can be further used for the `Query` creation. + * + * @return {QueryFactory} + */ + newQuery() { + return this._requestFactory.query(); + } +} diff --git a/client-js/main/client/subscribing-client.js b/client-js/main/client/subscribing-client.js new file mode 100644 index 000000000..766ba071d --- /dev/null +++ b/client-js/main/client/subscribing-client.js @@ -0,0 +1,120 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {EventSubscriptionRequest, SubscriptionRequest} from "./subscribing-request"; + +/** + * A client which manages entity state and event subscriptions. + * + * @abstract + */ +export class SubscribingClient { + + /** + * @param {!ActorRequestFactory} actorRequestFactory + * a request factory to build requests to Spine server + */ + constructor(actorRequestFactory) { + this._requestFactory = actorRequestFactory; + } + + /** + * Creates a new subscription request. + * + * @param {!Class} type the target entity type + * @param {!Client} client the client that initiated the request + * @return {SubscriptionRequest} a new subscription request + */ + subscribeTo(type, client) { + return new SubscriptionRequest(type, client, this._requestFactory); + } + + /** + * Subscribes to a given topic which targets an entity type. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise>} a subscription object + * + * @template a Protobuf type of entities being the target of a subscription + */ + subscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Creates a new event subscription request. + * + * @param {!Class} type the target event type + * @param {!Client} client the client that initiated the request + * @return {EventSubscriptionRequest} a new event subscription request + */ + subscribeToEvent(type, client) { + return new EventSubscriptionRequest(type, client, this._requestFactory); + } + + /** + * Subscribes to the given topic which targets an event type. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise} a subscription object + */ + subscribeToEvents(topic) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Returns a new topic factory instance which can be further used for the `Topic` creation. + * + * @return {TopicFactory} + */ + newTopic() { + return this._requestFactory.topic(); + } +} + +const SUBSCRIPTIONS_NOT_SUPPORTED = 'Subscriptions are not supported.'; + +/** + * A {@link SubscribingClient} which does not create subscriptions. + */ +export class NoOpSubscribingClient extends SubscribingClient { + + constructor(actorRequestFactory) { + super(actorRequestFactory) + } + + /** + * Always throws an error. + * + * @override + */ + subscribe(topic) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } + + /** + * Always throws an error. + * + * @override + */ + subscribeToEvents(topic) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } +} From 118d61f0528f7bacf7a22299d3956e3efba42f0a Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Thu, 12 Dec 2019 14:13:10 +0200 Subject: [PATCH 69/70] Optimize imports --- client-js/main/client/command-request.js | 6 +++--- client-js/main/client/direct-client.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client-js/main/client/command-request.js b/client-js/main/client/command-request.js index d6b255901..cd3bbf0d1 100644 --- a/client-js/main/client/command-request.js +++ b/client-js/main/client/command-request.js @@ -18,12 +18,12 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import {Filters} from "./actor-request-factory"; -import {MessageId, Origin} from "../proto/spine/core/diagnostics_pb"; -import {Type} from "./typed-message"; import {CommandId} from "../proto/spine/core/command_pb"; +import {MessageId, Origin} from "../proto/spine/core/diagnostics_pb"; +import {Filters} from "./actor-request-factory"; import {AnyPacker} from "./any-packer"; import {ClientRequest} from "./client-request"; +import {Type} from "./typed-message"; const NOOP_CALLBACK = () => {}; diff --git a/client-js/main/client/direct-client.js b/client-js/main/client/direct-client.js index aa1e53b19..739282aa0 100644 --- a/client-js/main/client/direct-client.js +++ b/client-js/main/client/direct-client.js @@ -20,11 +20,11 @@ "use strict"; -import {AnyPacker} from "./any-packer"; import {ActorRequestFactory} from './actor-request-factory'; +import {AnyPacker} from "./any-packer"; import {AbstractClientFactory} from './client-factory'; -import {CompositeClient} from "./composite-client"; import {CommandingClient} from "./commanding-client"; +import {CompositeClient} from "./composite-client"; import {HttpClient} from './http-client'; import {HttpEndpoint} from './http-endpoint'; import KnownTypes from "./known-types"; From 14d9c63e6dc21225f2b2d83c92a152a12fe710fc Mon Sep 17 00:00:00 2001 From: dmitrykuzmin Date: Thu, 12 Dec 2019 14:16:58 +0200 Subject: [PATCH 70/70] Reword the doc which looks disconnected after the classes are split --- client-js/main/client/commanding-client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client-js/main/client/commanding-client.js b/client-js/main/client/commanding-client.js index 36ea0259e..07f37adc1 100644 --- a/client-js/main/client/commanding-client.js +++ b/client-js/main/client/commanding-client.js @@ -29,7 +29,8 @@ const _statusType = Status.typeUrl(); /** * A client which posts commands. * - * This class has a default implementation. Override it to change the behaviour. + * This class has a default implementation but is intended to be overridden as necessary if it's + * required to change the behavior. */ export class CommandingClient {