@fal-ai/serverless-client
Version:
Deprecation note: this library has been deprecated in favor of @fal-ai/client
264 lines • 12.2 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.queue = void 0;
exports.buildUrl = buildUrl;
exports.send = send;
exports.run = run;
exports.subscribe = subscribe;
const request_1 = require("./request");
const storage_1 = require("./storage");
const streaming_1 = require("./streaming");
const utils_1 = require("./utils");
/**
* Builds the final url to run the function based on its `id` or alias and
* a the options from `RunOptions<Input>`.
*
* @private
* @param id the function id or alias
* @param options the run options
* @returns the final url to run the function
*/
function buildUrl(id, options = {}) {
var _a, _b;
const method = ((_a = options.method) !== null && _a !== void 0 ? _a : "post").toLowerCase();
const path = ((_b = options.path) !== null && _b !== void 0 ? _b : "").replace(/^\//, "").replace(/\/{2,}/, "/");
const input = options.input;
const params = Object.assign(Object.assign({}, (options.query || {})), (method === "get" ? input : {}));
const queryParams = Object.keys(params).length > 0
? `?${new URLSearchParams(params).toString()}`
: "";
// if a fal url is passed, just use it
if ((0, utils_1.isValidUrl)(id)) {
const url = id.endsWith("/") ? id : `${id}/`;
return `${url}${path}${queryParams}`;
}
const appId = (0, utils_1.ensureAppIdFormat)(id);
const subdomain = options.subdomain ? `${options.subdomain}.` : "";
const url = `https://${subdomain}fal.run/${appId}/${path}`;
return `${url.replace(/\/$/, "")}${queryParams}`;
}
function send(id_1) {
return __awaiter(this, arguments, void 0, function* (id, options = {}) {
var _a;
const input = options.input && options.autoUpload !== false
? yield storage_1.storageImpl.transformInput(options.input)
: options.input;
return (0, request_1.dispatchRequest)((_a = options.method) !== null && _a !== void 0 ? _a : "post", buildUrl(id, options), input);
});
}
/**
* Runs a fal serverless function identified by its `id`.
*
* @param id the registered function revision id or alias.
* @returns the remote function output
*/
function run(id_1) {
return __awaiter(this, arguments, void 0, function* (id, options = {}) {
return send(id, options);
});
}
const DEFAULT_POLL_INTERVAL = 500;
/**
* The fal run queue module. It allows to submit a function to the queue and get its result
* on a separate call. This is useful for long running functions that can be executed
* asynchronously and not .
*/
exports.queue = {
submit(endpointId, options) {
return __awaiter(this, void 0, void 0, function* () {
const { webhookUrl, path = "" } = options, runOptions = __rest(options, ["webhookUrl", "path"]);
return send(endpointId, Object.assign(Object.assign({}, runOptions), { subdomain: "queue", method: "post", path: path, query: webhookUrl ? { fal_webhook: webhookUrl } : undefined }));
});
},
status(endpointId_1, _a) {
return __awaiter(this, arguments, void 0, function* (endpointId, { requestId, logs = false }) {
const appId = (0, utils_1.parseAppId)(endpointId);
const prefix = appId.namespace ? `${appId.namespace}/` : "";
return send(`${prefix}${appId.owner}/${appId.alias}`, {
subdomain: "queue",
method: "get",
path: `/requests/${requestId}/status`,
input: {
logs: logs ? "1" : "0",
},
});
});
},
streamStatus(endpointId_1, _a) {
return __awaiter(this, arguments, void 0, function* (endpointId, { requestId, logs = false, connectionMode }) {
const appId = (0, utils_1.parseAppId)(endpointId);
const prefix = appId.namespace ? `${appId.namespace}/` : "";
const queryParams = {
logs: logs ? "1" : "0",
};
const url = buildUrl(`${prefix}${appId.owner}/${appId.alias}`, {
subdomain: "queue",
path: `/requests/${requestId}/status/stream`,
query: queryParams,
});
return new streaming_1.FalStream(endpointId, {
url,
method: "get",
connectionMode,
queryParams,
});
});
},
subscribeToStatus(endpointId, options) {
return __awaiter(this, void 0, void 0, function* () {
const requestId = options.requestId;
const timeout = options.timeout;
let timeoutId = undefined;
const handleCancelError = () => {
// Ignore errors as the client will follow through with the timeout
// regardless of the server response. In case cancelation fails, we
// still want to reject the promise and consider the client call canceled.
};
if (options.mode === "streaming") {
const status = yield exports.queue.streamStatus(endpointId, {
requestId,
logs: options.logs,
connectionMode: "connectionMode" in options
? options.connectionMode
: undefined,
});
const logs = [];
if (timeout) {
timeoutId = setTimeout(() => {
status.abort();
exports.queue.cancel(endpointId, { requestId }).catch(handleCancelError);
// TODO this error cannot bubble up to the user since it's thrown in
// a closure in the global scope due to setTimeout behavior.
// User will get a platform error instead. We should find a way to
// make this behavior aligned with polling.
throw new Error(`Client timed out waiting for the request to complete after ${timeout}ms`);
}, timeout);
}
status.on("data", (data) => {
if (options.onQueueUpdate) {
// accumulate logs to match previous polling behavior
if ("logs" in data &&
Array.isArray(data.logs) &&
data.logs.length > 0) {
logs.push(...data.logs);
}
options.onQueueUpdate("logs" in data ? Object.assign(Object.assign({}, data), { logs }) : data);
}
});
const doneStatus = yield status.done();
if (timeoutId) {
clearTimeout(timeoutId);
}
return doneStatus;
}
// default to polling until status streaming is stable and faster
return new Promise((resolve, reject) => {
var _a;
let pollingTimeoutId;
// type resolution isn't great in this case, so check for its presence
// and and type so the typechecker behaves as expected
const pollInterval = "pollInterval" in options && typeof options.pollInterval === "number"
? ((_a = options.pollInterval) !== null && _a !== void 0 ? _a : DEFAULT_POLL_INTERVAL)
: DEFAULT_POLL_INTERVAL;
const clearScheduledTasks = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (pollingTimeoutId) {
clearTimeout(pollingTimeoutId);
}
};
if (timeout) {
timeoutId = setTimeout(() => {
clearScheduledTasks();
exports.queue.cancel(endpointId, { requestId }).catch(handleCancelError);
reject(new Error(`Client timed out waiting for the request to complete after ${timeout}ms`));
}, timeout);
}
const poll = () => __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const requestStatus = yield exports.queue.status(endpointId, {
requestId,
logs: (_a = options.logs) !== null && _a !== void 0 ? _a : false,
});
if (options.onQueueUpdate) {
options.onQueueUpdate(requestStatus);
}
if (requestStatus.status === "COMPLETED") {
clearScheduledTasks();
resolve(requestStatus);
return;
}
pollingTimeoutId = setTimeout(poll, pollInterval);
}
catch (error) {
clearScheduledTasks();
reject(error);
}
});
poll().catch(reject);
});
});
},
result(endpointId_1, _a) {
return __awaiter(this, arguments, void 0, function* (endpointId, { requestId }) {
const appId = (0, utils_1.parseAppId)(endpointId);
const prefix = appId.namespace ? `${appId.namespace}/` : "";
return send(`${prefix}${appId.owner}/${appId.alias}`, {
subdomain: "queue",
method: "get",
path: `/requests/${requestId}`,
});
});
},
cancel(endpointId_1, _a) {
return __awaiter(this, arguments, void 0, function* (endpointId, { requestId }) {
const appId = (0, utils_1.parseAppId)(endpointId);
const prefix = appId.namespace ? `${appId.namespace}/` : "";
yield send(`${prefix}${appId.owner}/${appId.alias}`, {
subdomain: "queue",
method: "put",
path: `/requests/${requestId}/cancel`,
});
});
},
};
/**
* Subscribes to updates for a specific request in the queue.
*
* @param endpointId - The ID of the function web endpoint.
* @param options - Options to configure how the request is run and how updates are received.
* @returns A promise that resolves to the result of the request once it's completed.
*/
function subscribe(endpointId_1) {
return __awaiter(this, arguments, void 0, function* (endpointId, options = {}) {
const { request_id: requestId } = yield exports.queue.submit(endpointId, options);
if (options.onEnqueue) {
options.onEnqueue(requestId);
}
yield exports.queue.subscribeToStatus(endpointId, Object.assign({ requestId }, options));
return exports.queue.result(endpointId, { requestId });
});
}
//# sourceMappingURL=function.js.map