npm package: @hexagramio/saga-ts
Description: Saga SDK
Author: Hexagram
Latest version: 0.9.621-0

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**

- [Saga Typescript SDK](#saga-typescript-sdk)
  - [Install](#install)
  - [Description](#description)
  - [Fundamental Types](#fundamental-types)
    - [HTTPCommand](#httpcommand)
    - [Example Custom Command](#example-custom-command)
    - [SocketCommand](#socketcommand)
  - [Quickstart](#quickstart)
    - [Saga API URL](#saga-api-url)
    - [Register User](#register-user)
    - [Login User and Add User Property](#login-user-and-add-user-property)
    - [Paginate Listing, example using Listing](#paginate-listing-example-using-listing)
    - [Handle HTTP Error](#handle-http-error)
    - [Inspect Missing Fields in a failed HTTP Call](#inspect-missing-fields-in-a-failed-http-call)
    - [Subscribe to Socket Bot Property Changes](#subscribe-to-socket-bot-property-changes)
    - [Handle Socket Emit Comand Errors](#handle-socket-emit-comand-errors)
    - [Handle Socket Connection Error](#handle-socket-connection-error)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

# Saga Typescript SDK

## Install

```shell
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](./modules.md)

## 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
```ts
/**
 * 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.

```ts
/**
 * 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
```ts
/**
 * 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 ;)

### Saga API URL

Can be either string or URL.

```ts

let url_string = "https://saga-api.com"
let let_object = new URL("https://saga-api.com")

```

### Register User

```ts
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

Logging as a user and using the users access token for further commands

```ts
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

Pagination in saga using nextCommand and prevCommand.

```ts
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

How to handle the various saga errors

```ts
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 a failed HTTP Call

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

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

  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

```ts
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

```ts
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

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