Saga Documentation 0.9.434-4

Saga Typescript SDK

Install

npm install @hexagramio/saga-ts

Description

Typescript SDK to interface with a Saga API. The implementation is inspired by the Command Design Pattern. A command is send to a Reveiver. A command encapsulates all the data needed to call the API except the domain name. Currently there two kinds of command receivers, HTTP and Socket. Almost all commands except log in and some user creation methods require a accessToken. If a command fails an exception is being thrown that mirrors SAGA API errors.

SDK Documentation

Fundamental Types

Both HTTPCommand and SocketCommand below show the entire complexity of both types. Easy to adopt for specialized and new commands or to use for HTTP Scripts.

HTTPCommand

/**
 * Interfaces to represent the various mutations of a HTTP Command profile.
 */
export interface HTTPGetCommand<T> {
  method: "GET" ,
  path: string,
  params?: URLSearchParams,
  accessToken?: string
}
export interface HTTPPostPutCommand<T> {
  method: "PUT" | "POST",
  path: string,
  data: any ,
  accessToken?: string
}

export interface HTTPDeleteCommand<T> {
  method: "DELETE",
  path: string,
  params?: URLSearchParams,
  accessToken?: string
}

export type HTTPCommand<T> = HTTPGetCommand<T> | HTTPPostPutCommand<T> | HTTPDeleteCommand<T>

Example Custom Command

Sends HTTP post to requests/slack with the assembled body.

/**
 * Get a bot command
 * @param id the id of the bot
 * @param accessToken the required accessToken
 * @group HTTP Commands
 */
export const PostSlackMessageCommand = (accessToken: string, slack_id: string, slack_channel: string, message: string):HTTPCommand<{ok:boolean}> =>{
  return {
    method:"POST",
    path:`requests/slack`,
    accessToken,
    data: {slack_id,slack_channel,message}
  }
}

SocketCommand

/**
 * A socket join command
 */
export interface SocketJoinCommand {
    method: "/users" | "/bots" | "/globals" |
    "/jobs/runnable_calls" | "/jobs/versions" |
    "/scripts/runnable_calls" | "/scripts/versions" |
    "/system" | "/npm_packages"
    id: string, 
    join: boolean,
  }

Quickstart

For the unhurried among us ;)

Register User

import {
  RegisterUserCommand,
  sendHTTPCommand} from "saga-ts";

const baseURL=new URL("https://saga-api.com");
const user = await sendHTTPCommand(baseUrl,RegisterUserCommand({username:`foo`,password:`bar`}))

Login User and Add User Property

import {
  AddUserPropertyCommand,
  LoginUserCommand,
  sendHTTPCommand} from "saga-ts";

const baseURL=new URL("https://saga-api.com");
const user = await sendHTTPCommand(baseUrl,LoginUserCommand({username:`foo`,password:`bar`}))
await sendHTTPCommand(baseUrl,AddUserPropertyCommand(user.accessToken,{parent_id:user._id,name:"hello",value:"world"}))

Paginate Listing, example using Listing

import {
  ListBotsCommand,
  sendHTTPCommand} from "saga-ts";

const baseURL=new URL("https://saga-api.com");
const accessToken="123213132";//from cache
const botsListing = await sendHTTPCommand(baseUrl,ListBotsCommand(accessToken))

//do we have more? nextCommand contains all the data needed for the call
if (botsListing.nextCommand) {
  const botsNext = await sendHTTPCommand(baseUrl, botsListing.nextCommand);
}

//OR

//do we have some previous? prevcommand contains all the data needed for the call
if (botsListing.prevCommand) {
  const botsPrev = await sendHTTPCommand(baseUrl, botsListing.prevCommand);

Handle HTTP Error

import {
  ListUserCommand,
  RegisterUserCommand,
  sendHTTPCommand} from "saga-ts";

const baseURL=new URL("https://saga-api.com");
const accessToken="89012y417114211";//from cache
try{
  await sendHTTPCommand(baseUrl,CreateBot(user.accessToken,{name:"ALREADY TAKEN"}))  
} catch(e){
  if(e.statusCode===422){
    //e.errors contains [fieldName:string]: message
    //handle the specific error messages
  }
  //...
}

Inspect Missing Fields in failed HTTP Call

import {
  ListUserCommand,
  RegisterUserCommand,
  sendHTTPCommand} from "saga-ts";

const baseURL=new URL("https://saga-api.com");
const user = await sendHTTPCommand(baseUrl,RegisterUserCommand({username:`foo`,password:`bar`}))
try{
  await sendHTTPCommand(baseUrl,ListUserCommand(user.accessToken,{parent_id:user._id,name:"hello",value:"world"}))  
} catch(e){
  //handle 401 and 403 for
}

Subscribe to Socket Bot Property Changes

import {
  Authentication,
  SocketSession} from "saga-ts";

const baseURL=new URL("https://saga-api.com");
const socketSession = new SocketSession(
  baseUrl,
  accessToken, //assumed existing
  (error)=>console.error //disconnect errors need to be handled asynchronously
)
socketSession.on("/properties", (property)=>{
  //do something with propterties
  if(property.name="location"){
    updateMapLocation(property);
  }//....
})
//join bot command
await socketSession.emitCommand(JoinBotCommand("ID OF BOT TO JOIN"))

Handle Socket Emit Comand Errors

import {
  Authentication,
  SocketSession} from "saga-ts";

const baseURL=new URL("https://saga-api.com");
const socketSession = new SocketSession(
  baseUrl,
  accessToken, //assumed existing
  (error)=>console.error //disconnect errors need to be handled asynchronously
)

try{
  await socketSession.emitCommand(JoinBotCommand("Not access to id"))  
} catch(e){
  //deal with API Error like 401,403,404 and 422 depending on the comand
}

Handle Socket Connection Error

import {SocketSession} from "saga-ts";
const baseURL=new URL("https://saga-api.com");
new SocketSession(
    baseUrl,
    "bad token",
    (err)=>{
      //handleError
    }
)