0.2.16 • Published 12 months ago

@pulsefunctions/client v0.2.16

Weekly downloads
-
License
-
Repository
-
Last release
12 months ago

Pulse

Initializing

npm add @instantlycreative/pulse

Importing in browser

import { PulseClient } from '@instantlycreative/pulse'

const client = new PulseClient()

Importing in node

import { PulseClient } from '@instantlycreative/pulse'
import crossFetch from 'cross-fetch'
import WebSocket from 'ws'

const client = new PulseClient({
  fetch: crossFetch,
  ws: WebSocket,
})

Usage

Before running any command, you must login:

const { token } = await client.login({ email: 'user@example.com', password: 'mypasswd' })
if (token) {
  fs.writeFileSync('~/.pulse.token', token)
}

Alternatively, you can just pass a saved token.

const token = fs.readFileSync('~/.pulse.token', 'utf8')
await client.login({ token })

API

Runtime:

  • docker
  • node14.x
  • node16.x
  • node18.x
  • node20.x
  • python3.8
  • python3.9
  • python3.10
  • python3.11
  • python3.12
  • ruby3.2
  • ruby3.3

login

async login({
  email: string,
  password: string
}): Promise<{
  success: boolean;
  token?: string;
  error?: string
}>

async login({
  token: string
}): Promise<{
  success: boolean;
  token?: string;
  error?: string
}>

get

async get({
  pulseId
}: {
  pulseId: string
}): Promise<{
  success: boolean
  team?: Team
  pulse?: Pulse
  statuses?: Status[]
  error?: string
}>
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: Date
  updatedAt: Date
}
interface Pulse {
  id: string
  name: string
  teamId: string
  controllerId: string
  createdAt: Date
  updatedAt: Date
  deletedAt: Date | null
}
interface Status {
  id: string
  pulseId: string
  sequence: number
  status: PulseStatus
  isPaused: boolean
  data: any
  createdAt: Date
  updatedAt: Date
}
export enum PulseStatus {
  CREATED = 'created',
  INITIALIZING = 'initializing',
  INITIALIZED = 'initialized',
  UPDATING = 'updating',
  UPDATED = 'updated',
  WARMING = 'warming',
  READY = 'ready',
  EXECUTING = 'executing',
  STOPPING = 'stopping',
  STOPPED = 'stopped',
  DELETED = 'deleted',
}

create

async create({
  pulseId,      // if not provided, a random id will be generated
  name,
  teamId,
  runtime,
}: {
  pulseId?: string
  name?: string
  teamId: string
  runtime: string
}): Promise<{
  success: boolean;
  pulseId?: string;
  error?: string
}>

fork

async fork({
  pulseId,
  newPulseId,   // if not provided, a random id will be generated
}: {
  pulseId: string
  newPulseId?: string
}): Promise<{
  success: boolean;
  pulseId?: string
  error?: string;
}>

update

async update({
  pulseId,
  name,
  buildOptions, // if runtime supports it, i.e, docker build step args
  execOptions,  // runtime exec options, e.g., "--param" in "node --param /app/script.js"
  execPath,     // entry point, e.g., "/script.js" in "node /script.js", or "/Dockerfile" for "docker" runtime.
  files,        // files to be uploaded to the app directory. cannot be used with "zipFile"
  zipFile,      // zip with the app. will be extracted to /app/ directory. cannot be used with "files"
  initFile,     // initialization script
  envFile,      // .env file
  stdinFile,    // a file that will be used as stdin for the app
  vcpu,         // number of virtual CPUs
  memory,       // memory in MB
  balloon,      // initial balloon size in MB
  autoPause,    // pause the app after it's done executing the user app
  autoShutdown, // shutdown the app after it's done executing the user app
  autoDelete,   // delete the app on shutdown
}: {
  pulseId: string
  name?: string
  buildOptions?: string
  execOptions?: string
  execPath: string
  files?: { [path: string]: string | Buffer | File }
  zipFile?: string | Buffer | File
  initFile?: string | Buffer | File
  envFile?: string | Buffer | File
  stdinFile?: string | Buffer | File
  vcpu?: string
  memory?: string
  balloon?: string
  autoPause?: boolean
  autoShutdown?: boolean
  autoDelete?: boolean
}): Promise<{
  success: boolean;
  accessToken?: string;
  error?: string
}>
export interface File {
  source?: string // path on disk
  content?: string | Buffer
  mode?: number // like 0o755
}

Use accessToken to stream the output using client.stream().

Notes on build and exec options:

  • buildOptions is only used for the docker runtime.
  • execOptions is used for all runtimes.
const { accessToken } = await client.update({
  pulseId,
  execPath: '/script.js',
  files: {
    '/script.js': 'console.log("Hello, world!")',
  },
  execOptions: '--max-old-space-size=4096', // node param to set memory to 4GB
})

This is will result in executing "node --max-old-space-size=4096 /script.js".

