UNPKG

@golem-sdk/golem-js

Version:

NodeJS and WebBrowser SDK for building apps running on Golem Network

237 lines (236 loc) 12.4 kB
import { EventEmitter } from "eventemitter3"; import { Agreement } from "./agreement"; import { Demand, DraftOfferProposalPool, IMarketApi, MarketEvents, MarketProposalEvent, OfferProposalSelector } from "./index"; import { Logger, YagnaApi } from "../shared/utils"; import { Allocation, IPaymentApi } from "../payment"; import { Observable } from "rxjs"; import { OfferCounterProposal, OfferProposal, OfferProposalFilter } from "./proposal"; import { DemandBodyBuilder, DemandSpecification, OrderDemandOptions } from "./demand"; import { IActivityApi, IFileServer } from "../activity"; import { StorageProvider } from "../shared/storage"; import { MarketOrderSpec } from "../golem-network"; import { INetworkApi, NetworkModule } from "../network"; import { AgreementOptions } from "./agreement/agreement"; import { ScanOptions, ScanSpecification, ScannedOffer } from "./scan"; export type DemandEngine = "vm" | "vm-nvidia" | "wasmtime"; export type PricingOptions = { model: "linear"; maxStartPrice: number; maxCpuPerHourPrice: number; maxEnvPerHourPrice: number; } | { model: "burn-rate"; avgGlmPerHour: number; }; export interface OrderMarketOptions { /** How long you want to rent the resources in hours */ rentHours: number; /** Pricing strategy that will be used to filter the offers from the market */ pricing: PricingOptions; /** A user-defined filter function which will determine if the offer proposal is valid for use. */ offerProposalFilter?: OfferProposalFilter; /** A user-defined function that will be used to pick the best fitting offer proposal from available ones */ offerProposalSelector?: OfferProposalSelector; } export interface MarketModuleOptions { /** * Number of seconds after which the demand will be un-subscribed and subscribed again to get fresh * offers from the market * * @default 30 minutes */ demandRefreshIntervalSec: number; } export interface MarketModule { events: EventEmitter<MarketEvents>; /** * Build a DemandSpecification based on the given options and allocation. * You can obtain an allocation using the payment module. * The method returns a DemandSpecification that can be used to publish the demand to the market, * for example using the `publishDemand` method. */ buildDemandDetails(demandOptions: OrderDemandOptions, orderOptions: OrderMarketOptions, allocation: Allocation): Promise<DemandSpecification>; /** * Build a ScanSpecification that can be used to scan the market for offers. * The difference between this method and `buildDemandDetails` is that this method does not require an * allocation, doesn't inherit payment properties from `GolemNetwork` settings and doesn't provide any defaults. * If you wish to set the payment platform, you need to specify it in the ScanOptions. */ buildScanSpecification(options: ScanOptions): ScanSpecification; /** * Publishes the demand to the market and handles refreshing it when needed. * Each time the demand is refreshed, a new demand is emitted by the observable. * Keep in mind that since this method returns an observable, nothing will happen until you subscribe to it. * Unsubscribing will remove the demand from the market. */ publishAndRefreshDemand(demandSpec: DemandSpecification): Observable<Demand>; /** * Return an observable that will emit values representing various events related to this demand */ collectMarketProposalEvents(demand: Demand): Observable<MarketProposalEvent>; /** * Subscribes to the proposals for the given demand. * If an error occurs, the observable will emit an error and complete. * Keep in mind that since this method returns an observable, nothing will happen until you subscribe to it. * * This method will just yield all the proposals that will be found for that demand without any additional logic. * * The {@link collectDraftOfferProposals} is a more specialized variant of offer collection, which includes negotiations * and demand re-subscription logic */ collectAllOfferProposals(demand: Demand): Observable<OfferProposal>; /** * Sends a counter-offer to the provider. Note that to get the provider's response to your * counter you should listen to events returned by `collectDemandOfferEvents`. * * @returns The counter-proposal that the requestor made to the Provider */ negotiateProposal(receivedProposal: OfferProposal, counterDemandSpec: DemandSpecification): Promise<OfferCounterProposal>; /** * Internally * * - ya-ts-client createAgreement * - ya-ts-client approveAgreement * - ya-ts-client "wait for approval" * * @param proposal * @param signalOrTimeout - The timeout in milliseconds or an AbortSignal that will be used to cancel the operation * * @return Returns when the provider accepts the agreement, rejects otherwise. The resulting agreement is ready to create activities from. */ proposeAgreement(proposal: OfferProposal, agreementOptions?: AgreementOptions, signalOrTimeout?: AbortSignal | number): Promise<Agreement>; /** * @return The Agreement that has been terminated via Yagna * @param signalOrTimeout - The timeout in milliseconds or an AbortSignal that will be used to cancel the operation */ terminateAgreement(agreement: Agreement, reason?: string, signalOrTimeout?: AbortSignal | number): Promise<Agreement>; /** * Acquire a proposal from the pool and sign an agreement with the provider. If signing the agreement fails, * destroy the proposal and try again with another one. The method returns an agreement that's ready to be used. * Optionally, you can provide a timeout in milliseconds or an AbortSignal that can be used to cancel the operation * early. If the operation is cancelled, the method will throw an error. * Note that this method will respect the acquire timeout set in the pool and will throw an error if no proposal * is available within the specified time. * * @example * ```ts * const agreement = await marketModule.signAgreementFromPool(draftProposalPool, 10_000); // throws TimeoutError if the operation takes longer than 10 seconds * ``` * @example * ```ts * const signal = AbortSignal.timeout(10_000); * const agreement = await marketModule.signAgreementFromPool(draftProposalPool, signal); // throws TimeoutError if the operation takes longer than 10 seconds * ``` * @param draftProposalPool - The pool of draft proposals to acquire from * @param agreementOptions - options used to sign the agreement such as expiration or waitingForApprovalTimeout * @param signalOrTimeout - The timeout in milliseconds or an AbortSignal that will be used to cancel the operation */ signAgreementFromPool(draftProposalPool: DraftOfferProposalPool, agreementOptions?: AgreementOptions, signalOrTimeout?: number | AbortSignal): Promise<Agreement>; /** * Creates a demand for the given package and allocation and starts collecting, filtering and negotiating proposals. * The method returns an observable that emits a batch of draft proposals every time the buffer is full. * The method will automatically negotiate the proposals until they are moved to the `Draft` state. * Keep in mind that since this method returns an observable, nothing will happen until you subscribe to it. * Unsubscribing from the observable will stop the process and remove the demand from the market. */ collectDraftOfferProposals(options: { demandSpecification: DemandSpecification; pricing: PricingOptions; filter?: OfferProposalFilter; minProposalsBatchSize?: number; proposalsBatchReleaseTimeoutMs?: number; }): Observable<OfferProposal>; /** * Estimate the budget for the given order and maximum numbers of agreemnets. * Keep in mind that this is just an estimate and the actual cost may vary. * The method returns the estimated budget in GLM. * @param params */ estimateBudget({ maxAgreements, order }: { maxAgreements: number; order: MarketOrderSpec; }): number; /** * Fetch the most up-to-date agreement details from the yagna */ fetchAgreement(agreementId: string, signalOrTimeout?: AbortSignal | number): Promise<Agreement>; /** * Scan the market for offers that match the given demand specification. */ scan(scanSpecification: ScanSpecification): Observable<ScannedOffer>; } /** * Represents a director that can instruct DemandDetailsBuilder * * Demand is a complex concept in Golem. Requestors can place arbitrary properties and constraints on such * market entity. While the demand request on the Golem Protocol level is a flat list of properties (key, value) and constraints, * from the Requestor side they form logical groups that make sense together. * * The idea behind Directors is that you can encapsulate this grouping knowledge along with validation logic etc to prepare * all the final demand request body properties in a more controlled and organized manner. */ export interface IDemandDirector { apply(builder: DemandBodyBuilder): Promise<void> | void; } export declare class MarketModuleImpl implements MarketModule { private readonly deps; events: EventEmitter<MarketEvents, any>; private readonly logger; private readonly marketApi; private fileServer; private options; constructor(deps: { logger: Logger; yagna: YagnaApi; paymentApi: IPaymentApi; activityApi: IActivityApi; marketApi: IMarketApi; networkApi: INetworkApi; networkModule: NetworkModule; fileServer: IFileServer; storageProvider: StorageProvider; }, options?: Partial<MarketModuleOptions>); buildDemandDetails(demandOptions: OrderDemandOptions, orderOptions: OrderMarketOptions, allocation: Allocation): Promise<DemandSpecification>; buildScanSpecification(options: ScanOptions): ScanSpecification; /** * Augments the user-provided options with additional logic * * Use Case: serve the GVMI from the requestor and avoid registry */ private applyLocalGVMIServeSupport; /** * Publishes the specified demand and re-publishes it based on demandSpecification.expirationSec interval */ publishAndRefreshDemand(demandSpecification: DemandSpecification): Observable<Demand>; collectMarketProposalEvents(demand: Demand): Observable<MarketProposalEvent>; collectAllOfferProposals(demand: Demand): Observable<OfferProposal>; negotiateProposal(offerProposal: OfferProposal, counterDemand: DemandSpecification): Promise<OfferCounterProposal>; proposeAgreement(proposal: OfferProposal, options?: AgreementOptions, signalOrTimeout?: AbortSignal | number): Promise<Agreement>; terminateAgreement(agreement: Agreement, reason?: string, signalOrTimeout?: AbortSignal | number): Promise<Agreement>; collectDraftOfferProposals(options: { demandSpecification: DemandSpecification; pricing: PricingOptions; filter?: OfferProposalFilter; minProposalsBatchSize?: number; proposalsBatchReleaseTimeoutMs?: number; }): Observable<OfferProposal>; private emitMarketProposalEvents; signAgreementFromPool(draftProposalPool: DraftOfferProposalPool, agreementOptions?: AgreementOptions, signalOrTimeout?: number | AbortSignal): Promise<Agreement>; /** * Reduce initial proposals to a set grouped by the provider's key to avoid duplicate offers */ private reduceInitialProposalsByProviderKey; estimateBudget({ order, maxAgreements }: { order: MarketOrderSpec; maxAgreements: number; }): number; fetchAgreement(agreementId: string, signalOrTimeout?: AbortSignal | number): Promise<Agreement>; /** * Subscribes to an observable that maps yagna events into our domain events * and emits these domain events via EventEmitter */ private collectAndEmitAgreementEvents; private filterProposalsByUserFilter; private filterProposalsByPricingOptions; scan(scanSpecification: ScanSpecification): Observable<ScannedOffer>; }