import { JobService, ResourceService, VariableService } from './windmill-api/index.ts' import { OpenAPI } from './windmill-api/index.ts' export { AdminService, AuditService, FlowService, GranularAclService, GroupService, JobService, ResourceService, VariableService, ScriptService, ScheduleService, SettingsService, UserService, WorkspaceService } from './windmill-api/index.ts' // @ts-ignore: Otherwise BigInt is not supported for export BigInt.prototype.toJSON = function () { return this.toString(); }; export { pgSql, pgClient } from './pg.ts' export type Sql = string export type Email = string export type Base64 = string export type Resource = any export const SHARED_FOLDER = '/shared' export function setClient(token: string, baseUrl: string) { OpenAPI.WITH_CREDENTIALS = true OpenAPI.TOKEN = token OpenAPI.BASE = baseUrl + '/api' } setClient(Deno.env.get("WM_TOKEN") ?? 'no_token', Deno.env.get("BASE_INTERNAL_URL") ?? Deno.env.get("BASE_URL") ?? 'http://localhost:8000') /** * Create a client configuration from env variables * @returns client configuration */ export function getWorkspace(): string { return Deno.env.get("WM_WORKSPACE") ?? 'no_workspace' } /** * Get a resource value by path * @param path path of the resource, default to internal state path * @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 { const workspace = getWorkspace() path = path ?? getStatePath() try { const resource = await ResourceService.getResource({ workspace, path }) return await _transformLeaf(resource.value) } catch (e: any) { if (undefinedIfEmpty && e.status === 404) { return undefined } else { throw Error(`Resource not found at ${path} or not visible to you`) } } } export function getStatePath(): string { const state_path = Deno.env.get("WM_STATE_PATH") if (state_path === undefined) { throw Error("State path not set") } return state_path } /** * Set a resource value by path * @param path path of the resource to set, default to state path * @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(value: any, path?: string, initializeToTypeIfNotExist?: string): Promise { path = path ?? getStatePath() const workspace = getWorkspace() if (await ResourceService.existsResource({ workspace, path })) { await ResourceService.updateResourceValue({ workspace, path, requestBody: { value } }) } else if (initializeToTypeIfNotExist) { await ResourceService.createResource({ workspace, requestBody: { path, value, resource_type: initializeToTypeIfNotExist } }) } else { throw Error(`Resource at path ${path} does not exist and no type was provided to initialize it`) } } /** * Set the state * @param state state to set * @deprecated use setState instead */ export async function setInternalState(state: any): Promise { await setResource(state, undefined, 'state') } /** * Set the state * @param state state to set */ export async function setState(state: any): Promise { await setResource(state, undefined, 'state') } /** * Set the shared state * @param state state to set */ export async function setSharedState(state: any, path = 'state.json'): Promise { await Deno.writeTextFile(SHARED_FOLDER + '/' + path, JSON.stringify(state)) } /** * Get the shared state * @param state state to set */ export async function getSharedState(path = 'state.json'): Promise { return JSON.parse(await Deno.readTextFile(SHARED_FOLDER + '/' + path)) } /** * Get the internal state * @deprecated use getState instead */ export async function getInternalState(): Promise { return await getResource(getStatePath(), true) } /** * Get the state shared across executions */ export async function getState(): Promise { return await getResource(getStatePath(), true) } /** * Get a variable by path * @param path path of the variable * @returns variable value */ export async function getVariable(path: string): Promise { const workspace = getWorkspace() try { const variable = await VariableService.getVariable({ workspace, path }) return variable.value } catch (e: any) { throw Error(`Variable not found at ${path} or not visible to you`) } } 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 { 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 { const resource = await getResource(path) return `postgresql://${resource.user}:${resource.password}@${resource.host}:${resource.port}/${resource.dbname}?sslmode=${resource.sslmode}` } /** * Get URLs needed for resuming a flow after this step * @param approver approver name * @returns approval page UI URL, resume and cancel API URLs for resumeing the flow */ export async function getResumeUrls(approver?: string): Promise<{ approvalPage: string; resume: string; cancel: string; }> { const nonce = Math.floor(Math.random() * 4294967295); const workspace = getWorkspace() return await JobService.getResumeUrls({ workspace, resumeId: nonce, approver, id: Deno.env.get("WM_JOB_ID") ?? 'NO_JOB_ID' }) } /** * @deprecated use getResumeUrls instead */ export function getResumeEndpoints(approver?: string): Promise<{ approvalPage: string; resume: string; cancel: string; }> { return getResumeUrls(approver) } 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 }