const { accessToken } = await client.update({
  pulseId,
  execPath: '/script.js',
  files: {
    '/script.js': 'console.log("Hello, world!")',
  },
  buildOptions: '--build-arg=HELLO=WORLD --build-arg=ANOTHER=ARG', // docker build arg
})

This is will result in adding "--build-arg=HELLO=WORLD" and "--build-arg=ANOTHER=ARG" params to the "docker build" command.

Notes on files:

  • files is an object where the key is the path and the value is the content. The path must start with a slash, but the resulting file will be created in the app directory, which is /run/app.
  • zipFile is a zip file that will be extracted to the app directory (/run/app).
  • initFile is a script that will be executed before the user script.
  • envFile is a .env file that will be used to set environment variables.
  • stdinFile is a file that will be used as stdin for the app.

When any file is a string, it will be treated as utf-8 encoded content:

const { accessToken } = await client.update({
  pulseId,
  execPath: '/script.js',
  files: {
    '/script.js': 'console.log("Hello, world!")',
  },
})

When any file is a buffer, it will be treated as binary content:

import fs from 'fs'

const { accessToken } = await client.update({
  pulseId,
  execPath: '/script.js',
  zipFile: fs.readFileSync('/some/path/to/local/script.zip'), // returns Buffer
})

Otherwise, you can use the File interface:

Note:

  • "source" is the path to the file on disk.
  • "content" is the content of the file.
  • "mode" is the file mode.
  • "source" and "content" are mutually exclusive.
const { accessToken } = await client.update({
  pulseId,
  execPath: '/script.js',
  files: {
    '/script.js': {
      source: '/some/path/to/local/script.js',
      mode: 0o755,
    },
    '/other/script.js': {
      content: 'console.log("Hello, world!")',
      mode: 0o644,
    },
    '/another/script.js': {
      content: Buffer.from('console.log("Hello, world!")'),
      mode: 0o644,
    },
  },
})

Optional initFile is a bash script that will be executed before the user script:

const { accessToken } = await client.update({
  pulseId,
  execPath: '/script.js',
  zipFile: fs.readFileSync('/some/path/to/local/script.zip'),
  initFile: `#!/bin/bash
echo "Hello, world!"
echo "Goodbye, world!"
`,
})

Optional envFile is a .env file that will be used to set environment variables:

const { accessToken } = await client.update({
  pulseId,
  execPath: '/script.js',
  zipFile: fs.readFileSync('/some/path/to/local/script.zip'),
  envFile: `VAR1="value 1"
VAR2="value 2"
ANOTHER_VAR="another value"
# ... you get the idea
`,
})

Optional stdinFile is a file that will be used as stdin for the app:

const { accessToken } = await client.update({
  pulseId,
  execPath: '/script.js',
  zipFile: fs.readFileSync('/some/path/to/local/script.zip'),
  stdinFile: fs.readFileSync('/some/path/to/local/stdin.txt'),
})

If the app reads STDIN, it will read from that file.

warmUp

