Source: client/client.js

  1. /*
  2. * Copyright 2023, TeamDev. All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Redistribution and use in source and/or binary forms, with or without
  11. * modification, must retain the above copyright notice and the following
  12. * disclaimer.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  15. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  16. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  17. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  18. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  19. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  20. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  21. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  22. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. "use strict";
  27. import {Message} from 'google-protobuf';
  28. import {Observable} from 'rxjs';
  29. /**
  30. * The callback that doesn't accept any parameters.
  31. * @callback parameterlessCallback
  32. */
  33. /**
  34. * The callback that accepts single parameter.
  35. *
  36. * @callback consumerCallback
  37. * @param {T} the value the callback function accepts
  38. *
  39. * @template <T> the type of value passed to the callback
  40. */
  41. /**
  42. * The callback that accepts a single `Event` as a parameter.
  43. *
  44. * @callback eventConsumer
  45. *
  46. * @param {spine.core.Event} the event that is accepted by the callback
  47. */
  48. /**
  49. * @typedef {Object} EntitySubscriptionObject
  50. *
  51. * An object representing the result of a subscription to entity state changes.
  52. *
  53. * @property {!Observable<T>} itemAdded emits new items matching the subscription topic
  54. * @property {!Observable<T>} itemChanged emits updated items matching the subscription topic
  55. * @property {!Observable<T>} itemRemoved emits removed items matching the subscription topic
  56. * @property {!parameterlessCallback} unsubscribe a method to be called to cancel the subscription,
  57. * stopping the subscribers from receiving new
  58. * entities
  59. *
  60. * @template <T> a type of the subscription target entities
  61. */
  62. /**
  63. * @typedef {Object} EventSubscriptionObject
  64. *
  65. * An object which represents the result of a subscription to events of a certain type.
  66. *
  67. * @property <!Observable<spine.core.Event>> eventEmitted emits new items when the new events
  68. * matching the subscription topic occur in
  69. * the system
  70. * @property {!parameterlessCallback} unsubscribe a method to be called to cancel the subscription,
  71. * stopping the subscribers from receiving new
  72. * entities
  73. */
  74. /**
  75. * @typedef AckCallback
  76. *
  77. * Represents a command acknowledgement callback.
  78. *
  79. * @property {!parameterlessCallback} onOk
  80. * the callback to run when the command is handled properly
  81. * @property {!consumerCallback<Error>} onError
  82. * the callback to run when the command cannot be handled due to a technical error
  83. * @property {!consumerCallback<Message>} onImmediateRejection
  84. * the callback to run when the command is denied execution due to a business rejection
  85. */
  86. /**
  87. * An abstract client for Spine application backend. This is a single channel for client-server
  88. * communication in a Spine-based browser application.
  89. *
  90. * @abstract
  91. */
  92. export class Client {
  93. /**
  94. * Creates a query request that allows to configure and post a new query.
  95. *
  96. * @param {!Class<Message>} entityType a Protobuf type of the query target entities
  97. * @return {QueryRequest} the builder to construct and post a new query
  98. */
  99. select(entityType) {
  100. throw new Error('Not implemented in abstract base.');
  101. }
  102. /**
  103. * Executes the given `Query` instance specifying the data to be retrieved from
  104. * Spine server fulfilling a returned promise with an array of received objects.
  105. *
  106. * @param {!spine.client.Query} query a query instance to be executed
  107. * @return {Promise<Message[]>} a promise to be fulfilled with a list of Protobuf
  108. * messages of a given type or with an empty list if no entities matching given query
  109. * were found; rejected with a `SpineError` if error occurs
  110. *
  111. * @template <T> a Protobuf type of entities being the target of a query
  112. */
  113. read(query) {
  114. throw new Error('Not implemented in abstract base.');
  115. }
  116. /**
  117. * Executes the given `Query` instance specifying the data to be retrieved from
  118. * Spine server fulfilling a returned promise with an array of received objects.
  119. *
  120. * @param {!spine.client.Query} query a query instance to be executed
  121. * @return {Promise<Message[]>} a promise to be fulfilled with a list of Protobuf
  122. * messages of a given type or with an empty list if no entities matching given query
  123. * were found; rejected with a `SpineError` if error occurs;
  124. *
  125. * @template <T> a Protobuf type of entities being the target of a query
  126. *
  127. * @deprecated Please use {@link Client#read()} instead
  128. */
  129. execute(query) {
  130. return this.read(query);
  131. }
  132. /**
  133. * Creates a new {@link QueryFactory} for creating `Query` instances specifying
  134. * the data to be retrieved from Spine server.
  135. *
  136. * @example
  137. * // Build a query for `Task` domain entity, specifying particular IDs.
  138. * newQuery().select(Task)
  139. * .byIds([taskId1, taskId2])
  140. * .build()
  141. *
  142. * @example
  143. * // Build a query for `Task` domain entity, selecting the instances which assigned to the
  144. * // particular user.
  145. * newQuery().select(Task)
  146. * .where([Filters.eq('assignee', userId)])
  147. * .build()
  148. *
  149. * To execute the resulting `Query` instance pass it to the {@link Client#execute()}.
  150. *
  151. * Alternatively, the `QueryRequest` API can be used. See {@link Client#select()}.
  152. *
  153. * @return {QueryFactory} a factory for creating queries to the Spine server
  154. *
  155. * @see QueryFactory
  156. * @see QueryBuilder
  157. * @see AbstractTargetBuilder
  158. */
  159. newQuery() {
  160. throw new Error('Not implemented in abstract base.');
  161. }
  162. /**
  163. * Creates a subscription request that allows to configure and post a new entity subscription.
  164. *
  165. * @param {!Class<Message>} entityType a Protobuf type of the target entities
  166. * @return {SubscriptionRequest} the builder for the new entity subscription
  167. */
  168. subscribeTo(entityType) {
  169. throw new Error('Not implemented in abstract base.');
  170. }
  171. /**
  172. * Immediately cancels all active subscriptions.
  173. *
  174. * This endpoint is handy to use when an end-user chooses to end her session
  175. * with the web app. E.g. all subscriptions should be cancelled upon user sign-out.
  176. */
  177. cancelAllSubscriptions() {
  178. throw new Error('Not implemented in abstract base.');
  179. }
  180. /**
  181. * Subscribes to the given `Topic` instance.
  182. *
  183. * The topic should have an entity type as target. Use {@link #subscribeToEvents} to subscribe to
  184. * the topic that targets events.
  185. *
  186. * @param {!spine.client.Topic} topic a topic to subscribe to
  187. * @return {Promise<EntitySubscriptionObject<Message>>}
  188. * the subscription object which exposes entity changes via its callbacks
  189. *
  190. * @template <T> a Protobuf type of entities being the target of a subscription
  191. */
  192. subscribe(topic) {
  193. throw new Error('Not implemented in abstract base.');
  194. }
  195. /**
  196. * Creates an event subscription request that allows to configure and post a new event
  197. * subscription.
  198. *
  199. * @param {!Class<Message>} eventType a Protobuf type of the target events
  200. * @return {EventSubscriptionRequest} the builder for the new event subscription
  201. */
  202. subscribeToEvent(eventType) {
  203. throw new Error('Not implemented in abstract base.');
  204. }
  205. /**
  206. * Subscribes to the given `Topic` instance.
  207. *
  208. * The given topic should target an event type. To perform an entity subscription, use
  209. * {@link #subscribe}.
  210. *
  211. * @param {!spine.client.Topic} topic a topic to subscribe to
  212. *
  213. * @return {Promise<EventSubscriptionObject>}
  214. */
  215. subscribeToEvents(topic) {
  216. throw new Error('Not implemented in abstract base.');
  217. }
  218. /**
  219. * Creates a new {@link TopicFactory} for building subscription topics specifying
  220. * the state changes to be observed from Spine server.
  221. *
  222. * @example
  223. * // Build a subscription topic for `UserTasks` domain entity.
  224. * newTopic().select(Task)
  225. * .build()
  226. *
  227. * @example
  228. * // Build a subscription topic for `UserTasks` domain entity, selecting the instances
  229. * // with over 3 tasks.
  230. * newTopic().select(UserTasks)
  231. * .where(Filters.gt('tasksCount', 3))
  232. * .build()
  233. *
  234. * To turn the resulting `Topic` instance into a subscription pass it
  235. * to the {@link Client#subscribe()}.
  236. *
  237. * Alternatively, the `SubscriptionRequest` API can be used. See {@link Client#subscribeTo()},
  238. * {@link Client#subscribeToEvents()}.
  239. *
  240. * @return {TopicFactory} a factory for creating subscription topics to the Spine server
  241. *
  242. * @see TopicFactory
  243. * @see TopicBuilder
  244. * @see AbstractTargetBuilder
  245. */
  246. newTopic() {
  247. throw new Error('Not implemented in abstract base.');
  248. }
  249. /**
  250. * Creates a new command request which allows to post a command to the Spine server and
  251. * configures the command handling callbacks.
  252. *
  253. * @param {!Message} commandMessage a command to post to the server
  254. * @return {CommandRequest} a new command request
  255. */
  256. command(commandMessage) {
  257. throw new Error('Not implemented in abstract base.');
  258. }
  259. /**
  260. * Posts a given command to the Spine server.
  261. *
  262. * @param {!spine.core.Command} command a Command sent to Spine server
  263. * @param {!AckCallback} onAck a command acknowledgement callback
  264. */
  265. post(command, onAck) {
  266. throw new Error('Not implemented in abstract base.');
  267. }
  268. /**
  269. * Sends the provided command to the server.
  270. *
  271. * After sending the command to the server the following scenarios are possible:
  272. *
  273. * - the `acknowledgedCallback` is called if the command is acknowledged for further processing
  274. * - the `errorCallback` is called if sending of the command failed
  275. *
  276. * Invocation of the `acknowledgedCallback` and the `errorCallback` are mutually exclusive.
  277. *
  278. * If the command sending fails, the respective error is passed to the `errorCallback`. This error
  279. * is always the type of `CommandHandlingError`. Its cause can be retrieved by `getCause()` method
  280. * and can be represented with the following types of errors:
  281. *
  282. * - `ConnectionError` – if the connection error occurs;
  283. * - `ClientError` – if the server responds with `4xx` HTTP status code;
  284. * - `ServerError` – if the server responds with `5xx` HTTP status code;
  285. * - `spine.base.Error` – if the command message can't be processed by the server;
  286. * - `SpineError` – if parsing of the response fails;
  287. *
  288. * If the command sending fails due to a command validation error, an error passed to the
  289. * `errorCallback` is the type of `CommandValidationError` (inherited from
  290. * `CommandHandlingError`). The validation error can be retrieved by `validationError()` method.
  291. *
  292. * The occurrence of an error does not guarantee that the command is not accepted by the server
  293. * for further processing. To verify this, call the error `assuresCommandNeglected()` method.
  294. *
  295. * @param {!Message} commandMessage a Protobuf message representing the command
  296. * @param {!parameterlessCallback} acknowledgedCallback
  297. * a no-argument callback invoked if the command is acknowledged
  298. * @param {?consumerCallback<CommandHandlingError>} errorCallback
  299. * a callback receiving the errors executed if an error occurred when sending command
  300. * @param {?consumerCallback<Rejection>} rejectionCallback
  301. * a callback executed if the command is denied processing due to a business rejection
  302. * @see CommandHandlingError
  303. * @see CommandValidationError
  304. *
  305. * @deprecated Please use {@link Client#command()}
  306. */
  307. sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) {
  308. this.command(commandMessage)
  309. .onOk(acknowledgedCallback)
  310. .onError(errorCallback)
  311. .onImmediateRejection(rejectionCallback)
  312. .post();
  313. }
  314. }