UNPKG

@fal-ai/client

Version:

The fal.ai client for JavaScript and TypeScript

315 lines 14.3 kB
"use strict"; 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 __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i; function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; } function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FalStream = void 0; exports.createStreamingClient = createStreamingClient; const eventsource_parser_1 = require("eventsource-parser"); const auth_1 = require("./auth"); const request_1 = require("./request"); const response_1 = require("./response"); const CONTENT_TYPE_EVENT_STREAM = "text/event-stream"; const EVENT_STREAM_TIMEOUT = 15 * 1000; /** * The class representing a streaming response. With t */ class FalStream { constructor(endpointId, config, options) { var _a; // support for event listeners this.listeners = new Map(); this.buffer = []; // local state this.currentData = undefined; this.lastEventTimestamp = 0; this.streamClosed = false; this._requestId = null; this.abortController = new AbortController(); this.start = () => __awaiter(this, void 0, void 0, function* () { var _a, _b; const { endpointId, options } = this; const { input, method = "post", connectionMode = "server" } = options; try { if (connectionMode === "client") { // if we are in the browser, we need to get a temporary token // to authenticate the request const token = yield (0, auth_1.getTemporaryAuthToken)(endpointId, this.config); const { fetch } = this.config; const parsedUrl = new URL(this.url); parsedUrl.searchParams.set("fal_jwt_token", token); const response = yield fetch(parsedUrl.toString(), { method: method.toUpperCase(), headers: { accept: (_a = options.accept) !== null && _a !== void 0 ? _a : CONTENT_TYPE_EVENT_STREAM, "content-type": "application/json", }, body: input && method !== "get" ? JSON.stringify(input) : undefined, signal: this.abortController.signal, }); this._requestId = response.headers.get("x-fal-request-id"); return yield this.handleResponse(response); } return yield (0, request_1.dispatchRequest)({ method: method.toUpperCase(), targetUrl: this.url, input, config: this.config, options: { headers: { accept: (_b = options.accept) !== null && _b !== void 0 ? _b : CONTENT_TYPE_EVENT_STREAM, }, responseHandler: (response) => __awaiter(this, void 0, void 0, function* () { this._requestId = response.headers.get("x-fal-request-id"); return yield this.handleResponse(response); }), signal: this.abortController.signal, }, }); } catch (error) { this.handleError(error); } }); this.handleResponse = (response) => __awaiter(this, void 0, void 0, function* () { var _a, _b; if (!response.ok) { try { // we know the response failed, call the response handler // so the exception gets converted to ApiError correctly yield (0, response_1.defaultResponseHandler)(response); } catch (error) { this.emit("error", error); } return; } const body = response.body; if (!body) { this.emit("error", new response_1.ApiError({ message: "Response body is empty.", status: 400, body: undefined, })); return; } const isEventStream = ((_a = response.headers.get("content-type")) !== null && _a !== void 0 ? _a : "").startsWith(CONTENT_TYPE_EVENT_STREAM); // any response that is not a text/event-stream will be handled as a binary stream if (!isEventStream) { const reader = body.getReader(); const emitRawChunk = () => { reader.read().then(({ done, value }) => { if (done) { this.emit("done", this.currentData); return; } this.currentData = value; this.emit("data", value); emitRawChunk(); }); }; emitRawChunk(); return; } const decoder = new TextDecoder("utf-8"); const reader = response.body.getReader(); const parser = (0, eventsource_parser_1.createParser)((event) => { if (event.type === "event") { const data = event.data; try { const parsedData = JSON.parse(data); this.buffer.push(parsedData); this.currentData = parsedData; this.emit("data", parsedData); // also emit 'message'for backwards compatibility this.emit("message", parsedData); } catch (e) { this.emit("error", e); } } }); const timeout = (_b = this.options.timeout) !== null && _b !== void 0 ? _b : EVENT_STREAM_TIMEOUT; const readPartialResponse = () => __awaiter(this, void 0, void 0, function* () { const { value, done } = yield reader.read(); this.lastEventTimestamp = Date.now(); parser.feed(decoder.decode(value)); if (Date.now() - this.lastEventTimestamp > timeout) { this.emit("error", new response_1.ApiError({ message: `Event stream timed out after ${(timeout / 1000).toFixed(0)} seconds with no messages.`, status: 408, })); } if (!done) { readPartialResponse().catch(this.handleError); } else { this.emit("done", this.currentData); } }); readPartialResponse().catch(this.handleError); return; }); this.handleError = (error) => { var _a; // In case AbortError is thrown but the signal is marked as aborted // it means the user called abort() and we should not emit an error // as it's expected behavior // See note on: https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort if (error.name === "AbortError" || this.signal.aborted) { return; } const apiError = error instanceof response_1.ApiError ? error : new response_1.ApiError({ message: (_a = error.message) !== null && _a !== void 0 ? _a : "An unknown error occurred", status: 500, }); this.emit("error", apiError); return; }; this.on = (type, listener) => { var _a; if (!this.listeners.has(type)) { this.listeners.set(type, []); } (_a = this.listeners.get(type)) === null || _a === void 0 ? void 0 : _a.push(listener); }; this.emit = (type, event) => { const listeners = this.listeners.get(type) || []; for (const listener of listeners) { listener(event); } }; /** * Gets a reference to the `Promise` that indicates whether the streaming * is done or not. Developers should always call this in their apps to ensure * the request is over. * * An alternative to this, is to use `on('done')` in case your application * architecture works best with event listeners. * * @returns the promise that resolves when the request is done. */ this.done = () => __awaiter(this, void 0, void 0, function* () { return this.donePromise; }); /** * Aborts the streaming request. * * **Note:** This method is noop in case the request is already done. * * @param reason optional cause for aborting the request. */ this.abort = (reason) => { if (!this.streamClosed) { this.abortController.abort(reason); } }; this.endpointId = endpointId; this.config = config; this.url = (_a = options.url) !== null && _a !== void 0 ? _a : (0, request_1.buildUrl)(endpointId, { path: "/stream", query: options.queryParams, }); this.options = options; this.donePromise = new Promise((resolve, reject) => { if (this.streamClosed) { reject(new response_1.ApiError({ message: "Streaming connection is already closed.", status: 400, body: undefined, })); } this.signal.addEventListener("abort", () => { var _a; resolve((_a = this.currentData) !== null && _a !== void 0 ? _a : {}); }); this.on("done", (data) => { this.streamClosed = true; resolve(data); }); this.on("error", (error) => { this.streamClosed = true; reject(error); }); }); // if a abort signal was passed, sync it with the internal one if (options.signal) { options.signal.addEventListener("abort", () => { this.abortController.abort(); }); } // start the streaming request this.start().catch(this.handleError); } [Symbol.asyncIterator]() { return __asyncGenerator(this, arguments, function* _a() { let running = true; const stopAsyncIterator = () => (running = false); this.on("error", stopAsyncIterator); this.on("done", stopAsyncIterator); while (running || this.buffer.length > 0) { const data = this.buffer.shift(); if (data) { yield yield __await(data); } // the short timeout ensures the while loop doesn't block other // frames getting executed concurrently yield __await(new Promise((resolve) => setTimeout(resolve, 16))); } }); } /** * Gets the `AbortSignal` instance that can be used to listen for abort events. * * **Note:** this signal is internal to the `FalStream` instance. If you pass your * own abort signal, the `FalStream` will listen to it and abort it appropriately. * * @returns the `AbortSignal` instance. * @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal */ get signal() { return this.abortController.signal; } /** * Gets the request id of the streaming request. * * @returns the request id. */ get requestId() { return this._requestId; } } exports.FalStream = FalStream; function createStreamingClient({ config, storage, }) { return { stream(endpointId, options) { return __awaiter(this, void 0, void 0, function* () { const input = options.input ? yield storage.transformInput(options.input) : undefined; return new FalStream(endpointId, config, Object.assign(Object.assign({}, options), { input: input })); }); }, }; } //# sourceMappingURL=streaming.js.map