async warmUp({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>

run

async run({ pulseId }: { pulseId: string }): Promise<{ success: boolean; accessToken?: string; error?: string }>

pause

async pause({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>

resume

async resume({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>

shutdown

async shutdown({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>

delete

async delete({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>

balloon

async balloon({
  pulseId,
  balloon, // change balloon size to this (in MB)
}: {
  pulseId: string
  balloon: number
}): Promise<{
  success: boolean;
  error?: string
}>

history

async history({
  pulseId,
}: {
  pulseId: string
}): Promise<{
  success: boolean;
  outputs?: Output[];
  problems?: Problem[];
  code?: number | null;
  error?: string
}>
interface Output {
  pulseId: string
  time: Date
  source: string // "manager", "controller", "vm", "agent", "app"
  stream: string // "stdout" or "stderr"
  data: string
}
interface Problem {
  pulseId: string
  time: Date
  source: string // "manager", "controller", "vm", "agent", "app"
  message: string
  stack?: string
}

stats

async stats({
  pulseId,
}: {
  pulseId: string
}): Promise<{
  success: boolean;
  stats?: AgentStats | null;
  error?: string
}>
interface AgentStats {
  time: number
  cpuAverage: number
  memAverage: number
  memTotal: number
  memFree: number
  memAvailable: number
  vsz: number
  rss: number
  user: number
  system: number
  guest: number
}

teamList

async teamList(): Promise<{ success: boolean; teams?: Team[]; error?: string }>
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}

teamGet

async teamGet({ teamId }: { teamId: string }): Promise<{ success: boolean; team?: Team; error?: string }>
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}

teamCreate

async teamCreate({ name }: { name: string }): Promise<{ success: boolean; team?: Team; error?: string }>
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}

teamUpdate

async teamUpdate({ teamId, name }: { teamId: string; name: string }): Promise<{ success: boolean; team?: Team; error?: string }>
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}

teamDelete

async teamDelete({ teamId }: { teamId: string }): Promise<{ success: boolean; team?: Team; error?: string }>
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}

teamUserList

async teamUserList({ teamId }: { teamId: string }): Promise<{ success: boolean; users?: User[]; error?: string }>
interface User {
  id: string
  email: string
  name: string
  createdAt: number
  updatedAt: number
}

teamUserAdd

async teamUserAdd({ teamId, userId }: { teamId: string; userId: string }): Promise<{ success: boolean; error?: string }>
interface User {
  id: string
  email: string
  name: string
  createdAt: number
  updatedAt: number
}

teamUserRemove

async teamUserRemove({ teamId, userId }: { teamId: string; userId: string }): Promise<{ success: boolean; error?: string }>

run

This is a do-all method that will:

  • create
  • update
  • warmUp
  • run
  • delete
async run(({
  pulseId,
  name,
  buildOptions, // if runtime supports it, i.e, docker build step args
  execOptions,  // runtime exec options, e.g., "--param" in "node --param /app/script.js"
  execPath,     // entry point, e.g., "/script.js" in "node /script.js", or "/Dockerfile" for "docker" runtime.
  files,        // files to be uploaded to the app directory. cannot be used with "zipFile"
  zipFile,      // zip with the app. will be extracted to /app/ directory. cannot be used with "files"
  initFile,     // initialization script
  envFile,      // .env file
  stdinFile,    // a file that will be used as stdin for the app
  vcpu,         // number of virtual CPUs
  memory,       // memory in MB
  balloon,      // initial balloon size in MB
  autoPause,    // pause the app after it's done executing the user app
  autoShutdown, // shutdown the app after it's done executing the user app
  autoDelete,   // delete the app on shutdown
}: {
  pulseId: string
  name?: string
  buildOptions?: string
  execOptions?: string
  execPath: string
  files?: { [path: string]: string | Buffer | File }
  zipFile?: string | Buffer | File
  initFile?: string | Buffer | File
  envFile?: string | Buffer | File
  stdinFile?: string | Buffer | File
  vcpu?: string
  memory?: string
  balloon?: string
  autoPause?: boolean
  autoShutdown?: boolean
  autoDelete?: boolean
}): Promise<{
  success: boolean;
  accessToken?: string;
  error?: string
}>

Use accessToken to stream the output using client.stream().

stream

async stream({
  accessToken,
  onOutput,
  onProblem,
  onFinish,
}: {
  accessToken: string
  onOutput?: (data: Output) => void
  onProblem?: (data: Problem) => void
  onFinish?: (data: Finished) => void
}): Promise<void>
type ManagerMsgOutput = {
  type: 'OUTPUT'
  pulseId: string
  output: Output
}
interface Output {
  pulseId: string
  time: Date
  source: string // "manager", "controller", "vm", "agent", "app"
  stream: string // "stdout" or "stderr"
  data: string
}
interface Problem {
  pulseId: string
  time: Date
  source: string // "manager", "controller", "vm", "agent", "app"
  message: string
  stack?: string
}
export interface Finished {
  pulseId: string
  code: number | null
  stats: AgentStats | null
}
interface AgentStats {
  time: number
  cpuAverage: number
  memAverage: number
  memTotal: number
  memFree: number
  memAvailable: number
  vsz: number
  rss: number
  user: number
  system: number
  guest: number
}

An example:

await manager.stream({
  accessToken,
  onOutput: (data) => {
    process.stdout.write(data.output.data)
  },
  onProblem: (data) => {
    console.error(data.error.message)
  },
  onFinish: (data) => {
    console.log('--------------------')
    console.log(`Exit code: ${data.code}`)
    if (data.stats) {
      console.log(`Time: ${stats.time}ms`)
      console.log(`CPU average: ${stats.cpuAverage.toFixed(2)}%`)
      console.log(`Memory average: ${stats.memAverage.toFixed(2)}%`)
      console.log(`Memory total: ${(stats.memTotal / 1024).toFixed(2)}MB`)
      console.log(`Memory free: ${(stats.memFree / 1024).toFixed(2)}MB`)
      console.log(`Memory available: ${(stats.memAvailable / 1024).toFixed(2)}MB`)
      console.log(`VSZ: ${stats.vsz}`)
      console.log(`RSS: ${stats.rss}`)
      console.log(`User: ${stats.user.toFixed(2)}%`)
      console.log(`System: ${stats.system.toFixed(2)}%`)
      console.log(`Guest: ${stats.guest.toFixed(2)}%`)
    }
  },
})
0.2.16

12 months ago

0.2.15

12 months ago

0.2.14

1 year ago

0.2.13

1 year ago

0.2.12

1 year ago

0.2.11

1 year ago

0.2.10

1 year ago

0.2.9

1 year ago

0.2.8

1 year ago

0.2.7

1 year ago

0.2.6

1 year ago

0.2.5

1 year ago