UNPKG

@silvana-one/coordination

Version:

Silvana Coordination Client

223 lines (199 loc) 6.15 kB
import { Transaction } from "@mysten/sui/transactions"; import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils"; import { silvanaRegistryPackage } from "./package.js"; import { fetchSuiDynamicField, fetchSuiObject } from "./fetch.js"; export type JobStatus = | { type: "Pending" } | { type: "Running" } | { type: "Failed"; error: string }; export interface Job { id: string; jobId: number; description?: string; developer: string; agent: string; agentMethod: string; app: string; appInstance: string; appInstanceMethod: string; sequences?: number[]; data: Uint8Array; status: JobStatus; attempts: number; createdAt: number; updatedAt: number; } export interface CreateJobParams { jobs: string; // Jobs object ID description?: string; developer: string; agent: string; agentMethod: string; app: string; appInstance: string; appInstanceMethod: string; sequences?: number[]; data: Uint8Array; } export class JobManager { private readonly jobs: string; constructor(params: { jobs: string }) { this.jobs = params.jobs; } static createJobs(maxAttempts?: number): Transaction { const tx = new Transaction(); tx.moveCall({ target: `${silvanaRegistryPackage}::jobs::create_jobs`, arguments: [ tx.pure.option("u8", maxAttempts ?? null), ], }); return tx; } createJob(params: CreateJobParams): Transaction { const { description, developer, agent, agentMethod, app, appInstance, appInstanceMethod, sequences, data, } = params; const tx = new Transaction(); tx.moveCall({ target: `${silvanaRegistryPackage}::jobs::create_job`, arguments: [ tx.object(this.jobs), tx.pure.option("string", description ?? null), tx.pure.string(developer), tx.pure.string(agent), tx.pure.string(agentMethod), tx.pure.string(app), tx.pure.string(appInstance), tx.pure.string(appInstanceMethod), tx.pure.option("vector<u64>", sequences ?? null), tx.pure.vector("u8", Array.from(data)), tx.object(SUI_CLOCK_OBJECT_ID), ], }); return tx; } startJob(jobId: number): Transaction { const tx = new Transaction(); tx.moveCall({ target: `${silvanaRegistryPackage}::jobs::start_job`, arguments: [ tx.object(this.jobs), tx.pure.u64(jobId), tx.object(SUI_CLOCK_OBJECT_ID), ], }); return tx; } completeJob(jobId: number): Transaction { const tx = new Transaction(); tx.moveCall({ target: `${silvanaRegistryPackage}::jobs::complete_job`, arguments: [ tx.object(this.jobs), tx.pure.u64(jobId), ], }); return tx; } failJob(params: { jobId: number; error: string }): Transaction { const { jobId, error } = params; const tx = new Transaction(); tx.moveCall({ target: `${silvanaRegistryPackage}::jobs::fail_job`, arguments: [ tx.object(this.jobs), tx.pure.u64(jobId), tx.pure.string(error), tx.object(SUI_CLOCK_OBJECT_ID), ], }); return tx; } updateMaxAttempts(maxAttempts: number): Transaction { const tx = new Transaction(); tx.moveCall({ target: `${silvanaRegistryPackage}::jobs::update_max_attempts`, arguments: [ tx.object(this.jobs), tx.pure.u64(maxAttempts), ], }); return tx; } async getJob(jobId: number): Promise<Job | undefined> { try { const jobsObject = await fetchSuiObject(this.jobs); if (!jobsObject) return undefined; const jobsTableId = (jobsObject as any)?.jobs?.fields?.id?.id; if (!jobsTableId) return undefined; const job = await fetchSuiDynamicField({ parentID: jobsTableId, fieldName: "jobs", type: "u64", key: String(jobId), }); if (!job) return undefined; const parseStatus = (status: any): JobStatus => { if (status?.Pending !== undefined) return { type: "Pending" }; if (status?.Running !== undefined) return { type: "Running" }; if (status?.Failed !== undefined) return { type: "Failed", error: status.Failed }; return { type: "Pending" }; }; return { id: (job as any)?.id?.id, jobId: Number((job as any).job_id), description: (job as any)?.description ?? undefined, developer: (job as any).developer, agent: (job as any).agent, agentMethod: (job as any).agent_method, app: (job as any).app, appInstance: (job as any).app_instance, appInstanceMethod: (job as any).app_instance_method, sequences: (job as any)?.sequences?.map((s: string) => Number(s)) ?? undefined, data: new Uint8Array((job as any).data), status: parseStatus((job as any).status), attempts: Number((job as any).attempts), createdAt: Number((job as any).created_at), updatedAt: Number((job as any).updated_at), }; } catch (error) { console.error("Error fetching job:", error); return undefined; } } async getPendingJobs(): Promise<number[]> { try { const jobsObject = await fetchSuiObject(this.jobs); if (!jobsObject) return []; const pendingJobs = (jobsObject as any)?.pending_jobs?.fields?.contents; if (!Array.isArray(pendingJobs)) return []; return pendingJobs.map((id: string) => Number(id)); } catch (error) { console.error("Error fetching pending jobs:", error); return []; } } async getNextPendingJob(): Promise<number | undefined> { const pendingJobs = await this.getPendingJobs(); return pendingJobs.length > 0 ? pendingJobs[0] : undefined; } async getMaxAttempts(): Promise<number> { try { const jobsObject = await fetchSuiObject(this.jobs); if (!jobsObject) return 3; // default value return Number((jobsObject as any)?.max_attempts ?? 3); } catch (error) { console.error("Error fetching max attempts:", error); return 3; } } }