@pulsefunctions/client v0.2.16
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 thedocker
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)}%`)
}
},
})