2023-05-21 21:37:29 +02:00
|
|
|
import LRU from "lru-cache";
|
2023-10-09 21:53:44 +02:00
|
|
|
import type { Query, UpObject } from ".";
|
2023-10-07 13:13:00 +02:00
|
|
|
import { UpListing } from ".";
|
2023-05-21 21:37:29 +02:00
|
|
|
import type {
|
|
|
|
Address,
|
2024-02-05 22:04:48 +01:00
|
|
|
ADDRESS_TYPE,
|
2023-05-21 21:37:29 +02:00
|
|
|
AttributeListingResult,
|
|
|
|
EntityListing,
|
|
|
|
IJob,
|
|
|
|
IValue,
|
|
|
|
ListingResult,
|
|
|
|
PutInput,
|
|
|
|
PutResult,
|
|
|
|
StoreInfo,
|
|
|
|
VaultInfo,
|
|
|
|
} from "./types";
|
2024-02-05 22:04:48 +01:00
|
|
|
import type { AddressComponents, UpEndWasmExtensions } from "./wasm";
|
2023-06-05 12:56:33 +02:00
|
|
|
import debug from "debug";
|
2024-02-05 22:04:48 +01:00
|
|
|
import { browser } from "./util";
|
|
|
|
|
2023-06-05 12:56:33 +02:00
|
|
|
const dbg = debug("upend:api");
|
2023-05-21 21:37:29 +02:00
|
|
|
|
2023-10-07 13:13:00 +02:00
|
|
|
export type { AddressComponents };
|
2023-06-28 14:26:11 +02:00
|
|
|
|
2023-05-21 21:37:29 +02:00
|
|
|
export class UpEndApi {
|
|
|
|
private instanceUrl = "";
|
2024-01-27 14:27:24 +01:00
|
|
|
private readonly wasmExtensions: UpEndWasmExtensions | undefined = undefined;
|
|
|
|
public readonly timeout: number;
|
2023-05-21 21:37:29 +02:00
|
|
|
|
|
|
|
private queryOnceLRU = new LRU<string, UpListing>({ max: 128 });
|
|
|
|
private inFlightRequests: { [key: string]: Promise<UpListing> | null } = {};
|
|
|
|
|
2023-10-07 17:28:26 +02:00
|
|
|
constructor(config: {
|
|
|
|
instanceUrl?: string;
|
|
|
|
wasmExtensions?: UpEndWasmExtensions;
|
2024-01-27 14:27:24 +01:00
|
|
|
timeout?: number;
|
2023-10-07 17:28:26 +02:00
|
|
|
}) {
|
|
|
|
this.setInstanceUrl(config.instanceUrl || "http://localhost:8093");
|
|
|
|
this.wasmExtensions = config.wasmExtensions;
|
2024-01-27 14:27:24 +01:00
|
|
|
this.timeout = config.timeout || 30_000;
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public setInstanceUrl(apiUrl: string) {
|
|
|
|
this.instanceUrl = apiUrl.replace(/\/+$/g, "");
|
|
|
|
}
|
|
|
|
|
2023-05-22 20:57:06 +02:00
|
|
|
public get apiUrl() {
|
2023-05-21 21:37:29 +02:00
|
|
|
return this.instanceUrl + "/api";
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async fetchEntity(
|
|
|
|
address: string,
|
|
|
|
options?: ApiFetchOptions,
|
|
|
|
): Promise<UpObject> {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Fetching Entity %s", address);
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
const entityFetch = await fetch(`${this.apiUrl}/obj/${address}`, {
|
|
|
|
signal,
|
|
|
|
});
|
2023-05-21 21:37:29 +02:00
|
|
|
const entityResult = (await entityFetch.json()) as EntityListing;
|
|
|
|
const entityListing = new UpListing(entityResult.entries);
|
|
|
|
return entityListing.getObject(address);
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async fetchEntry(address: string, options?: ApiFetchOptions) {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Fetching entry %s", address);
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
const response = await fetch(`${this.apiUrl}/raw/${address}`, { signal });
|
2023-05-21 21:37:29 +02:00
|
|
|
const data = await response.json();
|
|
|
|
const listing = new UpListing({ address: data });
|
|
|
|
return listing.entries[0];
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async query(
|
|
|
|
query: string | Query,
|
|
|
|
options?: ApiFetchOptions,
|
|
|
|
): Promise<UpListing> {
|
2023-10-09 21:53:44 +02:00
|
|
|
const queryStr = query.toString();
|
|
|
|
|
|
|
|
const cacheResult = this.queryOnceLRU.get(queryStr);
|
2023-05-21 21:37:29 +02:00
|
|
|
if (!cacheResult) {
|
2023-10-09 21:53:44 +02:00
|
|
|
if (!this.inFlightRequests[queryStr]) {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg(`Querying: ${query}`);
|
2023-10-09 21:53:44 +02:00
|
|
|
this.inFlightRequests[queryStr] = new Promise((resolve, reject) => {
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
2023-05-21 21:37:29 +02:00
|
|
|
fetch(`${this.apiUrl}/query`, {
|
|
|
|
method: "POST",
|
2023-10-09 21:53:44 +02:00
|
|
|
body: queryStr,
|
2023-05-21 21:37:29 +02:00
|
|
|
keepalive: true,
|
2024-01-27 14:27:24 +01:00
|
|
|
signal,
|
2023-05-21 21:37:29 +02:00
|
|
|
})
|
|
|
|
.then(async (response) => {
|
2023-10-09 22:13:11 +02:00
|
|
|
if (!response.ok) {
|
|
|
|
reject(
|
|
|
|
`Query ${queryStr} failed: ${response.status} ${
|
|
|
|
response.statusText
|
2024-01-27 14:27:24 +01:00
|
|
|
}: ${await response.text()}}`,
|
2023-10-09 22:13:11 +02:00
|
|
|
);
|
|
|
|
}
|
2023-05-21 21:37:29 +02:00
|
|
|
resolve(new UpListing(await response.json()));
|
2023-10-09 21:53:44 +02:00
|
|
|
this.inFlightRequests[queryStr] = null;
|
2023-05-21 21:37:29 +02:00
|
|
|
})
|
|
|
|
.catch((err) => reject(err));
|
|
|
|
});
|
|
|
|
} else {
|
2023-10-09 21:53:44 +02:00
|
|
|
dbg(`Chaining request for ${queryStr}...`);
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
2023-10-09 21:53:44 +02:00
|
|
|
return await (this.inFlightRequests[queryStr] as Promise<UpListing>); // TODO?
|
2023-05-21 21:37:29 +02:00
|
|
|
} else {
|
2023-10-09 21:53:44 +02:00
|
|
|
dbg(`Returning cached: ${queryStr}`);
|
2023-05-21 21:37:29 +02:00
|
|
|
return cacheResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async putEntry(
|
|
|
|
input: PutInput,
|
|
|
|
options?: ApiFetchOptions,
|
|
|
|
): Promise<PutResult> {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Putting %O", input);
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
2023-05-21 21:37:29 +02:00
|
|
|
const response = await fetch(`${this.apiUrl}/obj`, {
|
|
|
|
method: "PUT",
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
body: JSON.stringify(input),
|
2024-01-27 14:27:24 +01:00
|
|
|
signal,
|
2023-05-21 21:37:29 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
return await response.json();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async putEntityAttribute(
|
|
|
|
entity: Address,
|
|
|
|
attribute: string,
|
2023-06-19 16:45:55 +02:00
|
|
|
value: IValue,
|
2024-01-27 14:27:24 +01:00
|
|
|
provenance?: string,
|
|
|
|
options?: ApiFetchOptions,
|
2023-05-21 21:37:29 +02:00
|
|
|
): Promise<Address> {
|
2023-09-23 17:59:52 +02:00
|
|
|
dbg("Putting %s = %o for %s (%s)", attribute, value, entity, provenance);
|
2023-06-19 16:45:55 +02:00
|
|
|
let url = `${this.apiUrl}/obj/${entity}/${attribute}`;
|
|
|
|
if (provenance) {
|
|
|
|
url += `?provenance=${provenance}`;
|
|
|
|
}
|
2024-01-27 14:27:24 +01:00
|
|
|
|
|
|
|
const signal = this.getAbortSignal(options);
|
2023-06-19 16:45:55 +02:00
|
|
|
const response = await fetch(url, {
|
2023-05-21 21:37:29 +02:00
|
|
|
method: "PUT",
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
body: JSON.stringify(value),
|
2024-01-27 14:27:24 +01:00
|
|
|
signal,
|
2023-05-21 21:37:29 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
return await response.json();
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async putBlob(
|
|
|
|
fileOrUrl: File | URL,
|
2024-02-05 22:04:48 +01:00
|
|
|
options?: ApiFetchOptions & { onProgress?: (ev: ProgressEvent) => void },
|
2024-01-27 14:27:24 +01:00
|
|
|
): Promise<PutResult> {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Putting Blob: %O", fileOrUrl);
|
2024-02-05 22:04:48 +01:00
|
|
|
|
2023-05-21 21:37:29 +02:00
|
|
|
const formData = new FormData();
|
2023-05-27 15:52:18 +02:00
|
|
|
if (fileOrUrl instanceof File) {
|
|
|
|
formData.append(fileOrUrl.name, fileOrUrl);
|
|
|
|
} else {
|
|
|
|
formData.append("@url", fileOrUrl.toString());
|
|
|
|
}
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
2023-05-21 21:37:29 +02:00
|
|
|
|
2024-02-05 22:04:48 +01:00
|
|
|
if (browser && fileOrUrl instanceof File) {
|
|
|
|
dbg("Using XHR for file upload");
|
|
|
|
const xhrdbg = debug("upend:api:xhr");
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
signal.addEventListener("abort", () => xhr.abort());
|
|
|
|
for (const event of [
|
|
|
|
"loadstart",
|
|
|
|
"load",
|
|
|
|
"loadend",
|
|
|
|
"progress",
|
|
|
|
"abort",
|
|
|
|
"error",
|
|
|
|
] as const) {
|
|
|
|
xhr.addEventListener(event, (ev) => xhrdbg(`XHR ${event}: %O`, ev));
|
|
|
|
xhr.upload.addEventListener(event, (ev) =>
|
|
|
|
xhrdbg(`XHR upload ${event}: %O`, ev),
|
|
|
|
);
|
|
|
|
if (options?.onProgress) {
|
|
|
|
xhr.upload.addEventListener(event, options.onProgress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
xhr.open("PUT", `${this.apiUrl}/blob`, true);
|
|
|
|
xhr.onload = () => {
|
|
|
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
|
|
try {
|
|
|
|
resolve(JSON.parse(xhr.responseText));
|
|
|
|
} catch (e) {
|
|
|
|
reject(e);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
reject(xhr.statusText);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
xhr.send(formData);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const response = await fetch(`${this.apiUrl}/blob`, {
|
|
|
|
method: "PUT",
|
|
|
|
body: formData,
|
|
|
|
signal,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
throw Error(await response.text());
|
|
|
|
}
|
2023-05-21 21:37:29 +02:00
|
|
|
|
2024-02-05 22:04:48 +01:00
|
|
|
return await response.json();
|
|
|
|
}
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async deleteEntry(
|
|
|
|
address: Address,
|
|
|
|
options?: ApiFetchOptions,
|
|
|
|
): Promise<void> {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Deleting entry %s", address);
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
await fetch(`${this.apiUrl}/obj/${address}`, { method: "DELETE", signal });
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
2023-10-20 20:55:10 +02:00
|
|
|
public getRaw(address: Address, preview = false) {
|
|
|
|
return `${this.apiUrl}/${preview ? "thumb" : "raw"}/${address}`;
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async fetchRaw(
|
|
|
|
address: Address,
|
|
|
|
preview = false,
|
|
|
|
options?: ApiFetchOptions,
|
|
|
|
) {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Getting %s raw (preview = %s)", address, preview);
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
return await fetch(this.getRaw(address, preview), { signal });
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async refreshVault(options?: ApiFetchOptions) {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Triggering vault refresh");
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
return await fetch(`${this.apiUrl}/refresh`, { method: "POST", signal });
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async nativeOpen(address: Address, options?: ApiFetchOptions) {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Opening %s natively", address);
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
return fetch(`${this.apiUrl}/raw/${address}?native=1`, { signal });
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async fetchRoots(options?: ApiFetchOptions): Promise<ListingResult> {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Fetching hierarchical roots...");
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
const response = await fetch(`${this.apiUrl}/hier_roots`, { signal });
|
2023-06-05 12:56:33 +02:00
|
|
|
const roots = await response.json();
|
|
|
|
dbg("Hierarchical roots: %O", roots);
|
|
|
|
return roots;
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async fetchJobs(options?: ApiFetchOptions): Promise<IJob[]> {
|
|
|
|
// dbg("Fetching jobs...");
|
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
const response = await fetch(`${this.apiUrl}/jobs`, { signal });
|
2023-05-21 21:37:29 +02:00
|
|
|
return await response.json();
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async fetchAllAttributes(
|
|
|
|
options?: ApiFetchOptions,
|
|
|
|
): Promise<AttributeListingResult> {
|
2023-06-05 12:56:33 +02:00
|
|
|
dbg("Fetching all attributes...");
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
const response = await fetch(`${this.apiUrl}/all/attributes`, { signal });
|
2023-06-05 12:56:33 +02:00
|
|
|
const result = await response.json();
|
|
|
|
dbg("All attributes: %O", result);
|
|
|
|
return await result;
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async fetchInfo(options?: ApiFetchOptions): Promise<VaultInfo> {
|
|
|
|
dbg("Fetching vault info...");
|
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
const response = await fetch(`${this.apiUrl}/info`, { signal });
|
2023-06-05 12:56:33 +02:00
|
|
|
const result = await response.json();
|
|
|
|
dbg("Vault info: %O", result);
|
|
|
|
return result;
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async fetchStoreInfo(
|
|
|
|
options?: ApiFetchOptions,
|
|
|
|
): Promise<{ [key: string]: StoreInfo }> {
|
|
|
|
dbg("Fetching store info...");
|
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
const response = await fetch(`${this.apiUrl}/stats/store`, { signal });
|
2023-06-05 12:56:33 +02:00
|
|
|
const result = await response.json();
|
|
|
|
dbg("Store info: %O");
|
|
|
|
return await result;
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async getAddress(
|
2024-01-27 14:27:24 +01:00
|
|
|
input: { urlContent: string } | ADDRESS_TYPE,
|
|
|
|
options?: ApiFetchOptions,
|
2023-05-21 21:37:29 +02:00
|
|
|
): Promise<string> {
|
2023-08-25 23:35:29 +02:00
|
|
|
let response: Response;
|
2023-06-28 14:26:11 +02:00
|
|
|
if (typeof input === "string") {
|
2023-10-07 13:00:34 +02:00
|
|
|
if (this.wasmExtensions) {
|
|
|
|
await this.wasmExtensions.init();
|
|
|
|
return this.wasmExtensions.AddressTypeConstants[input];
|
2023-06-28 18:36:47 +02:00
|
|
|
}
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
response = await fetch(`${this.apiUrl}/address?type=${input}`, {
|
|
|
|
signal,
|
|
|
|
});
|
2023-05-21 21:37:29 +02:00
|
|
|
} else {
|
2023-11-29 14:42:31 +01:00
|
|
|
if ("urlContent" in input) {
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(options);
|
2023-06-28 14:26:11 +02:00
|
|
|
response = await fetch(
|
2024-01-27 14:27:24 +01:00
|
|
|
`${this.apiUrl}/address?url_content=${input.urlContent}`,
|
|
|
|
{ signal },
|
2023-06-28 14:26:11 +02:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
throw new Error("Input cannot be empty.");
|
|
|
|
}
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
2023-06-05 12:56:33 +02:00
|
|
|
const result = await response.json();
|
|
|
|
dbg("Address for %o = %s", input, result);
|
|
|
|
return result;
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|
2023-06-28 14:26:11 +02:00
|
|
|
|
|
|
|
public async addressToComponents(
|
2024-01-27 14:27:24 +01:00
|
|
|
address: string,
|
2023-06-28 14:26:11 +02:00
|
|
|
): Promise<AddressComponents> {
|
2023-10-07 13:00:34 +02:00
|
|
|
if (!this.wasmExtensions) {
|
|
|
|
throw new Error("WASM extensions not supplied.");
|
|
|
|
}
|
|
|
|
await this.wasmExtensions.init();
|
|
|
|
return this.wasmExtensions.addr_to_components(address);
|
2023-06-28 14:26:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async componentsToAddress(
|
2024-01-27 14:27:24 +01:00
|
|
|
components: AddressComponents,
|
2023-06-28 14:26:11 +02:00
|
|
|
): Promise<string> {
|
2023-10-07 13:00:34 +02:00
|
|
|
if (!this.wasmExtensions) {
|
|
|
|
throw new Error("WASM extensions not initialized.");
|
2023-06-28 14:26:11 +02:00
|
|
|
}
|
2023-10-07 13:00:34 +02:00
|
|
|
await this.wasmExtensions.init();
|
|
|
|
return this.wasmExtensions.components_to_addr(components);
|
2023-06-28 14:26:11 +02:00
|
|
|
}
|
2023-11-08 22:14:41 +01:00
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async getVaultOptions(
|
|
|
|
options?: ApiFetchOptions,
|
|
|
|
): Promise<VaultOptions> {
|
|
|
|
const signal = this.getAbortSignal(options);
|
|
|
|
const response = await fetch(`${this.apiUrl}/options`, { signal });
|
2023-11-08 22:14:41 +01:00
|
|
|
return await response.json();
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
public async setVaultOptions(
|
|
|
|
options: VaultOptions,
|
|
|
|
apiOptions?: ApiFetchOptions,
|
|
|
|
): Promise<void> {
|
2023-11-08 22:14:41 +01:00
|
|
|
const payload: Record<string, unknown> = {};
|
|
|
|
|
|
|
|
if (options.blob_mode) {
|
|
|
|
const blob_mode: Record<string, unknown> = {};
|
|
|
|
blob_mode[options.blob_mode] = null;
|
|
|
|
payload["blob_mode"] = blob_mode;
|
|
|
|
}
|
|
|
|
|
2024-01-27 14:27:24 +01:00
|
|
|
const signal = this.getAbortSignal(apiOptions);
|
2023-11-08 22:14:41 +01:00
|
|
|
const response = await fetch(`${this.apiUrl}/options`, {
|
|
|
|
method: "PUT",
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
body: JSON.stringify(payload),
|
2024-01-27 14:27:24 +01:00
|
|
|
signal,
|
2023-11-08 22:14:41 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
throw Error(await response.text());
|
|
|
|
}
|
|
|
|
}
|
2024-01-27 14:27:24 +01:00
|
|
|
|
|
|
|
private getAbortSignal(options: ApiFetchOptions | undefined) {
|
|
|
|
const controller = options?.abortController || new AbortController();
|
|
|
|
const timeout = options?.timeout || this.timeout;
|
|
|
|
if (timeout > 0) {
|
|
|
|
setTimeout(() => controller.abort(), timeout);
|
|
|
|
}
|
|
|
|
return controller.signal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ApiFetchOptions {
|
|
|
|
timeout?: number;
|
|
|
|
abortController?: AbortController;
|
2023-11-08 22:14:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export type VaultBlobMode = "Flat" | "Mirror" | "Incoming";
|
|
|
|
export interface VaultOptions {
|
|
|
|
blob_mode: VaultBlobMode;
|
2023-05-21 21:37:29 +02:00
|
|
|
}
|