@activimetrics/socket
Absinthe Socket
Features
- Immutable functional API
All received and returned objects with the exception of AbsintheSocket
instances (there are plans to make this immutable too) are treated in an
immutable way. Objects have no methods and instead we provide independant
stateless functions to interact with them.
- Lazy connect / join
If provided phoenix socket instance is not connected, then instead of
connecting at creation time, connection will be established on the next
invocation of send.
- Handle pending operations on connection lost
Pending mutations will be aborted, queries will be resent, and subscriptions
reestablished.
- Cancellable requests
Calling cancel removes given notifier from absintheSocket instance
and sends a Cancel event to all its observers and unsubscribes in case it
holds a subscription request.
- Operations deduplication
If an already sent request is given to send, then instead of sending
it again, the notifier associated with it will be returned.
- Observer support of recoverable errors
Since connection lost is handled, then two events needs to exist to represent
this fact: Error (recoverable), Abort (unrecoverable).
- Multiple observers per request
Calling send returns a notifier which allows attaching any number of
observers that will be notified when result arrives.
- Observer interaction depending on operation type
For the case of subscriptions, Start event is dispatched when the
subscription is established, while for the other types
(queries and mutations), when the request is sent.
Installation
$ npm install --save phoenix @activimetrics/socket
$ yarn add phoenix @activimetrics/socket
Types
type RequestStatus = "canceled" | "canceling" | "pending" | "sent" | "sending";
// from @jumpn/utils-graphql
type GqlRequest<Variables: void | Object = void> = {
operation: string,
variables?: Variables
};
// from @jumpn/utils-graphql
type GqlResponse<Data> = {
data?: Data,
errors?: Array<GqlError>
};
// from @jumpn/utils-graphql
type GqlOperationType = "mutation" | "query" | "subscription";
type Observer<Result, Variables: void | Object = void> = {|
onAbort?: (error: Error) => any,
onCancel?: () => any,
onError?: (error: Error) => any,
onStart?: (notifier: Notifier<Result, Variables>) => any,
onResult?: (result: Result) => any
|};
type Notifier<Result, Variables: void | Object = void> = {|
activeObservers: $ReadOnlyArray<Observer<Result, Variables>>,
canceledObservers: $ReadOnlyArray<Observer<Result, Variables>>,
isActive: boolean,
operationType: GqlOperationType,
request: GqlRequest<Variables>,
requestStatus: RequestStatus,
subscriptionId?: string
|};
type AbsintheSocket = {|
channel: Channel,
channelJoinCreated: boolean,
notifiers: Array<Notifier<any>>,
phoenixSocket: PhoenixSocket
|};
API
cancel
Cancels a notifier sending a Cancel event to all its observers and
unsubscribing in case it holds a subscription request
Parameters
absintheSocket
AbsintheSocketnotifier
Notifier<any, any>
Examples
import * as withAbsintheSocket from "@absinthe/socket";
withAbsintheSocket.cancel(absintheSocket, notifier);
Returns AbsintheSocket
create
Creates an Absinthe Socket using the given Phoenix Socket instance
Parameters
phoenixSocket
PhoenixSocket
Examples
import * as withAbsintheSocket from "@absinthe/socket";
import {Socket as PhoenixSocket} from "phoenix";
const absintheSocket = withAbsintheSocket.create(
new PhoenixSocket("ws://localhost:4000/socket")
);
Returns AbsintheSocket
observe
Observes given notifier using the provided observer
Parameters
absintheSocket
AbsintheSocketnotifier
Notifier<Result, Variables>observer
Observer<Result, Variables>
Examples
import * as withAbsintheSocket from "@absinthe/socket"
const logEvent = eventName => (...args) => console.log(eventName, ...args);
const updatedNotifier = withAbsintheSocket.observe(absintheSocket, notifier, {
onAbort: logEvent("abort"),
onError: logEvent("error"),
onStart: logEvent("open"),
onResult: logEvent("result")
});
send
Sends given request and returns an object (notifier) to track its progress
(see observe function)
Parameters
absintheSocket
AbsintheSocketrequest
GqlRequest<Variables>
Examples
import * as withAbsintheSocket from "@absinthe/socket";
const operation = `
subscription userSubscription($userId: ID!) {
user(userId: $userId) {
id
name
}
}
`;
const notifier = withAbsintheSocket.send(absintheSocket, {
operation,
variables: {userId: 10}
});
Returns Notifier<Result, Variables>
toObservable
Creates an Observable that will follow the given notifier
Parameters
absintheSocket
AbsintheSocketnotifier
Notifier<Result, Variables>options
Object? (optional, default {}
)
options.unsubscribe
function (): undefined?options.onError
function (error: Error): undefined?options.onStart
function (notifier: Notifier<Result, Variables>): undefined?options.handlers
...any
Examples
import * as withAbsintheSocket from "@absinthe/socket";
const unobserveOrCancelIfNeeded = (absintheSocket, notifier, observer) => {
if (notifier && observer) {
withAbsintheSocket.unobserveOrCancel(absintheSocket, notifier, observer);
}
};
const logEvent = eventName => (...args) => console.log(eventName, ...args);
const observable = withAbsintheSocket.toObservable(absintheSocket, notifier, {
onError: logEvent("error"),
onStart: logEvent("open"),
unsubscribe: unobserveOrCancelIfNeeded
});
Returns Observable
unobserve
Detaches observer from notifier
Parameters
absintheSocket
AbsintheSocketnotifier
Notifier<any, any>observer
Observer<any, any>
Examples
import * as withAbsintheSocket from "@absinthe/socket";
withAbsintheSocket.unobserve(absintheSocket, notifier, observer);
Returns AbsintheSocket
unobserveOrCancel
Cancels notifier if there are no more observers apart from the one given, or
detaches given observer from notifier otherwise
Parameters
absintheSocket
AbsintheSocketnotifier
Notifier<Result, Variables>observer
Observer<Result, Variables>
Examples
import * as withAbsintheSocket from "@absinthe/socket";
withAbsintheSocket.unobserve(absintheSocket, notifier, observer);
License
MIT :copyright: Jumpn Limited.