Skip to main content
Module

x/windmill/mod.ts

Windmill deno client (separated from the main repo because most of the code is auto-generated from the openapi and not worth committing)
Go to Latest
File
import { ResourceApi, VariableApi, ServerConfiguration, JobApi } from './windmill-api/index.ts'import { createConfiguration, type Configuration as Configuration } from './windmill-api/configuration.ts'
export { AdminApi, AuditApi, FlowApi, GranularAclApi, GroupApi, JobApi, ResourceApi, VariableApi, ScriptApi, ScheduleApi, SettingsApi, UserApi, WorkspaceApi} from './windmill-api/index.ts'
export { pgSql, pgClient } from './pg.ts'
export type Sql = stringexport type Email = stringexport type Base64 = stringexport type Resource<S extends string> = any
type Conf = Configuration & { workspace_id: string }
export const SHARED_FOLDER = '/shared'
/** * Create a client configuration from env variables * @returns client configuration */export function createConf(): Conf { const token = Deno.env.get("WM_TOKEN") ?? 'no_token' const base_url = Deno.env.get("BASE_INTERNAL_URL") ?? 'http://localhost:8000' return { ...createConfiguration({ baseServer: new ServerConfiguration(`${base_url}/api`, {}), authMethods: { bearerAuth: { tokenProvider: { getToken() { return token } } } }, }), workspace_id: Deno.env.get("WM_WORKSPACE") ?? 'no_workspace' }}
/** * Get a resource value by path * @param path path of the resource * @param undefinedIfEmpty if the resource does not exist, return undefined instead of throwing an error * @returns resource value */export async function getResource(path: string, undefinedIfEmpty?: boolean): Promise<any> { const conf = createConf() try { const resource = await new ResourceApi(conf).getResource(conf.workspace_id, path) return await _transformLeaf(resource.value) } catch (e) { if (undefinedIfEmpty && e.code === 404) { return undefined } else { throw e } }}
export function getInternalStatePath(suffix?: string): string { const env_flow_path = Deno.env.get("WM_FLOW_PATH") const env_job_path = Deno.env.get("WM_JOB_PATH") const permissioned_as = Deno.env.get("WM_PERMISSIONED_AS") const flow_path = env_flow_path != undefined && env_flow_path != "" ? env_flow_path : 'NO_FLOW_PATH' const script_path = suffix ?? (env_job_path != undefined && env_job_path != "" ? env_job_path : 'NO_JOB_PATH') const env_schedule_path = Deno.env.get("WM_SCHEDULE_PATH") const schedule_path = env_schedule_path != undefined && env_schedule_path != "" ? `/${env_schedule_path}` : ''
if (script_path.slice(script_path.length - 1) === '/') { throw Error(`The script path must not end with '/', give a name to your script!`) } return `${permissioned_as}/${flow_path}/${script_path}${schedule_path}`}
/** * Set a resource value by path * @param path path of the resource to set * @param value new value of the resource to set * @param initializeToTypeIfNotExist if the resource does not exist, initialize it with this type */export async function setResource(path: string, value: any, initializeToTypeIfNotExist?: string): Promise<void> { const conf = createConf() const resourceApi = new ResourceApi(conf) if (await resourceApi.existsResource(conf.workspace_id, path)) { await resourceApi.updateResource(conf.workspace_id, path, { value }) } else if (initializeToTypeIfNotExist) { await resourceApi.createResource(conf.workspace_id, { path, value, resourceType: initializeToTypeIfNotExist }) } else { throw Error(`Resource at path ${path} does not exist and no type was provided to initialize it`) }}
/** * Set the internal state * @param state state to set * @param suffix suffix of the path of the internal state (useful to share internal state between jobs) */export async function setInternalState(state: any, suffix?: string): Promise<void> { await setResource(getInternalStatePath(suffix), state, 'state')}
/** * Get the internal state * @param suffix suffix of the path of the internal state (useful to share internal state between jobs) */export async function getInternalState(suffix?: string): Promise<any> { return await getResource(getInternalStatePath(suffix), true)}
/** * Get a variable by path * @param path path of the variable * @returns variable value */export async function getVariable(path: string): Promise<string | undefined> { const conf = createConf() const variable = await new VariableApi(conf).getVariable(conf.workspace_id, path) return variable.value}
async function transformLeaves(d: { [key: string]: any }): Promise<{ [key: string]: any }> { for (const k in d) { d[k] = await _transformLeaf(d[k]) } return d}
const VAR_RESOURCE_PREFIX = "$var:"async function _transformLeaf(v: any): Promise<any> { if (typeof v === 'object') { return transformLeaves(v) } else if (typeof v === 'string' && v.startsWith(VAR_RESOURCE_PREFIX)) { const varName = v.substring(VAR_RESOURCE_PREFIX.length) return await getVariable(varName) } else { return v }}
export async function databaseUrlFromResource(path: string): Promise<string> { const resource = await getResource(path) return `postgresql://${resource.user}:${resource.password}@${resource.host}:${resource.port}/${resource.dbname}?sslmode=${resource.sslmode}`}

export async function genNounceAndHmac(conf: Conf, jobId: string) { const nounce = Math.floor(Math.random() * 4294967295); const sig = await fetch(Deno.env.get("WM_BASE_URL") + `/api/w/${conf.workspace_id}/jobs/job_signature/${jobId}/${nounce}?token=${Deno.env.get("WM_TOKEN")}`) return { nounce, signature: await sig.text() };}
export async function getResumeEndpoints() { const conf = createConf(); const { nounce, signature } = await genNounceAndHmac( conf, Deno.env.get("WM_JOB_ID") ?? "no_job_id", ); const url_prefix = Deno.env.get("WM_BASE_URL") + `/api/w/${conf.workspace_id}/jobs/`;
function getResumeUrl(op: string) { return url_prefix + `${op}/${Deno.env.get("WM_JOB_ID")}/${nounce}/${signature}`; }
return { resume: getResumeUrl("resume"), cancel: getResumeUrl("cancel"), };}

export function base64ToUint8Array(data: string): Uint8Array { return Uint8Array.from(atob(data), c => c.charCodeAt(0))}
export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string { let base64 = '' const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
const bytes = new Uint8Array(arrayBuffer) const byteLength = bytes.byteLength const byteRemainder = byteLength % 3 const mainLength = byteLength - byteRemainder
let a, b, c, d let chunk
// Main loop deals with bytes in chunks of 3 for (let i = 0; i < mainLength; i = i + 3) { // Combine the three bytes into a single integer chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
// Use bitmasks to extract 6-bit segments from the triplet a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18 b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12 c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6 d = chunk & 63 // 63 = 2^6 - 1
// Convert the raw binary segments to the appropriate ASCII encoding base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d] }
// Deal with the remaining bytes and padding if (byteRemainder == 1) { chunk = bytes[mainLength]
a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero b = (chunk & 3) << 4 // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '==' } else if (byteRemainder == 2) { chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10 b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero c = (chunk & 15) << 2 // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '=' }
return base64}