From edef36310b4c93df4bbd10d71958c1b2ccccab64 Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Wed, 25 Sep 2024 15:53:08 -0700 Subject: [PATCH 01/11] chore(client): rename function module --- libs/client/src/{function.spec.ts => client.spec.ts} | 2 +- libs/client/src/{function.ts => client.ts} | 0 libs/client/src/index.ts | 2 +- libs/client/src/streaming.ts | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename libs/client/src/{function.spec.ts => client.spec.ts} (86%) rename libs/client/src/{function.ts => client.ts} (100%) diff --git a/libs/client/src/function.spec.ts b/libs/client/src/client.spec.ts similarity index 86% rename from libs/client/src/function.spec.ts rename to libs/client/src/client.spec.ts index 9392037..cf8bea9 100644 --- a/libs/client/src/function.spec.ts +++ b/libs/client/src/client.spec.ts @@ -1,4 +1,4 @@ -import { buildUrl } from "./function"; +import { buildUrl } from "./client"; describe("The function test suite", () => { it("should build the URL with a function username/app-alias", () => { diff --git a/libs/client/src/function.ts b/libs/client/src/client.ts similarity index 100% rename from libs/client/src/function.ts rename to libs/client/src/client.ts diff --git a/libs/client/src/index.ts b/libs/client/src/index.ts index f8bcdb7..1d98e49 100644 --- a/libs/client/src/index.ts +++ b/libs/client/src/index.ts @@ -1,5 +1,5 @@ +export { queue, run, subscribe } from "./client"; export { config, getConfig } from "./config"; -export { queue, run, subscribe } from "./function"; export { withMiddleware, withProxy } from "./middleware"; export type { RequestMiddleware } from "./middleware"; export { realtimeImpl as realtime } from "./realtime"; diff --git a/libs/client/src/streaming.ts b/libs/client/src/streaming.ts index 2d972f4..db5a93d 100644 --- a/libs/client/src/streaming.ts +++ b/libs/client/src/streaming.ts @@ -1,7 +1,7 @@ import { createParser } from "eventsource-parser"; import { getTemporaryAuthToken } from "./auth"; +import { buildUrl } from "./client"; import { getConfig } from "./config"; -import { buildUrl } from "./function"; import { dispatchRequest } from "./request"; import { ApiError, defaultResponseHandler } from "./response"; import { storageImpl } from "./storage"; From 27ac447953a8bf693f925eaa0ceaf0d2fc41b259 Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Wed, 25 Sep 2024 21:30:45 -0700 Subject: [PATCH 02/11] chore: allow client to be created multiple times singleton client is not the default but it still present as a compatibility layer --- README.md | 10 +- apps/demo-express-app/src/main.ts | 4 +- .../app/api/fal/proxy/route.ts | 2 +- .../app/camera-turbo/page.tsx | 4 +- .../app/comfy/image-to-image/page.tsx | 8 +- .../app/comfy/image-to-video/page.tsx | 8 +- .../app/comfy/text-to-image/page.tsx | 8 +- apps/demo-nextjs-app-router/app/page.tsx | 2 +- .../demo-nextjs-app-router/app/queue/page.tsx | 4 +- .../app/realtime/page.tsx | 4 +- .../app/streaming/page.tsx | 4 +- .../app/whisper/page.tsx | 4 +- .../pages/api/fal/proxy.ts | 2 +- apps/demo-nextjs-page-router/pages/index.tsx | 2 +- libs/client/README.md | 4 +- libs/client/package.json | 6 +- libs/client/src/auth.ts | 22 +- libs/client/src/client.spec.ts | 2 +- libs/client/src/client.ts | 556 ++---------------- libs/client/src/config.spec.ts | 5 +- libs/client/src/config.ts | 21 +- libs/client/src/index.ts | 56 +- libs/client/src/queue.ts | 415 +++++++++++++ libs/client/src/realtime.ts | 349 +++++------ libs/client/src/request.ts | 58 +- libs/client/src/runtime.spec.ts | 2 +- libs/client/src/storage.ts | 99 ++-- libs/client/src/streaming.ts | 94 ++- libs/client/src/types.ts | 42 +- libs/client/src/utils.spec.ts | 14 +- libs/client/src/utils.ts | 6 +- libs/proxy/README.md | 2 +- libs/proxy/package.json | 4 +- tsconfig.base.json | 8 +- 34 files changed, 980 insertions(+), 851 deletions(-) create mode 100644 libs/client/src/queue.ts diff --git a/README.md b/README.md index 8b384ea..753af46 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # The fal.ai JS client -![@fal-ai/serverless-client npm package](https://img.shields.io/npm/v/@fal-ai/serverless-client?color=%237527D7&label=client&style=flat-square) +![@fal-ai/client npm package](https://img.shields.io/npm/v/@fal-ai/client?color=%237527D7&label=client&style=flat-square) ![@fal-ai/serverless-proxy npm package](https://img.shields.io/npm/v/@fal-ai/serverless-proxy?color=%237527D7&label=proxy&style=flat-square) ![Build](https://img.shields.io/github/actions/workflow/status/fal-ai/fal-js/build.yml?style=flat-square) ![License](https://img.shields.io/github/license/fal-ai/fal-js?style=flat-square) @@ -11,7 +11,7 @@ The fal serverless JavaScript/TypeScript Client is a robust and user-friendly li ## Getting Started -The `@fal-ai/serverless-client` library serves as a client for fal serverless Python functions. For guidance on creating your functions, refer to the [quickstart guide](https://fal.ai/docs). +The `@fal-ai/client` library serves as a client for fal serverless Python functions. For guidance on creating your functions, refer to the [quickstart guide](https://fal.ai/docs). ### Client Library @@ -22,12 +22,12 @@ This client library is crafted as a lightweight layer atop platform standards li 1. Install the client library ```sh - npm install --save @fal-ai/serverless-client + npm install --save @fal-ai/client ``` 2. Start by configuring your credentials: ```ts - import * as fal from "@fal-ai/serverless-client"; + import { fal } from "@fal-ai/client"; fal.config({ // Can also be auto-configured using environment variables: @@ -60,7 +60,7 @@ For example, if you are using Next.js, you can: ``` 3. Configure the client to use the proxy: ```ts - import * as fal from "@fal-ai/serverless-client"; + import { fal } from "@fal-ai/client"; fal.config({ proxyUrl: "/api/fal/proxy", }); diff --git a/apps/demo-express-app/src/main.ts b/apps/demo-express-app/src/main.ts index 5b19b5f..5d63e92 100644 --- a/apps/demo-express-app/src/main.ts +++ b/apps/demo-express-app/src/main.ts @@ -3,8 +3,8 @@ * This is only a minimal backend to get started. */ -import * as fal from "@fal-ai/serverless-client"; -import * as falProxy from "@fal-ai/serverless-proxy/express"; +import { fal } from "@fal-ai/client"; +import * as falProxy from "@fal-ai/server-proxy/express"; import cors from "cors"; import { configDotenv } from "dotenv"; import express from "express"; diff --git a/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts b/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts index db10d70..e5ea428 100644 --- a/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts +++ b/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts @@ -1,3 +1,3 @@ -import { route } from "@fal-ai/serverless-proxy/nextjs"; +import { route } from "@fal-ai/server-proxy/nextjs"; export const { GET, POST, PUT } = route; diff --git a/apps/demo-nextjs-app-router/app/camera-turbo/page.tsx b/apps/demo-nextjs-app-router/app/camera-turbo/page.tsx index ce74a8d..8f61e79 100644 --- a/apps/demo-nextjs-app-router/app/camera-turbo/page.tsx +++ b/apps/demo-nextjs-app-router/app/camera-turbo/page.tsx @@ -1,10 +1,10 @@ /* eslint-disable @next/next/no-img-element */ "use client"; -import * as fal from "@fal-ai/serverless-client"; +import { createFalClient } from "@fal-ai/client"; import { MutableRefObject, useEffect, useRef, useState } from "react"; -fal.config({ +const fal = createFalClient({ proxyUrl: "/api/fal/proxy", }); diff --git a/apps/demo-nextjs-app-router/app/comfy/image-to-image/page.tsx b/apps/demo-nextjs-app-router/app/comfy/image-to-image/page.tsx index c8e9ab6..60f2761 100644 --- a/apps/demo-nextjs-app-router/app/comfy/image-to-image/page.tsx +++ b/apps/demo-nextjs-app-router/app/comfy/image-to-image/page.tsx @@ -1,16 +1,13 @@ "use client"; -import * as fal from "@fal-ai/serverless-client"; +import { createFalClient } from "@fal-ai/client"; import { useMemo, useState } from "react"; -// @snippet:start(client.config) -fal.config({ +const fal = createFalClient({ proxyUrl: "/api/fal/proxy", // the built-int nextjs proxy // proxyUrl: 'http://localhost:3333/api/fal/proxy', // or your own external proxy }); -// @snippet:end -// @snippet:start(client.result.type) type Image = { filename: string; subfolder: string; @@ -23,7 +20,6 @@ type Result = { outputs: Record[]; images: Image[]; }; -// @snippet:end type ErrorProps = { error: any; diff --git a/apps/demo-nextjs-app-router/app/comfy/image-to-video/page.tsx b/apps/demo-nextjs-app-router/app/comfy/image-to-video/page.tsx index 11fb365..dc72267 100644 --- a/apps/demo-nextjs-app-router/app/comfy/image-to-video/page.tsx +++ b/apps/demo-nextjs-app-router/app/comfy/image-to-video/page.tsx @@ -1,16 +1,13 @@ "use client"; -import * as fal from "@fal-ai/serverless-client"; +import { createFalClient } from "@fal-ai/client"; import { useMemo, useState } from "react"; -// @snippet:start(client.config) -fal.config({ +const fal = createFalClient({ proxyUrl: "/api/fal/proxy", // the built-int nextjs proxy // proxyUrl: 'http://localhost:3333/api/fal/proxy', // or your own external proxy }); -// @snippet:end -// @snippet:start(client.result.type) type Image = { filename: string; subfolder: string; @@ -23,7 +20,6 @@ type Result = { outputs: Record[]; images: Image[]; }; -// @snippet:end type ErrorProps = { error: any; diff --git a/apps/demo-nextjs-app-router/app/comfy/text-to-image/page.tsx b/apps/demo-nextjs-app-router/app/comfy/text-to-image/page.tsx index 21dd7d4..8c9d0f9 100644 --- a/apps/demo-nextjs-app-router/app/comfy/text-to-image/page.tsx +++ b/apps/demo-nextjs-app-router/app/comfy/text-to-image/page.tsx @@ -1,16 +1,13 @@ "use client"; -import * as fal from "@fal-ai/serverless-client"; +import { createFalClient } from "@fal-ai/client"; import { useMemo, useState } from "react"; -// @snippet:start(client.config) -fal.config({ +const fal = createFalClient({ proxyUrl: "/api/fal/proxy", // the built-int nextjs proxy // proxyUrl: 'http://localhost:3333/api/fal/proxy', // or your own external proxy }); -// @snippet:end -// @snippet:start(client.result.type) type Image = { filename: string; subfolder: string; @@ -23,7 +20,6 @@ type Result = { outputs: Record[]; images: Image[]; }; -// @snippet:end type ErrorProps = { error: any; diff --git a/apps/demo-nextjs-app-router/app/page.tsx b/apps/demo-nextjs-app-router/app/page.tsx index 2b227f6..a1ac1ce 100644 --- a/apps/demo-nextjs-app-router/app/page.tsx +++ b/apps/demo-nextjs-app-router/app/page.tsx @@ -1,6 +1,6 @@ "use client"; -import * as fal from "@fal-ai/serverless-client"; +import { fal } from "@fal-ai/client"; import { useMemo, useState } from "react"; // @snippet:start(client.config) diff --git a/apps/demo-nextjs-app-router/app/queue/page.tsx b/apps/demo-nextjs-app-router/app/queue/page.tsx index 68a4513..ac477f7 100644 --- a/apps/demo-nextjs-app-router/app/queue/page.tsx +++ b/apps/demo-nextjs-app-router/app/queue/page.tsx @@ -1,9 +1,9 @@ "use client"; -import * as fal from "@fal-ai/serverless-client"; +import { createFalClient } from "@fal-ai/client"; import { useState } from "react"; -fal.config({ +const fal = createFalClient({ proxyUrl: "/api/fal/proxy", }); diff --git a/apps/demo-nextjs-app-router/app/realtime/page.tsx b/apps/demo-nextjs-app-router/app/realtime/page.tsx index dc6cda9..58df97e 100644 --- a/apps/demo-nextjs-app-router/app/realtime/page.tsx +++ b/apps/demo-nextjs-app-router/app/realtime/page.tsx @@ -1,11 +1,11 @@ "use client"; /* eslint-disable @next/next/no-img-element */ -import * as fal from "@fal-ai/serverless-client"; +import { createFalClient } from "@fal-ai/client"; import { ChangeEvent, useRef, useState } from "react"; import { DrawingCanvas } from "../../components/drawing"; -fal.config({ +const fal = createFalClient({ proxyUrl: "/api/fal/proxy", }); diff --git a/apps/demo-nextjs-app-router/app/streaming/page.tsx b/apps/demo-nextjs-app-router/app/streaming/page.tsx index 3335b4d..42e9cf9 100644 --- a/apps/demo-nextjs-app-router/app/streaming/page.tsx +++ b/apps/demo-nextjs-app-router/app/streaming/page.tsx @@ -1,9 +1,9 @@ "use client"; -import * as fal from "@fal-ai/serverless-client"; +import { createFalClient } from "@fal-ai/client"; import { useState } from "react"; -fal.config({ +const fal = createFalClient({ proxyUrl: "/api/fal/proxy", }); diff --git a/apps/demo-nextjs-app-router/app/whisper/page.tsx b/apps/demo-nextjs-app-router/app/whisper/page.tsx index b40afb8..561daa8 100644 --- a/apps/demo-nextjs-app-router/app/whisper/page.tsx +++ b/apps/demo-nextjs-app-router/app/whisper/page.tsx @@ -1,9 +1,9 @@ "use client"; -import * as fal from "@fal-ai/serverless-client"; +import { createFalClient } from "@fal-ai/client"; import { useCallback, useMemo, useState } from "react"; -fal.config({ +const fal = createFalClient({ // credentials: 'FAL_KEY_ID:FAL_KEY_SECRET', proxyUrl: "/api/fal/proxy", }); diff --git a/apps/demo-nextjs-page-router/pages/api/fal/proxy.ts b/apps/demo-nextjs-page-router/pages/api/fal/proxy.ts index ca598dc..4d149bc 100644 --- a/apps/demo-nextjs-page-router/pages/api/fal/proxy.ts +++ b/apps/demo-nextjs-page-router/pages/api/fal/proxy.ts @@ -1,3 +1,3 @@ // @snippet:start("client.proxy.nextjs") -export { handler as default } from "@fal-ai/serverless-proxy/nextjs"; +export { handler as default } from "@fal-ai/server-proxy/nextjs"; // @snippet:end diff --git a/apps/demo-nextjs-page-router/pages/index.tsx b/apps/demo-nextjs-page-router/pages/index.tsx index 896174c..f0219e1 100644 --- a/apps/demo-nextjs-page-router/pages/index.tsx +++ b/apps/demo-nextjs-page-router/pages/index.tsx @@ -1,4 +1,4 @@ -import * as fal from "@fal-ai/serverless-client"; +import { fal } from "@fal-ai/client"; import { useMemo, useState } from "react"; // @snippet:start(client.config) diff --git a/libs/client/README.md b/libs/client/README.md index 55feea0..d348fa8 100644 --- a/libs/client/README.md +++ b/libs/client/README.md @@ -1,6 +1,6 @@ # fal.ai JavaScript/TypeScript client library -![@fal-ai/serverless-client npm package](https://img.shields.io/npm/v/@fal-ai/serverless-client?color=%237527D7&label=%40fal-ai%2Fserverless-client&style=flat-square) +![@fal-ai/client npm package](https://img.shields.io/npm/v/@fal-ai/client?color=%237527D7&label=%40fal-ai%2Fclient&style=flat-square) ## Introduction @@ -11,7 +11,7 @@ The `fal.ai` JavaScript Client Library provides a seamless way to interact with Before diving into the client-specific features, ensure you've set up your credentials: ```ts -import * as fal from "@fal-ai/serverless-client"; +import { fal } from "@fal-ai/client"; fal.config({ // Can also be auto-configured using environment variables: diff --git a/libs/client/package.json b/libs/client/package.json index a9903f1..ce307bf 100644 --- a/libs/client/package.json +++ b/libs/client/package.json @@ -1,7 +1,7 @@ { - "name": "@fal-ai/serverless-client", - "description": "The fal serverless JS/TS client", - "version": "0.14.3", + "name": "@fal-ai/client", + "description": "The fal.ai client for JavaScript and TypeScript", + "version": "1.0.0", "license": "MIT", "repository": { "type": "git", diff --git a/libs/client/src/auth.ts b/libs/client/src/auth.ts index b8ad4e5..da61035 100644 --- a/libs/client/src/auth.ts +++ b/libs/client/src/auth.ts @@ -1,22 +1,26 @@ -import { getRestApiUrl } from "./config"; +import { getRestApiUrl, RequiredConfig } from "./config"; import { dispatchRequest } from "./request"; -import { parseAppId } from "./utils"; +import { parseEndpointId } from "./utils"; export const TOKEN_EXPIRATION_SECONDS = 120; /** * Get a token to connect to the realtime endpoint. */ -export async function getTemporaryAuthToken(app: string): Promise { - const appId = parseAppId(app); - const token: string | object = await dispatchRequest( - "POST", - `${getRestApiUrl()}/tokens/`, - { +export async function getTemporaryAuthToken( + app: string, + config: RequiredConfig, +): Promise { + const appId = parseEndpointId(app); + const token: string | object = await dispatchRequest({ + method: "POST", + targetUrl: `${getRestApiUrl()}/tokens/`, + config, + input: { allowed_apps: [appId.alias], token_expiration: TOKEN_EXPIRATION_SECONDS, }, - ); + }); // keep this in case the response was wrapped (old versions of the proxy do that) // should be safe to remove in the future if (typeof token !== "string" && token["detail"]) { diff --git a/libs/client/src/client.spec.ts b/libs/client/src/client.spec.ts index cf8bea9..5087e71 100644 --- a/libs/client/src/client.spec.ts +++ b/libs/client/src/client.spec.ts @@ -1,4 +1,4 @@ -import { buildUrl } from "./client"; +import { buildUrl } from "./request"; describe("The function test suite", () => { it("should build the URL with a function username/app-alias", () => { diff --git a/libs/client/src/client.ts b/libs/client/src/client.ts index 4b59705..2c15f7e 100644 --- a/libs/client/src/client.ts +++ b/libs/client/src/client.ts @@ -1,521 +1,79 @@ -import { dispatchRequest } from "./request"; -import { storageImpl } from "./storage"; -import { FalStream, StreamingConnectionMode } from "./streaming"; -import { - CompletedQueueStatus, - EnqueueResult, - QueueStatus, - RequestLog, -} from "./types"; -import { ensureAppIdFormat, isValidUrl, parseAppId } from "./utils"; +import { Config, createConfig } from "./config"; +import { createQueueClient, QueueClient, QueueSubscribeOptions } from "./queue"; +import { createRealtimeClient, RealtimeClient } from "./realtime"; +import { buildUrl, dispatchRequest } from "./request"; +import { createStorageClient, StorageClient } from "./storage"; +import { createStreamingClient, StreamingClient } from "./streaming"; +import { RunOptions } from "./types"; -/** - * The function input and other configuration when running - * the function, such as the HTTP method to use. - */ -type RunOptions = { - /** - * The path to the function, if any. Defaults to ``. - * @deprecated Pass the path as part of the app id itself, e.g. `fal-ai/sdxl/image-to-image` - */ - readonly path?: string; +export interface FalClient { + readonly queue: QueueClient; - /** - * The function input. It will be submitted either as query params - * or the body payload, depending on the `method`. - */ - readonly input?: Input; + readonly realtime: RealtimeClient; - /** - * The HTTP method, defaults to `post`; - */ - readonly method?: "get" | "post" | "put" | "delete" | string; + readonly storage: StorageClient; - /** - * If `true`, the function will automatically upload any files - * (i.e. instances of `Blob`). - * - * This is enabled by default. You can disable it by setting it to `false`. - */ - readonly autoUpload?: boolean; -}; + readonly streaming: StreamingClient; -type ExtraOptions = { /** - * If `true`, the function will use the queue to run the function - * asynchronously and return the result in a separate call. This - * influences how the URL is built. - */ - readonly subdomain?: string; - - /** - * The query parameters to include in the URL. - */ - readonly query?: Record; -}; - -/** - * Builds the final url to run the function based on its `id` or alias and - * a the options from `RunOptions`. - * - * @private - * @param id the function id or alias - * @param options the run options - * @returns the final url to run the function - */ -export function buildUrl( - id: string, - options: RunOptions & ExtraOptions = {}, -): string { - const method = (options.method ?? "post").toLowerCase(); - const path = (options.path ?? "").replace(/^\//, "").replace(/\/{2,}/, "/"); - const input = options.input; - const params = { - ...(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 (isValidUrl(id)) { - const url = id.endsWith("/") ? id : `${id}/`; - return `${url}${path}${queryParams}`; - } - - const appId = ensureAppIdFormat(id); - const subdomain = options.subdomain ? `${options.subdomain}.` : ""; - const url = `https://${subdomain}fal.run/${appId}/${path}`; - return `${url.replace(/\/$/, "")}${queryParams}`; -} - -export async function send( - id: string, - options: RunOptions & ExtraOptions = {}, -): Promise { - const input = - options.input && options.autoUpload !== false - ? await storageImpl.transformInput(options.input) - : options.input; - return dispatchRequest( - options.method ?? "post", - buildUrl(id, options), - input as Input, - ); -} - -export type QueueStatusSubscriptionOptions = QueueStatusOptions & - Omit; - -/** - * Runs a fal serverless function identified by its `id`. - * - * @param id the registered function revision id or alias. - * @returns the remote function output - */ -export async function run( - id: string, - options: RunOptions = {}, -): Promise { - return send(id, options); -} - -type TimeoutId = ReturnType | undefined; - -const DEFAULT_POLL_INTERVAL = 500; - -/** - * Options for subscribing to the request queue. - */ -type QueueSubscribeOptions = { - /** - * The mode to use for subscribing to updates. It defaults to `polling`. - * You can also use client-side streaming by setting it to `streaming`. - * - * **Note:** Streaming is currently experimental and once stable, it will - * be the default mode. + * Runs a fal serverless function identified by its `id`. * - * @see pollInterval - */ - mode?: "polling" | "streaming"; - - /** - * Callback function that is called when a request is enqueued. - * @param requestId - The unique identifier for the enqueued request. - */ - onEnqueue?: (requestId: string) => void; - - /** - * Callback function that is called when the status of the queue changes. - * @param status - The current status of the queue. - */ - onQueueUpdate?: (status: QueueStatus) => void; - - /** - * If `true`, the response will include the logs for the request. - * Defaults to `false`. + * @param id the registered function revision id or alias. + * @returns the remote function output */ - logs?: boolean; + run(id: string, options: RunOptions): Promise; /** - * The timeout (in milliseconds) for the request. If the request is not - * completed within this time, the subscription will be cancelled. - * - * Keep in mind that although the client resolves the function on a timeout, - * and will try to cancel the request on the server, the server might not be - * able to cancel the request if it's already running. - * - * Note: currently, the timeout is not enforced and the default is `undefined`. - * This behavior might change in the future. - */ - timeout?: number; - - /** - * The URL to send a webhook notification to when the request is completed. - * @see WebHookResponse - */ - webhookUrl?: string; -} & ( - | { - mode?: "polling"; - /** - * The interval (in milliseconds) at which to poll for updates. - * If not provided, a default value of `500` will be used. - * - * This value is ignored if `mode` is set to `streaming`. - */ - pollInterval?: number; - } - | { - mode: "streaming"; - - /** - * The connection mode to use for streaming updates. It defaults to `server`. - * Set to `client` if your server proxy doesn't support streaming. - */ - connectionMode?: StreamingConnectionMode; - } -); - -/** - * Options for submitting a request to the queue. - */ -type SubmitOptions = RunOptions & { - /** - * The URL to send a webhook notification to when the request is completed. - * @see WebHookResponse - */ - webhookUrl?: string; -}; - -type BaseQueueOptions = { - /** - * The unique identifier for the enqueued request. - */ - requestId: string; -}; - -type QueueStatusOptions = BaseQueueOptions & { - /** - * If `true`, the response will include the logs for the request. - * Defaults to `false`. - */ - logs?: boolean; -}; - -type QueueStatusStreamOptions = QueueStatusOptions & { - /** - * The connection mode to use for streaming updates. It defaults to `server`. - * Set to `client` if your server proxy doesn't support streaming. - */ - connectionMode?: StreamingConnectionMode; -}; - -/** - * Represents a request queue with methods for submitting requests, - * checking their status, retrieving results, and subscribing to updates. - */ -interface Queue { - /** - * Submits a request to the queue. - * - * @param endpointId - The ID of the function web endpoint. - * @param options - Options to configure how the request is run. - * @returns A promise that resolves to the result of enqueuing the request. - */ - submit( - endpointId: string, - options: SubmitOptions, - ): Promise; - - /** - * Retrieves the status of 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. - * @returns A promise that resolves to the status of the request. - */ - status(endpointId: string, options: QueueStatusOptions): Promise; - - /** - * Subscribes to updates for a specific request in the queue using HTTP streaming events. - * - * @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 The streaming object that can be used to listen for updates. - */ - streamStatus( - endpointId: string, - options: QueueStatusStreamOptions, - ): Promise>; - - /** - * Subscribes to updates for a specific request in the queue using polling or streaming. - * See `options.mode` for more details. + * 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 final status of the request. - */ - subscribeToStatus( - endpointId: string, - options: QueueStatusSubscriptionOptions, - ): Promise; - - /** - * Retrieves the result of a specific request from the queue. - * - * @param endpointId - The ID of the function web endpoint. - * @param options - Options to configure how the request is run. - * @returns A promise that resolves to the result of the request. + * @returns A promise that resolves to the result of the request once it's completed. */ - result( + subscribe( endpointId: string, - options: BaseQueueOptions, + options: RunOptions & QueueSubscribeOptions, ): Promise; - /** - * Cancels a 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 once the request is cancelled. - * @throws {Error} If the request cannot be cancelled. - */ - cancel(endpointId: string, options: BaseQueueOptions): Promise; + stream: StreamingClient["stream"]; } -/** - * 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 . - */ -export const queue: Queue = { - async submit( - endpointId: string, - options: SubmitOptions, - ): Promise { - const { webhookUrl, path = "", ...runOptions } = options; - return send(endpointId, { - ...runOptions, - subdomain: "queue", - method: "post", - path: path, - query: webhookUrl ? { fal_webhook: webhookUrl } : undefined, - }); - }, - async status( - endpointId: string, - { requestId, logs = false }: QueueStatusOptions, - ): Promise { - const appId = 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", - }, - }); - }, - - async streamStatus( - endpointId: string, - { requestId, logs = false, connectionMode }: QueueStatusStreamOptions, - ): Promise> { - const appId = 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 FalStream(endpointId, { - url, - method: "get", - connectionMode, - queryParams, - }); - }, - - async subscribeToStatus(endpointId, options): Promise { - const requestId = options.requestId; - const timeout = options.timeout; - let timeoutId: 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 = await queue.streamStatus(endpointId, { - requestId, - logs: options.logs, - connectionMode: - "connectionMode" in options - ? (options.connectionMode as StreamingConnectionMode) - : undefined, +export const createFalClient = (userConfig: Config = {}): FalClient => { + const config = createConfig(userConfig); + const storage = createStorageClient({ config }); + const queue = createQueueClient({ config, storage }); + const streaming = createStreamingClient({ config, storage }); + const realtime = createRealtimeClient({ config }); + return { + queue, + realtime, + storage, + streaming, + stream: streaming.stream, + async run( + id: string, + options: RunOptions = {}, + ): Promise { + const input = options.input + ? await storage.transformInput(options.input) + : undefined; + return dispatchRequest({ + method: options.method, + targetUrl: buildUrl(id, options), + input: input as Input, + config, }); - const logs: RequestLog[] = []; - if (timeout) { - timeoutId = setTimeout(() => { - status.abort(); - 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: QueueStatus) => { - 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 ? { ...data, logs } : data); - } - }); - const doneStatus = await status.done(); - if (timeoutId) { - clearTimeout(timeoutId); - } - return doneStatus as CompletedQueueStatus; - } - // default to polling until status streaming is stable and faster - return new Promise((resolve, reject) => { - let pollingTimeoutId: TimeoutId; - // 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" - ? (options.pollInterval ?? DEFAULT_POLL_INTERVAL) - : DEFAULT_POLL_INTERVAL; - - const clearScheduledTasks = () => { - if (timeoutId) { - clearTimeout(timeoutId); - } - if (pollingTimeoutId) { - clearTimeout(pollingTimeoutId); - } - }; - if (timeout) { - timeoutId = setTimeout(() => { - clearScheduledTasks(); - queue.cancel(endpointId, { requestId }).catch(handleCancelError); - reject( - new Error( - `Client timed out waiting for the request to complete after ${timeout}ms`, - ), - ); - }, timeout); + }, + async subscribe( + endpointId: string, + options: RunOptions & QueueSubscribeOptions = {}, + ): Promise { + const { request_id: requestId } = await queue.submit(endpointId, options); + if (options.onEnqueue) { + options.onEnqueue(requestId); } - const poll = async () => { - try { - const requestStatus = await queue.status(endpointId, { - requestId, - logs: options.logs ?? 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); - }); - }, - - async result( - endpointId: string, - { requestId }: BaseQueueOptions, - ): Promise { - const appId = parseAppId(endpointId); - const prefix = appId.namespace ? `${appId.namespace}/` : ""; - return send(`${prefix}${appId.owner}/${appId.alias}`, { - subdomain: "queue", - method: "get", - path: `/requests/${requestId}`, - }); - }, - - async cancel( - endpointId: string, - { requestId }: BaseQueueOptions, - ): Promise { - const appId = parseAppId(endpointId); - const prefix = appId.namespace ? `${appId.namespace}/` : ""; - await send(`${prefix}${appId.owner}/${appId.alias}`, { - subdomain: "queue", - method: "put", - path: `/requests/${requestId}/cancel`, - }); - }, + await queue.subscribeToStatus(endpointId, { requestId, ...options }); + return queue.result(endpointId, { requestId }); + }, + }; }; - -/** - * 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. - */ -export async function subscribe( - endpointId: string, - options: RunOptions & QueueSubscribeOptions = {}, -): Promise { - const { request_id: requestId } = await queue.submit(endpointId, options); - if (options.onEnqueue) { - options.onEnqueue(requestId); - } - await queue.subscribeToStatus(endpointId, { requestId, ...options }); - return queue.result(endpointId, { requestId }); -} diff --git a/libs/client/src/config.spec.ts b/libs/client/src/config.spec.ts index 73f743c..fc873a6 100644 --- a/libs/client/src/config.spec.ts +++ b/libs/client/src/config.spec.ts @@ -1,12 +1,11 @@ -import { config, getConfig } from "./config"; +import { createConfig } from "./config"; describe("The config test suite", () => { it("should set the config variables accordingly", () => { const newConfig = { credentials: "key-id:key-secret", }; - config(newConfig); - const currentConfig = getConfig(); + const currentConfig = createConfig(newConfig); expect(currentConfig.credentials).toEqual(newConfig.credentials); }); }); diff --git a/libs/client/src/config.ts b/libs/client/src/config.ts index 985adf1..a16984f 100644 --- a/libs/client/src/config.ts +++ b/libs/client/src/config.ts @@ -97,15 +97,13 @@ const DEFAULT_CONFIG: Partial = { responseHandler: defaultResponseHandler, }; -let configuration: RequiredConfig; - /** * Configures the fal serverless client. * * @param config the new configuration. */ -export function config(config: Config) { - configuration = { +export function createConfig(config: Config): RequiredConfig { + let configuration = { ...DEFAULT_CONFIG, ...config, fetch: config.fetch ?? resolveDefaultFetch(), @@ -135,21 +133,6 @@ export function config(config: Config) { "That's not recommended for production use cases.", ); } -} - -/** - * Get the current fal serverless client configuration. - * - * @returns the current client configuration. - */ -export function getConfig(): RequiredConfig { - if (!configuration) { - console.info("Using default configuration for the fal client"); - return { - ...DEFAULT_CONFIG, - fetch: resolveDefaultFetch(), - } as RequiredConfig; - } return configuration; } diff --git a/libs/client/src/index.ts b/libs/client/src/index.ts index 1d98e49..e80875d 100644 --- a/libs/client/src/index.ts +++ b/libs/client/src/index.ts @@ -1,15 +1,59 @@ -export { queue, run, subscribe } from "./client"; -export { config, getConfig } from "./config"; +import { createFalClient, FalClient } from "./client"; +import { Config, createConfig, RequiredConfig } from "./config"; +import { RunOptions } from "./types"; + +export { createFalClient, type FalClient } from "./client"; export { withMiddleware, withProxy } from "./middleware"; export type { RequestMiddleware } from "./middleware"; -export { realtimeImpl as realtime } from "./realtime"; +export type { QueueClient } from "./queue"; +export type { RealtimeClient } from "./realtime"; export { ApiError, ValidationError } from "./response"; export type { ResponseHandler } from "./response"; -export { storageImpl as storage } from "./storage"; -export { stream } from "./streaming"; +export type { StorageClient } from "./storage"; +export type { StreamingClient } from "./streaming"; export type { QueueStatus, ValidationErrorInfo, WebHookResponse, } from "./types"; -export { parseAppId } from "./utils"; +export { parseEndpointId } from "./utils"; + +type SingletonFalClient = { + config(config: Config): void; +} & FalClient; + +/** + * Creates a singleton instance of the client. This is useful as a compatibility + * layer for existing code that uses the clients version prior to 1.0.0. + */ +export const fal: SingletonFalClient = (function createSingletonFalClient() { + let currentConfig: RequiredConfig = createConfig({}); + let currentInstance: FalClient = createFalClient(currentConfig); + return { + config(config: Config) { + currentConfig = createConfig(config); + currentInstance = createFalClient(currentConfig); + }, + get queue() { + return currentInstance.queue; + }, + get realtime() { + return currentInstance.realtime; + }, + get storage() { + return currentInstance.storage; + }, + get streaming() { + return currentInstance.streaming; + }, + run(id: string, options: RunOptions) { + return currentInstance.run(id, options); + }, + subscribe(endpointId: string, options: RunOptions) { + return currentInstance.subscribe(endpointId, options); + }, + stream(endpointId, options) { + return currentInstance.stream(endpointId, options); + }, + } satisfies SingletonFalClient; +})(); diff --git a/libs/client/src/queue.ts b/libs/client/src/queue.ts new file mode 100644 index 0000000..ad8bc94 --- /dev/null +++ b/libs/client/src/queue.ts @@ -0,0 +1,415 @@ +import { RequiredConfig } from "./config"; +import { buildUrl, dispatchRequest } from "./request"; +import { StorageClient } from "./storage"; +import { FalStream, StreamingConnectionMode } from "./streaming"; +import { + CompletedQueueStatus, + EnqueueResult, + QueueStatus, + RequestLog, + RunOptions, +} from "./types"; +import { parseEndpointId } from "./utils"; + +export type QueueStatusSubscriptionOptions = QueueStatusOptions & + Omit; + +type TimeoutId = ReturnType | undefined; + +const DEFAULT_POLL_INTERVAL = 500; + +/** + * Options for subscribing to the request queue. + */ +export type QueueSubscribeOptions = { + /** + * The mode to use for subscribing to updates. It defaults to `polling`. + * You can also use client-side streaming by setting it to `streaming`. + * + * **Note:** Streaming is currently experimental and once stable, it will + * be the default mode. + * + * @see pollInterval + */ + mode?: "polling" | "streaming"; + + /** + * Callback function that is called when a request is enqueued. + * @param requestId - The unique identifier for the enqueued request. + */ + onEnqueue?: (requestId: string) => void; + + /** + * Callback function that is called when the status of the queue changes. + * @param status - The current status of the queue. + */ + onQueueUpdate?: (status: QueueStatus) => void; + + /** + * If `true`, the response will include the logs for the request. + * Defaults to `false`. + */ + logs?: boolean; + + /** + * The timeout (in milliseconds) for the request. If the request is not + * completed within this time, the subscription will be cancelled. + * + * Keep in mind that although the client resolves the function on a timeout, + * and will try to cancel the request on the server, the server might not be + * able to cancel the request if it's already running. + * + * Note: currently, the timeout is not enforced and the default is `undefined`. + * This behavior might change in the future. + */ + timeout?: number; + + /** + * The URL to send a webhook notification to when the request is completed. + * @see WebHookResponse + */ + webhookUrl?: string; +} & ( + | { + mode?: "polling"; + /** + * The interval (in milliseconds) at which to poll for updates. + * If not provided, a default value of `500` will be used. + * + * This value is ignored if `mode` is set to `streaming`. + */ + pollInterval?: number; + } + | { + mode: "streaming"; + + /** + * The connection mode to use for streaming updates. It defaults to `server`. + * Set to `client` if your server proxy doesn't support streaming. + */ + connectionMode?: StreamingConnectionMode; + } +); + +/** + * Options for submitting a request to the queue. + */ +export type SubmitOptions = RunOptions & { + /** + * The URL to send a webhook notification to when the request is completed. + * @see WebHookResponse + */ + webhookUrl?: string; +}; + +type BaseQueueOptions = { + /** + * The unique identifier for the enqueued request. + */ + requestId: string; +}; + +export type QueueStatusOptions = BaseQueueOptions & { + /** + * If `true`, the response will include the logs for the request. + * Defaults to `false`. + */ + logs?: boolean; +}; + +export type QueueStatusStreamOptions = QueueStatusOptions & { + /** + * The connection mode to use for streaming updates. It defaults to `server`. + * Set to `client` if your server proxy doesn't support streaming. + */ + connectionMode?: StreamingConnectionMode; +}; + +/** + * Represents a request queue with methods for submitting requests, + * checking their status, retrieving results, and subscribing to updates. + */ +export interface QueueClient { + /** + * Submits a request to the queue. + * + * @param endpointId - The ID of the function web endpoint. + * @param options - Options to configure how the request is run. + * @returns A promise that resolves to the result of enqueuing the request. + */ + submit( + endpointId: string, + options: SubmitOptions, + ): Promise; + + /** + * Retrieves the status of 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. + * @returns A promise that resolves to the status of the request. + */ + status(endpointId: string, options: QueueStatusOptions): Promise; + + /** + * Subscribes to updates for a specific request in the queue using HTTP streaming events. + * + * @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 The streaming object that can be used to listen for updates. + */ + streamStatus( + endpointId: string, + options: QueueStatusStreamOptions, + ): Promise>; + + /** + * Subscribes to updates for a specific request in the queue using polling or streaming. + * See `options.mode` for more details. + * + * @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 final status of the request. + */ + subscribeToStatus( + endpointId: string, + options: QueueStatusSubscriptionOptions, + ): Promise; + + /** + * Retrieves the result of a specific request from the queue. + * + * @param endpointId - The ID of the function web endpoint. + * @param options - Options to configure how the request is run. + * @returns A promise that resolves to the result of the request. + */ + result( + endpointId: string, + options: BaseQueueOptions, + ): Promise; + + /** + * Cancels a 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 once the request is cancelled. + * @throws {Error} If the request cannot be cancelled. + */ + cancel(endpointId: string, options: BaseQueueOptions): Promise; +} + +type QueueClientDependencies = { + config: RequiredConfig; + storage: StorageClient; +}; + +export const createQueueClient = ({ + config, + storage, +}: QueueClientDependencies): QueueClient => { + const ref: QueueClient = { + async submit( + endpointId: string, + options: SubmitOptions, + ): Promise { + const { webhookUrl, ...runOptions } = options; + const input = options.input + ? await storage.transformInput(options.input) + : undefined; + return dispatchRequest({ + method: options.method, + targetUrl: buildUrl(endpointId, { + ...runOptions, + subdomain: "queue", + query: webhookUrl ? { fal_webhook: webhookUrl } : undefined, + }), + input: input as Input, + config, + }); + }, + async status( + endpointId: string, + { requestId, logs = false }: QueueStatusOptions, + ): Promise { + const appId = parseEndpointId(endpointId); + const prefix = appId.namespace ? `${appId.namespace}/` : ""; + return dispatchRequest({ + method: "get", + targetUrl: buildUrl(`${prefix}${appId.owner}/${appId.alias}`, { + subdomain: "queue", + query: { logs: logs ? "1" : "0" }, + path: `/requests/${requestId}/status`, + }), + config, + }); + }, + + async streamStatus( + endpointId: string, + { requestId, logs = false, connectionMode }: QueueStatusStreamOptions, + ): Promise> { + const appId = parseEndpointId(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 FalStream(endpointId, config, { + url, + method: "get", + connectionMode, + queryParams, + }); + }, + + async subscribeToStatus( + endpointId, + options, + ): Promise { + const requestId = options.requestId; + const timeout = options.timeout; + let timeoutId: 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 = await ref.streamStatus(endpointId, { + requestId, + logs: options.logs, + connectionMode: + "connectionMode" in options + ? (options.connectionMode as StreamingConnectionMode) + : undefined, + }); + const logs: RequestLog[] = []; + if (timeout) { + timeoutId = setTimeout(() => { + status.abort(); + ref.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: QueueStatus) => { + 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 ? { ...data, logs } : data); + } + }); + const doneStatus = await status.done(); + if (timeoutId) { + clearTimeout(timeoutId); + } + return doneStatus as CompletedQueueStatus; + } + // default to polling until status streaming is stable and faster + return new Promise((resolve, reject) => { + let pollingTimeoutId: TimeoutId; + // 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" + ? (options.pollInterval ?? DEFAULT_POLL_INTERVAL) + : DEFAULT_POLL_INTERVAL; + + const clearScheduledTasks = () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + if (pollingTimeoutId) { + clearTimeout(pollingTimeoutId); + } + }; + if (timeout) { + timeoutId = setTimeout(() => { + clearScheduledTasks(); + ref.cancel(endpointId, { requestId }).catch(handleCancelError); + reject( + new Error( + `Client timed out waiting for the request to complete after ${timeout}ms`, + ), + ); + }, timeout); + } + const poll = async () => { + try { + const requestStatus = await ref.status(endpointId, { + requestId, + logs: options.logs ?? 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); + }); + }, + + async result( + endpointId: string, + { requestId }: BaseQueueOptions, + ): Promise { + const appId = parseEndpointId(endpointId); + const prefix = appId.namespace ? `${appId.namespace}/` : ""; + return dispatchRequest({ + method: "get", + targetUrl: buildUrl(`${prefix}${appId.owner}/${appId.alias}`, { + subdomain: "queue", + path: `/requests/${requestId}`, + }), + config, + }); + }, + + async cancel( + endpointId: string, + { requestId }: BaseQueueOptions, + ): Promise { + const appId = parseEndpointId(endpointId); + const prefix = appId.namespace ? `${appId.namespace}/` : ""; + await dispatchRequest({ + method: "put", + targetUrl: buildUrl(`${prefix}${appId.owner}/${appId.alias}`, { + subdomain: "queue", + path: `/requests/${requestId}/cancel`, + }), + config, + }); + }, + }; + return ref; +}; diff --git a/libs/client/src/realtime.ts b/libs/client/src/realtime.ts index 0dcb94c..a599d85 100644 --- a/libs/client/src/realtime.ts +++ b/libs/client/src/realtime.ts @@ -13,9 +13,10 @@ import { transition, } from "robot3"; import { TOKEN_EXPIRATION_SECONDS, getTemporaryAuthToken } from "./auth"; +import { RequiredConfig } from "./config"; import { ApiError } from "./response"; import { isBrowser } from "./runtime"; -import { ensureAppIdFormat, isReact, throttle } from "./utils"; +import { ensureEndpointIdFormat, isReact, throttle } from "./utils"; // Define the context interface Context { @@ -258,7 +259,7 @@ function buildRealtimeUrl( if (maxBuffering !== undefined) { queryParams.set("max_buffering", maxBuffering.toFixed(0)); } - const appId = ensureAppIdFormat(app); + const appId = ensureEndpointIdFormat(app); return `wss://fal.run/${appId}/realtime?${queryParams.toString()}`; } @@ -346,178 +347,186 @@ function isFalErrorResult(data: any): data is FalErrorResult { return data.type === "x-fal-error"; } -/** - * The default implementation of the realtime client. - */ -export const realtimeImpl: RealtimeClient = { - connect( - app: string, - handler: RealtimeConnectionHandler, - ): RealtimeConnection { - const { - // if running on React in the server, set clientOnly to true by default - clientOnly = isReact() && !isBrowser(), - connectionKey = crypto.randomUUID(), - maxBuffering, - throttleInterval = DEFAULT_THROTTLE_INTERVAL, - } = handler; - if (clientOnly && !isBrowser()) { - return NoOpConnection; - } - - let previousState: string | undefined; +type RealtimeClientDependencies = { + config: RequiredConfig; +}; - // Although the state machine is cached so we don't open multiple connections, - // we still need to update the callbacks so we can call the correct references - // when the state machine is reused. This is needed because the callbacks - // are passed as part of the handler object, which can be different across - // different calls to `connect`. - connectionCallbacks.set(connectionKey, { - onError: handler.onError, - onResult: handler.onResult, - }); - const getCallbacks = () => - connectionCallbacks.get(connectionKey) as RealtimeConnectionCallback; - const stateMachine = reuseInterpreter( - connectionKey, - throttleInterval, - ({ context, machine, send }) => { - const { enqueuedMessage, token } = context; - if (machine.current === "active" && enqueuedMessage) { - send({ type: "send", message: enqueuedMessage }); - } - if ( - machine.current === "authRequired" && - token === undefined && - previousState !== machine.current - ) { - send({ type: "initiateAuth" }); - getTemporaryAuthToken(app) - .then((token) => { - send({ type: "authenticated", token }); - const tokenExpirationTimeout = Math.round( - TOKEN_EXPIRATION_SECONDS * 0.9 * 1000, - ); - setTimeout(() => { - send({ type: "expireToken" }); - }, tokenExpirationTimeout); - }) - .catch((error) => { - send({ type: "unauthorized", error }); - }); - } - if ( - machine.current === "connecting" && - previousState !== machine.current && - token !== undefined - ) { - const ws = new WebSocket( - buildRealtimeUrl(app, { token, maxBuffering }), - ); - ws.onopen = () => { - send({ type: "connected", websocket: ws }); - }; - ws.onclose = (event) => { - if (event.code !== WebSocketErrorCodes.NORMAL_CLOSURE) { - const { onError = noop } = getCallbacks(); - onError( - new ApiError({ - message: `Error closing the connection: ${event.reason}`, - status: event.code, - }), - ); - } - send({ type: "connectionClosed", code: event.code }); - }; - ws.onerror = (event) => { - // TODO specify error protocol for identified errors - const { onError = noop } = getCallbacks(); - onError(new ApiError({ message: "Unknown error", status: 500 })); - }; - ws.onmessage = (event) => { - const { onResult } = getCallbacks(); - - // Handle binary messages as msgpack messages - if (event.data instanceof ArrayBuffer) { - const result = decode(new Uint8Array(event.data)); - onResult(result); - return; - } - if (event.data instanceof Uint8Array) { - const result = decode(event.data); - onResult(result); - return; - } - if (event.data instanceof Blob) { - event.data.arrayBuffer().then((buffer) => { - const result = decode(new Uint8Array(buffer)); - onResult(result); +export function createRealtimeClient({ + config, +}: RealtimeClientDependencies): RealtimeClient { + return { + connect( + app: string, + handler: RealtimeConnectionHandler, + ): RealtimeConnection { + const { + // if running on React in the server, set clientOnly to true by default + clientOnly = isReact() && !isBrowser(), + connectionKey = crypto.randomUUID(), + maxBuffering, + throttleInterval = DEFAULT_THROTTLE_INTERVAL, + } = handler; + if (clientOnly && !isBrowser()) { + return NoOpConnection; + } + + let previousState: string | undefined; + + // Although the state machine is cached so we don't open multiple connections, + // we still need to update the callbacks so we can call the correct references + // when the state machine is reused. This is needed because the callbacks + // are passed as part of the handler object, which can be different across + // different calls to `connect`. + connectionCallbacks.set(connectionKey, { + onError: handler.onError, + onResult: handler.onResult, + }); + const getCallbacks = () => + connectionCallbacks.get(connectionKey) as RealtimeConnectionCallback; + const stateMachine = reuseInterpreter( + connectionKey, + throttleInterval, + ({ context, machine, send }) => { + const { enqueuedMessage, token } = context; + if (machine.current === "active" && enqueuedMessage) { + send({ type: "send", message: enqueuedMessage }); + } + if ( + machine.current === "authRequired" && + token === undefined && + previousState !== machine.current + ) { + send({ type: "initiateAuth" }); + getTemporaryAuthToken(app, config) + .then((token) => { + send({ type: "authenticated", token }); + const tokenExpirationTimeout = Math.round( + TOKEN_EXPIRATION_SECONDS * 0.9 * 1000, + ); + setTimeout(() => { + send({ type: "expireToken" }); + }, tokenExpirationTimeout); + }) + .catch((error) => { + send({ type: "unauthorized", error }); }); - return; - } - - // Otherwise handle strings as plain JSON messages - const data = JSON.parse(event.data); - - // Drop messages that are not related to the actual result. - // In the future, we might want to handle other types of messages. - // TODO: specify the fal ws protocol format - if (isUnauthorizedError(data)) { - send({ type: "unauthorized", error: new Error("Unauthorized") }); - return; - } - if (isSuccessfulResult(data)) { - onResult(data); - return; - } - if (isFalErrorResult(data)) { - if (data.error === "TIMEOUT") { - // Timeout error messages just indicate that the connection hasn't - // received an incoming message for a while. We don't need to - // handle them as errors. - return; + } + if ( + machine.current === "connecting" && + previousState !== machine.current && + token !== undefined + ) { + const ws = new WebSocket( + buildRealtimeUrl(app, { token, maxBuffering }), + ); + ws.onopen = () => { + send({ type: "connected", websocket: ws }); + }; + ws.onclose = (event) => { + if (event.code !== WebSocketErrorCodes.NORMAL_CLOSURE) { + const { onError = noop } = getCallbacks(); + onError( + new ApiError({ + message: `Error closing the connection: ${event.reason}`, + status: event.code, + }), + ); } + send({ type: "connectionClosed", code: event.code }); + }; + ws.onerror = (event) => { + // TODO specify error protocol for identified errors const { onError = noop } = getCallbacks(); - onError( - new ApiError({ - message: `${data.error}: ${data.reason}`, - // TODO better error status code - status: 400, - body: data, - }), - ); - return; - } - }; - } - previousState = machine.current; - }, - ); - - const send = (input: Input & Partial) => { - // Use throttled send to avoid sending too many messages - - const message = - input instanceof Uint8Array - ? input - : { - ...input, - request_id: input["request_id"] ?? crypto.randomUUID(), + onError(new ApiError({ message: "Unknown error", status: 500 })); }; + ws.onmessage = (event) => { + const { onResult } = getCallbacks(); - stateMachine.throttledSend({ - type: "send", - message, - }); - }; - - const close = () => { - stateMachine.send({ type: "close" }); - }; + // Handle binary messages as msgpack messages + if (event.data instanceof ArrayBuffer) { + const result = decode(new Uint8Array(event.data)); + onResult(result); + return; + } + if (event.data instanceof Uint8Array) { + const result = decode(event.data); + onResult(result); + return; + } + if (event.data instanceof Blob) { + event.data.arrayBuffer().then((buffer) => { + const result = decode(new Uint8Array(buffer)); + onResult(result); + }); + return; + } - return { - send, - close, - }; - }, -}; + // Otherwise handle strings as plain JSON messages + const data = JSON.parse(event.data); + + // Drop messages that are not related to the actual result. + // In the future, we might want to handle other types of messages. + // TODO: specify the fal ws protocol format + if (isUnauthorizedError(data)) { + send({ + type: "unauthorized", + error: new Error("Unauthorized"), + }); + return; + } + if (isSuccessfulResult(data)) { + onResult(data); + return; + } + if (isFalErrorResult(data)) { + if (data.error === "TIMEOUT") { + // Timeout error messages just indicate that the connection hasn't + // received an incoming message for a while. We don't need to + // handle them as errors. + return; + } + const { onError = noop } = getCallbacks(); + onError( + new ApiError({ + message: `${data.error}: ${data.reason}`, + // TODO better error status code + status: 400, + body: data, + }), + ); + return; + } + }; + } + previousState = machine.current; + }, + ); + + const send = (input: Input & Partial) => { + // Use throttled send to avoid sending too many messages + + const message = + input instanceof Uint8Array + ? input + : { + ...input, + request_id: input["request_id"] ?? crypto.randomUUID(), + }; + + stateMachine.throttledSend({ + type: "send", + message, + }); + }; + + const close = () => { + stateMachine.send({ type: "close" }); + }; + + return { + send, + close, + }; + }, + }; +} diff --git a/libs/client/src/request.ts b/libs/client/src/request.ts index 4e42c03..2d1e8f6 100644 --- a/libs/client/src/request.ts +++ b/libs/client/src/request.ts @@ -1,6 +1,8 @@ -import { getConfig } from "./config"; +import { RequiredConfig } from "./config"; import { ResponseHandler } from "./response"; import { getUserAgent, isBrowser } from "./runtime"; +import { RunOptions, UrlOptions } from "./types"; +import { ensureEndpointIdFormat, isValidUrl } from "./utils"; const isCloudflareWorkers = typeof navigator !== "undefined" && @@ -10,18 +12,24 @@ type RequestOptions = { responseHandler?: ResponseHandler; }; +type RequestParams = { + method?: string; + targetUrl: string; + input?: Input; + config: RequiredConfig; + options?: RequestOptions & RequestInit; +}; + export async function dispatchRequest( - method: string, - targetUrl: string, - input: Input, - options: RequestOptions & RequestInit = {}, + params: RequestParams, ): Promise { + const { method = "POST", targetUrl, input, config, options = {} } = params; const { credentials: credentialsValue, requestMiddleware, responseHandler, fetch, - } = getConfig(); + } = config; const userAgent = isBrowser() ? {} : { "User-Agent": getUserAgent() }; const credentials = typeof credentialsValue === "function" @@ -58,3 +66,41 @@ export async function dispatchRequest( const handleResponse = customResponseHandler ?? responseHandler; return await handleResponse(response); } + +/** + * Builds the final url to run the function based on its `id` or alias and + * a the options from `RunOptions`. + * + * @private + * @param id the function id or alias + * @param options the run options + * @returns the final url to run the function + */ +export function buildUrl( + id: string, + options: RunOptions & UrlOptions = {}, +): string { + const method = (options.method ?? "post").toLowerCase(); + const path = (options.path ?? "").replace(/^\//, "").replace(/\/{2,}/, "/"); + const input = options.input; + const params = { + ...(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 (isValidUrl(id)) { + const url = id.endsWith("/") ? id : `${id}/`; + return `${url}${path}${queryParams}`; + } + + const appId = ensureEndpointIdFormat(id); + const subdomain = options.subdomain ? `${options.subdomain}.` : ""; + const url = `https://${subdomain}fal.run/${appId}/${path}`; + return `${url.replace(/\/$/, "")}${queryParams}`; +} diff --git a/libs/client/src/runtime.spec.ts b/libs/client/src/runtime.spec.ts index 182ab57..1952f2f 100644 --- a/libs/client/src/runtime.spec.ts +++ b/libs/client/src/runtime.spec.ts @@ -14,6 +14,6 @@ describe("the runtime test suite", () => { }); it("should create the correct user agent identifier", () => { - expect(getUserAgent()).toMatch(/@fal-ai\/serverless-client/); + expect(getUserAgent()).toMatch(/@fal-ai\/client/); }); }); diff --git a/libs/client/src/storage.ts b/libs/client/src/storage.ts index cb51362..adb1981 100644 --- a/libs/client/src/storage.ts +++ b/libs/client/src/storage.ts @@ -1,4 +1,4 @@ -import { getConfig, getRestApiUrl } from "./config"; +import { getRestApiUrl, RequiredConfig } from "./config"; import { dispatchRequest } from "./request"; import { isPlainObject } from "./utils"; @@ -7,7 +7,7 @@ import { isPlainObject } from "./utils"; * uploading files to the server and transforming the input to replace file * objects with URLs. */ -export interface StorageSupport { +export interface StorageClient { /** * Upload a file to the server. Returns the URL of the uploaded file. * @param file the file to upload @@ -57,56 +57,71 @@ function getExtensionFromContentType(contentType: string): string { * @param file the file to upload * @returns the URL to upload the file to and the URL of the file once it is uploaded. */ -async function initiateUpload(file: Blob): Promise { +async function initiateUpload( + file: Blob, + config: RequiredConfig, +): Promise { const contentType = file.type || "application/octet-stream"; const filename = file.name || `${Date.now()}.${getExtensionFromContentType(contentType)}`; - return await dispatchRequest( - "POST", - `${getRestApiUrl()}/storage/upload/initiate`, - { + return await dispatchRequest({ + method: "POST", + targetUrl: `${getRestApiUrl()}/storage/upload/initiate`, + input: { content_type: contentType, file_name: filename, }, - ); + config, + }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any type KeyValuePair = [string, any]; -export const storageImpl: StorageSupport = { - upload: async (file: Blob) => { - const { fetch } = getConfig(); - const { upload_url: uploadUrl, file_url: url } = await initiateUpload(file); - const response = await fetch(uploadUrl, { - method: "PUT", - body: file, - headers: { - "Content-Type": file.type || "application/octet-stream", - }, - }); - const { responseHandler } = getConfig(); - await responseHandler(response); - return url; - }, +type StorageClientDependencies = { + config: RequiredConfig; +}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - transformInput: async (input: any): Promise => { - if (Array.isArray(input)) { - return Promise.all(input.map((item) => storageImpl.transformInput(item))); - } else if (input instanceof Blob) { - return await storageImpl.upload(input); - } else if (isPlainObject(input)) { - const inputObject = input as Record; - const promises = Object.entries(inputObject).map( - async ([key, value]): Promise => { - return [key, await storageImpl.transformInput(value)]; - }, +export function createStorageClient({ + config, +}: StorageClientDependencies): StorageClient { + const ref: StorageClient = { + upload: async (file: Blob) => { + const { fetch, responseHandler } = config; + const { upload_url: uploadUrl, file_url: url } = await initiateUpload( + file, + config, ); - const results = await Promise.all(promises); - return Object.fromEntries(results); - } - // Return the input as is if it's neither an object nor a file/blob/data URI - return input; - }, -}; + const response = await fetch(uploadUrl, { + method: "PUT", + body: file, + headers: { + "Content-Type": file.type || "application/octet-stream", + }, + }); + await responseHandler(response); + return url; + }, + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformInput: async (input: any): Promise => { + if (Array.isArray(input)) { + return Promise.all(input.map((item) => ref.transformInput(item))); + } else if (input instanceof Blob) { + return await ref.upload(input); + } else if (isPlainObject(input)) { + const inputObject = input as Record; + const promises = Object.entries(inputObject).map( + async ([key, value]): Promise => { + return [key, await ref.transformInput(value)]; + }, + ); + const results = await Promise.all(promises); + return Object.fromEntries(results); + } + // Return the input as is if it's neither an object nor a file/blob/data URI + return input; + }, + }; + return ref; +} diff --git a/libs/client/src/streaming.ts b/libs/client/src/streaming.ts index db5a93d..94ef43b 100644 --- a/libs/client/src/streaming.ts +++ b/libs/client/src/streaming.ts @@ -1,10 +1,9 @@ import { createParser } from "eventsource-parser"; import { getTemporaryAuthToken } from "./auth"; -import { buildUrl } from "./client"; -import { getConfig } from "./config"; -import { dispatchRequest } from "./request"; +import { RequiredConfig } from "./config"; +import { buildUrl, dispatchRequest } from "./request"; import { ApiError, defaultResponseHandler } from "./response"; -import { storageImpl } from "./storage"; +import { type StorageClient } from "./storage"; export type StreamingConnectionMode = "client" | "server"; @@ -76,6 +75,7 @@ type EventHandler = (event: T) => void; */ export class FalStream { // properties + config: RequiredConfig; endpointId: string; url: string; options: StreamOptions; @@ -92,8 +92,13 @@ export class FalStream { private abortController = new AbortController(); - constructor(endpointId: string, options: StreamOptions) { + constructor( + endpointId: string, + config: RequiredConfig, + options: StreamOptions, + ) { this.endpointId = endpointId; + this.config = config; this.url = options.url ?? buildUrl(endpointId, { @@ -130,8 +135,8 @@ export class FalStream { if (connectionMode === "client") { // if we are in the browser, we need to get a temporary token // to authenticate the request - const token = await getTemporaryAuthToken(endpointId); - const { fetch } = getConfig(); + const token = await getTemporaryAuthToken(endpointId, this.config); + const { fetch } = this.config; const parsedUrl = new URL(this.url); parsedUrl.searchParams.set("fal_jwt_token", token); const response = await fetch(parsedUrl.toString(), { @@ -145,12 +150,18 @@ export class FalStream { }); return await this.handleResponse(response); } - return await dispatchRequest(method.toUpperCase(), this.url, input, { - headers: { - accept: options.accept ?? CONTENT_TYPE_EVENT_STREAM, + return await dispatchRequest({ + method: method.toUpperCase(), + targetUrl: this.url, + input, + config: this.config, + options: { + headers: { + accept: options.accept ?? CONTENT_TYPE_EVENT_STREAM, + }, + responseHandler: this.handleResponse, + signal: this.abortController.signal, }, - responseHandler: this.handleResponse, - signal: this.abortController.signal, }); } catch (error) { this.handleError(error); @@ -317,24 +328,45 @@ export class FalStream { } /** - * Calls a fal app that supports streaming and provides a streaming-capable - * object as a result, that can be used to get partial results through either - * `AsyncIterator` or through an event listener. - * - * @param endpointId the endpoint id, e.g. `fal-ai/llavav15-13b`. - * @param options the request options, including the input payload. - * @returns the `FalStream` instance. + * The streaming client interface. */ -export async function stream, Output = any>( - endpointId: string, - options: StreamOptions, -): Promise> { - const input = - options.input && options.autoUpload !== false - ? await storageImpl.transformInput(options.input) - : options.input; - return new FalStream(endpointId, { - ...options, - input: input as Input, - }); +export interface StreamingClient { + /** + * Calls a fal app that supports streaming and provides a streaming-capable + * object as a result, that can be used to get partial results through either + * `AsyncIterator` or through an event listener. + * + * @param endpointId the endpoint id, e.g. `fal-ai/llavav15-13b`. + * @param options the request options, including the input payload. + * @returns the `FalStream` instance. + */ + stream, Output = any>( + endpointId: string, + options: StreamOptions, + ): Promise>; +} + +type StreamingClientDependencies = { + config: RequiredConfig; + storage: StorageClient; +}; + +export function createStreamingClient({ + config, + storage, +}: StreamingClientDependencies): StreamingClient { + return { + async stream( + endpointId: string, + options: StreamOptions, + ) { + const input = options.input + ? await storage.transformInput(options.input) + : undefined; + return new FalStream(endpointId, config, { + ...options, + input: input as Input, + }); + }, + }; } diff --git a/libs/client/src/types.ts b/libs/client/src/types.ts index 5365b2d..b5ca9dd 100644 --- a/libs/client/src/types.ts +++ b/libs/client/src/types.ts @@ -1,5 +1,41 @@ -export type Result = { - result: T; +// export type Result = { +// result: T; +// }; + +/** + * The function input and other configuration when running + * the function, such as the HTTP method to use. + */ +export type RunOptions = { + /** + * The function input. It will be submitted either as query params + * or the body payload, depending on the `method`. + */ + readonly input?: Input; + + /** + * The HTTP method, defaults to `post`; + */ + readonly method?: "get" | "post" | "put" | "delete" | string; +}; + +export type UrlOptions = { + /** + * If `true`, the function will use the queue to run the function + * asynchronously and return the result in a separate call. This + * influences how the URL is built. + */ + readonly subdomain?: string; + + /** + * The query parameters to include in the URL. + */ + readonly query?: Record; + + /** + * The path to append to the function URL. + */ + path?: string; }; export type EnqueueResult = { @@ -31,7 +67,7 @@ export interface CompletedQueueStatus extends BaseQueueStatus { status: "COMPLETED"; response_url: string; logs: RequestLog[]; - metrics: Metrics; + metrics?: Metrics; } export interface EnqueuedQueueStatus extends BaseQueueStatus { diff --git a/libs/client/src/utils.spec.ts b/libs/client/src/utils.spec.ts index 16ee2fe..e58a563 100644 --- a/libs/client/src/utils.spec.ts +++ b/libs/client/src/utils.spec.ts @@ -1,24 +1,24 @@ -import { ensureAppIdFormat, parseAppId } from "./utils"; +import { ensureEndpointIdFormat, parseEndpointId } from "./utils"; describe("The utils test suite", () => { it("shoud match a current appOwner/appId format", () => { const id = "fal-ai/fast-sdxl"; - expect(ensureAppIdFormat(id)).toBe(id); + expect(ensureEndpointIdFormat(id)).toBe(id); }); it("shoud match a current appOwner/appId/path format", () => { const id = "fal-ai/fast-sdxl/image-to-image"; - expect(ensureAppIdFormat(id)).toBe(id); + expect(ensureEndpointIdFormat(id)).toBe(id); }); it("should throw on an invalid app id format", () => { const id = "just-an-id"; - expect(() => ensureAppIdFormat(id)).toThrowError(); + expect(() => ensureEndpointIdFormat(id)).toThrowError(); }); it("should parse a current app id", () => { const id = "fal-ai/fast-sdxl"; - const parsed = parseAppId(id); + const parsed = parseEndpointId(id); expect(parsed).toEqual({ owner: "fal-ai", alias: "fast-sdxl", @@ -27,7 +27,7 @@ describe("The utils test suite", () => { it("should parse a current app id with path", () => { const id = "fal-ai/fast-sdxl/image-to-image"; - const parsed = parseAppId(id); + const parsed = parseEndpointId(id); expect(parsed).toEqual({ owner: "fal-ai", alias: "fast-sdxl", @@ -37,7 +37,7 @@ describe("The utils test suite", () => { it("should parse a current app id with namespace", () => { const id = "workflows/fal-ai/fast-sdxl"; - const parsed = parseAppId(id); + const parsed = parseEndpointId(id); expect(parsed).toEqual({ owner: "fal-ai", alias: "fast-sdxl", diff --git a/libs/client/src/utils.ts b/libs/client/src/utils.ts index 7184593..a2bb518 100644 --- a/libs/client/src/utils.ts +++ b/libs/client/src/utils.ts @@ -1,4 +1,4 @@ -export function ensureAppIdFormat(id: string): string { +export function ensureEndpointIdFormat(id: string): string { const parts = id.split("/"); if (parts.length > 1) { return id; @@ -23,8 +23,8 @@ export type AppId = { readonly namespace?: AppNamespace; }; -export function parseAppId(id: string): AppId { - const normalizedId = ensureAppIdFormat(id); +export function parseEndpointId(id: string): AppId { + const normalizedId = ensureEndpointIdFormat(id); const parts = normalizedId.split("/"); if (APP_NAMESPACES.includes(parts[0] as any)) { return { diff --git a/libs/proxy/README.md b/libs/proxy/README.md index 9df3fc1..10318e1 100644 --- a/libs/proxy/README.md +++ b/libs/proxy/README.md @@ -65,7 +65,7 @@ For Express applications: Once you've set up the proxy, you can configure the client to use it: ```ts -import * as fal from "@fal-ai/serverless-client"; +import { fal } from "@fal-ai/client"; fal.config({ proxyUrl: "/api/fal/proxy", // or https://my.app.com/api/fal/proxy diff --git a/libs/proxy/package.json b/libs/proxy/package.json index 68ae210..0b6ed82 100644 --- a/libs/proxy/package.json +++ b/libs/proxy/package.json @@ -1,6 +1,6 @@ { - "name": "@fal-ai/serverless-proxy", - "version": "0.9.0", + "name": "@fal-ai/server-proxy", + "version": "1.0.0", "license": "MIT", "repository": { "type": "git", diff --git a/tsconfig.base.json b/tsconfig.base.json index d3023e2..67d0d9a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -15,11 +15,11 @@ "skipDefaultLibCheck": true, "baseUrl": ".", "paths": { + "@fal-ai/client": ["libs/client/src/index.ts"], "@fal-ai/create-app": ["libs/create-app/src/index.ts"], - "@fal-ai/serverless-client": ["libs/client/src/index.ts"], - "@fal-ai/serverless-proxy": ["libs/proxy/src/index.ts"], - "@fal-ai/serverless-proxy/express": ["libs/proxy/src/express.ts"], - "@fal-ai/serverless-proxy/nextjs": ["libs/proxy/src/nextjs.ts"] + "@fal-ai/server-proxy": ["libs/proxy/src/index.ts"], + "@fal-ai/server-proxy/express": ["libs/proxy/src/express.ts"], + "@fal-ai/server-proxy/nextjs": ["libs/proxy/src/nextjs.ts"] } }, "exclude": ["node_modules/**", "tmp/**", "dist/**"] From 22995e8e800c5fb70006679cc9639bfa8e5eaaae Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Wed, 25 Sep 2024 21:42:57 -0700 Subject: [PATCH 03/11] chore: update docs --- libs/client/src/client.ts | 40 +++++++++++++++++++++++++++++++++++++-- libs/client/src/index.ts | 5 +++-- libs/client/src/utils.ts | 14 +++++++------- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/libs/client/src/client.ts b/libs/client/src/client.ts index 2c15f7e..88e0ff6 100644 --- a/libs/client/src/client.ts +++ b/libs/client/src/client.ts @@ -6,13 +6,35 @@ import { createStorageClient, StorageClient } from "./storage"; import { createStreamingClient, StreamingClient } from "./streaming"; import { RunOptions } from "./types"; +/** + * The main client type, it provides access to simple API model usage, + * as well as access to the `queue` and `storage` APIs. + * + * @see createFalClient + */ export interface FalClient { + /** + * The queue client to interact with the queue API. + */ readonly queue: QueueClient; + /** + * The realtime client to interact with the realtime API + * and receive updates in real-time. + * @see #RealtimeClient + * @see #RealtimeClient.connect + */ readonly realtime: RealtimeClient; + /** + * The storage client to interact with the storage API. + */ readonly storage: StorageClient; + /** + * The streaming client to interact with the streaming API. + * @see #stream + */ readonly streaming: StreamingClient; /** @@ -35,10 +57,24 @@ export interface FalClient { options: RunOptions & QueueSubscribeOptions, ): Promise; + /** + * Calls a fal app that supports streaming and provides a streaming-capable + * object as a result, that can be used to get partial results through either + * `AsyncIterator` or through an event listener. + * + * @param endpointId the endpoint id, e.g. `fal-ai/llavav15-13b`. + * @param options the request options, including the input payload. + * @returns the `FalStream` instance. + */ stream: StreamingClient["stream"]; } -export const createFalClient = (userConfig: Config = {}): FalClient => { +/** + * Creates a new reference of the `FalClient`. + * @param userConfig Optional configuration to override the default settings. + * @returns a new instance of the `FalClient`. + */ +export function createFalClient(userConfig: Config = {}): FalClient { const config = createConfig(userConfig); const storage = createStorageClient({ config }); const queue = createQueueClient({ config, storage }); @@ -76,4 +112,4 @@ export const createFalClient = (userConfig: Config = {}): FalClient => { return queue.result(endpointId, { requestId }); }, }; -}; +} diff --git a/libs/client/src/index.ts b/libs/client/src/index.ts index e80875d..4852d37 100644 --- a/libs/client/src/index.ts +++ b/libs/client/src/index.ts @@ -1,5 +1,5 @@ -import { createFalClient, FalClient } from "./client"; -import { Config, createConfig, RequiredConfig } from "./config"; +import { createFalClient, type FalClient } from "./client"; +import { Config, createConfig, type RequiredConfig } from "./config"; import { RunOptions } from "./types"; export { createFalClient, type FalClient } from "./client"; @@ -11,6 +11,7 @@ export { ApiError, ValidationError } from "./response"; export type { ResponseHandler } from "./response"; export type { StorageClient } from "./storage"; export type { StreamingClient } from "./streaming"; +export * from "./types"; export type { QueueStatus, ValidationErrorInfo, diff --git a/libs/client/src/utils.ts b/libs/client/src/utils.ts index a2bb518..b0a28e0 100644 --- a/libs/client/src/utils.ts +++ b/libs/client/src/utils.ts @@ -12,26 +12,26 @@ export function ensureEndpointIdFormat(id: string): string { ); } -const APP_NAMESPACES = ["workflows", "comfy"] as const; +const ENDPOINT_NAMESPACES = ["workflows", "comfy"] as const; -type AppNamespace = (typeof APP_NAMESPACES)[number]; +type EndpointNamespace = (typeof ENDPOINT_NAMESPACES)[number]; -export type AppId = { +export type EndpointId = { readonly owner: string; readonly alias: string; readonly path?: string; - readonly namespace?: AppNamespace; + readonly namespace?: EndpointNamespace; }; -export function parseEndpointId(id: string): AppId { +export function parseEndpointId(id: string): EndpointId { const normalizedId = ensureEndpointIdFormat(id); const parts = normalizedId.split("/"); - if (APP_NAMESPACES.includes(parts[0] as any)) { + if (ENDPOINT_NAMESPACES.includes(parts[0] as any)) { return { owner: parts[1], alias: parts[2], path: parts.slice(3).join("/") || undefined, - namespace: parts[0] as AppNamespace, + namespace: parts[0] as EndpointNamespace, }; } return { From 82acabf74415f7a22a5a725d69a790d21af05dec Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Sat, 28 Sep 2024 14:18:33 -0700 Subject: [PATCH 04/11] feat(client): improved result typing --- libs/client/src/client.ts | 27 +++++++++++++++++---------- libs/client/src/config.ts | 15 ++++++--------- libs/client/src/index.ts | 22 +++++++++++----------- libs/client/src/middleware.ts | 16 ++++++++++------ libs/client/src/queue.ts | 21 +++++++++++++-------- libs/client/src/response.ts | 19 ++++++++++++++++++- libs/client/src/streaming.ts | 10 +++++----- libs/client/src/types.ts | 32 +++++++++++++++++--------------- 8 files changed, 97 insertions(+), 65 deletions(-) diff --git a/libs/client/src/client.ts b/libs/client/src/client.ts index 88e0ff6..651d39b 100644 --- a/libs/client/src/client.ts +++ b/libs/client/src/client.ts @@ -2,9 +2,10 @@ import { Config, createConfig } from "./config"; import { createQueueClient, QueueClient, QueueSubscribeOptions } from "./queue"; import { createRealtimeClient, RealtimeClient } from "./realtime"; import { buildUrl, dispatchRequest } from "./request"; +import { resultResponseHandler } from "./response"; import { createStorageClient, StorageClient } from "./storage"; import { createStreamingClient, StreamingClient } from "./streaming"; -import { RunOptions } from "./types"; +import { Result, RunOptions } from "./types"; /** * The main client type, it provides access to simple API model usage, @@ -43,7 +44,10 @@ export interface FalClient { * @param id the registered function revision id or alias. * @returns the remote function output */ - run(id: string, options: RunOptions): Promise; + run>( + id: string, + options: RunOptions, + ): Promise>; /** * Subscribes to updates for a specific request in the queue. @@ -52,10 +56,10 @@ export interface FalClient { * @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. */ - subscribe( + subscribe>( endpointId: string, options: RunOptions & QueueSubscribeOptions, - ): Promise; + ): Promise>; /** * Calls a fal app that supports streaming and provides a streaming-capable @@ -86,24 +90,27 @@ export function createFalClient(userConfig: Config = {}): FalClient { storage, streaming, stream: streaming.stream, - async run( + async run( id: string, options: RunOptions = {}, - ): Promise { + ): Promise> { const input = options.input ? await storage.transformInput(options.input) : undefined; - return dispatchRequest({ + return dispatchRequest>({ method: options.method, targetUrl: buildUrl(id, options), input: input as Input, - config, + config: { + ...config, + responseHandler: resultResponseHandler, + }, }); }, - async subscribe( + async subscribe( endpointId: string, options: RunOptions & QueueSubscribeOptions = {}, - ): Promise { + ): Promise> { const { request_id: requestId } = await queue.submit(endpointId, options); if (options.onEnqueue) { options.onEnqueue(requestId); diff --git a/libs/client/src/config.ts b/libs/client/src/config.ts index a16984f..9e17651 100644 --- a/libs/client/src/config.ts +++ b/libs/client/src/config.ts @@ -1,8 +1,4 @@ -import { - withMiddleware, - withProxy, - type RequestMiddleware, -} from "./middleware"; +import { withProxy, type RequestMiddleware } from "./middleware"; import type { ResponseHandler } from "./response"; import { defaultResponseHandler } from "./response"; @@ -111,10 +107,11 @@ export function createConfig(config: Config): RequiredConfig { if (config.proxyUrl) { configuration = { ...configuration, - requestMiddleware: withMiddleware( - withProxy({ targetUrl: config.proxyUrl }), - configuration.requestMiddleware, - ), + requestMiddleware: withProxy({ targetUrl: config.proxyUrl }), + // requestMiddleware: withMiddleware( + // withProxy({ targetUrl: config.proxyUrl }), + // configuration.requestMiddleware, + // ), }; } const { credentials: resolveCredentials, suppressLocalCredentialsWarning } = diff --git a/libs/client/src/index.ts b/libs/client/src/index.ts index 4852d37..bf97ab7 100644 --- a/libs/client/src/index.ts +++ b/libs/client/src/index.ts @@ -1,5 +1,6 @@ import { createFalClient, type FalClient } from "./client"; -import { Config, createConfig, type RequiredConfig } from "./config"; +import { Config, createConfig } from "./config"; +import { StreamOptions } from "./streaming"; import { RunOptions } from "./types"; export { createFalClient, type FalClient } from "./client"; @@ -28,12 +29,11 @@ type SingletonFalClient = { * layer for existing code that uses the clients version prior to 1.0.0. */ export const fal: SingletonFalClient = (function createSingletonFalClient() { - let currentConfig: RequiredConfig = createConfig({}); - let currentInstance: FalClient = createFalClient(currentConfig); + let currentInstance: FalClient = createFalClient(); return { config(config: Config) { - currentConfig = createConfig(config); - currentInstance = createFalClient(currentConfig); + console.log(config.requestMiddleware); + currentInstance = createFalClient(createConfig(config)); }, get queue() { return currentInstance.queue; @@ -47,14 +47,14 @@ export const fal: SingletonFalClient = (function createSingletonFalClient() { get streaming() { return currentInstance.streaming; }, - run(id: string, options: RunOptions) { - return currentInstance.run(id, options); + run(id: string, options: RunOptions) { + return currentInstance.run(id, options); }, - subscribe(endpointId: string, options: RunOptions) { - return currentInstance.subscribe(endpointId, options); + subscribe(endpointId: string, options: RunOptions) { + return currentInstance.subscribe(endpointId, options); }, - stream(endpointId, options) { - return currentInstance.stream(endpointId, options); + stream(endpointId: string, options: StreamOptions) { + return currentInstance.stream(endpointId, options); }, } satisfies SingletonFalClient; })(); diff --git a/libs/client/src/middleware.ts b/libs/client/src/middleware.ts index 127c2fb..b87e2ff 100644 --- a/libs/client/src/middleware.ts +++ b/libs/client/src/middleware.ts @@ -26,12 +26,16 @@ export type RequestMiddleware = ( export function withMiddleware( ...middlewares: RequestMiddleware[] ): RequestMiddleware { - return (config) => - middlewares.reduce( - (configPromise, middleware) => - configPromise.then((req) => middleware(req)), - Promise.resolve(config), - ); + const isDefined = (middleware: RequestMiddleware): boolean => + typeof middleware === "function"; + + return async (config: RequestConfig) => { + let currentConfig = config; + for (const middleware of middlewares.filter(isDefined)) { + currentConfig = await middleware(currentConfig); + } + return currentConfig; + }; } export type RequestProxyConfig = { diff --git a/libs/client/src/queue.ts b/libs/client/src/queue.ts index ad8bc94..a6648a1 100644 --- a/libs/client/src/queue.ts +++ b/libs/client/src/queue.ts @@ -1,12 +1,14 @@ import { RequiredConfig } from "./config"; import { buildUrl, dispatchRequest } from "./request"; +import { resultResponseHandler } from "./response"; import { StorageClient } from "./storage"; import { FalStream, StreamingConnectionMode } from "./streaming"; import { CompletedQueueStatus, - EnqueueResult, + InQueueQueueStatus, QueueStatus, RequestLog, + Result, RunOptions, } from "./types"; import { parseEndpointId } from "./utils"; @@ -140,7 +142,7 @@ export interface QueueClient { submit( endpointId: string, options: SubmitOptions, - ): Promise; + ): Promise; /** * Retrieves the status of a specific request in the queue. @@ -186,7 +188,7 @@ export interface QueueClient { result( endpointId: string, options: BaseQueueOptions, - ): Promise; + ): Promise>; /** * Cancels a request in the queue. @@ -213,12 +215,12 @@ export const createQueueClient = ({ async submit( endpointId: string, options: SubmitOptions, - ): Promise { + ): Promise { const { webhookUrl, ...runOptions } = options; const input = options.input ? await storage.transformInput(options.input) : undefined; - return dispatchRequest({ + return dispatchRequest({ method: options.method, targetUrl: buildUrl(endpointId, { ...runOptions, @@ -382,16 +384,19 @@ export const createQueueClient = ({ async result( endpointId: string, { requestId }: BaseQueueOptions, - ): Promise { + ): Promise> { const appId = parseEndpointId(endpointId); const prefix = appId.namespace ? `${appId.namespace}/` : ""; - return dispatchRequest({ + return dispatchRequest>({ method: "get", targetUrl: buildUrl(`${prefix}${appId.owner}/${appId.alias}`, { subdomain: "queue", path: `/requests/${requestId}`, }), - config, + config: { + ...config, + responseHandler: resultResponseHandler, + }, }); }, diff --git a/libs/client/src/response.ts b/libs/client/src/response.ts index b532335..e310450 100644 --- a/libs/client/src/response.ts +++ b/libs/client/src/response.ts @@ -1,7 +1,14 @@ -import { ValidationErrorInfo } from "./types"; +import { RequiredConfig } from "./config"; +import { Result, ValidationErrorInfo } from "./types"; export type ResponseHandler = (response: Response) => Promise; +const REQUEST_ID_HEADER = "x-fal-request-id"; + +export type ResponseHandlerCreator = ( + config: RequiredConfig, +) => ResponseHandler; + type ApiErrorArgs = { message: string; status: number; @@ -81,3 +88,13 @@ export async function defaultResponseHandler( // TODO convert to either number or bool automatically return response.text() as Promise; } + +export async function resultResponseHandler( + response: Response, +): Promise> { + const data = await defaultResponseHandler(response); + return { + data, + requestId: response.headers.get(REQUEST_ID_HEADER) || "", + } satisfies Result; +} diff --git a/libs/client/src/streaming.ts b/libs/client/src/streaming.ts index 94ef43b..ebfe5dd 100644 --- a/libs/client/src/streaming.ts +++ b/libs/client/src/streaming.ts @@ -13,7 +13,7 @@ const CONTENT_TYPE_EVENT_STREAM = "text/event-stream"; * The stream API options. It requires the API input and also * offers configuration options. */ -type StreamOptions = { +export type StreamOptions = { /** * The endpoint URL. If not provided, it will be generated from the * `endpointId` and the `queryParams`. @@ -193,9 +193,9 @@ export class FalStream { return; } - const isEventStream = response.headers - .get("content-type") - .startsWith(CONTENT_TYPE_EVENT_STREAM); + const isEventStream = ( + response.headers.get("content-type") ?? "" + ).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(); @@ -340,7 +340,7 @@ export interface StreamingClient { * @param options the request options, including the input payload. * @returns the `FalStream` instance. */ - stream, Output = any>( + stream>( endpointId: string, options: StreamOptions, ): Promise>; diff --git a/libs/client/src/types.ts b/libs/client/src/types.ts index b5ca9dd..d51b4e8 100644 --- a/libs/client/src/types.ts +++ b/libs/client/src/types.ts @@ -1,6 +1,11 @@ -// export type Result = { -// result: T; -// }; +/** + * Represents an API result, containing the data, + * the request ID and any other relevant information. + */ +export type Result = { + data: T; + requestId: string; +}; /** * The function input and other configuration when running @@ -38,10 +43,6 @@ export type UrlOptions = { path?: string; }; -export type EnqueueResult = { - request_id: string; -}; - export type RequestLog = { message: string; level: "STDERR" | "STDOUT" | "ERROR" | "INFO" | "WARN" | "DEBUG"; @@ -54,7 +55,14 @@ export type Metrics = { }; interface BaseQueueStatus { - status: "IN_PROGRESS" | "COMPLETED" | "IN_QUEUE"; + status: "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED"; + request_id: string; +} + +export interface InQueueQueueStatus extends BaseQueueStatus { + status: "IN_QUEUE"; + queue_position: number; + response_url: string; } export interface InProgressQueueStatus extends BaseQueueStatus { @@ -70,16 +78,10 @@ export interface CompletedQueueStatus extends BaseQueueStatus { metrics?: Metrics; } -export interface EnqueuedQueueStatus extends BaseQueueStatus { - status: "IN_QUEUE"; - queue_position: number; - response_url: string; -} - export type QueueStatus = | InProgressQueueStatus | CompletedQueueStatus - | EnqueuedQueueStatus; + | InQueueQueueStatus; export function isQueueStatus(obj: any): obj is QueueStatus { return obj && obj.status && obj.response_url; From 77d866246a020c65916e0a4e4d20922edb853712 Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Sat, 28 Sep 2024 14:19:10 -0700 Subject: [PATCH 05/11] chore: update demo app code --- .../app/comfy/image-to-image/page.tsx | 11 +++--- .../app/comfy/image-to-video/page.tsx | 10 ++--- .../app/comfy/text-to-image/page.tsx | 39 ++++++++++--------- apps/demo-nextjs-app-router/app/page.tsx | 15 +++---- .../demo-nextjs-app-router/app/queue/page.tsx | 6 +-- .../app/streaming/page.tsx | 2 +- apps/demo-nextjs-page-router/pages/index.tsx | 12 +++--- 7 files changed, 48 insertions(+), 47 deletions(-) diff --git a/apps/demo-nextjs-app-router/app/comfy/image-to-image/page.tsx b/apps/demo-nextjs-app-router/app/comfy/image-to-image/page.tsx index 60f2761..4be2197 100644 --- a/apps/demo-nextjs-app-router/app/comfy/image-to-image/page.tsx +++ b/apps/demo-nextjs-app-router/app/comfy/image-to-image/page.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @next/next/no-img-element */ "use client"; import { createFalClient } from "@fal-ai/client"; @@ -15,7 +16,7 @@ type Image = { url: string; }; -type Result = { +type ComfyOutput = { url: string; outputs: Record[]; images: Image[]; @@ -50,7 +51,7 @@ export default function ComfyImageToImagePage() { // Result state const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [result, setResult] = useState(null); + const [result, setResult] = useState(null); const [logs, setLogs] = useState([]); const [elapsedTime, setElapsedTime] = useState(0); // @snippet:end @@ -69,7 +70,7 @@ export default function ComfyImageToImagePage() { setElapsedTime(0); }; - const getImageURL = (result: Result) => { + const getImageURL = (result: ComfyOutput) => { return result.outputs[9].images[0]; }; @@ -79,7 +80,7 @@ export default function ComfyImageToImagePage() { setLoading(true); const start = Date.now(); try { - const result: Result = await fal.subscribe( + const { data } = await fal.subscribe( "comfy/fal-ai/image-to-image", { input: { @@ -98,7 +99,7 @@ export default function ComfyImageToImagePage() { }, }, ); - setResult(getImageURL(result)); + setResult(getImageURL(data)); } catch (error: any) { setError(error); } finally { diff --git a/apps/demo-nextjs-app-router/app/comfy/image-to-video/page.tsx b/apps/demo-nextjs-app-router/app/comfy/image-to-video/page.tsx index dc72267..af75f01 100644 --- a/apps/demo-nextjs-app-router/app/comfy/image-to-video/page.tsx +++ b/apps/demo-nextjs-app-router/app/comfy/image-to-video/page.tsx @@ -15,7 +15,7 @@ type Image = { url: string; }; -type Result = { +type ComfyOutput = { url: string; outputs: Record[]; images: Image[]; @@ -46,7 +46,7 @@ export default function ComfyImageToVideoPage() { // Result state const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [result, setResult] = useState(null); + const [result, setResult] = useState(null); const [logs, setLogs] = useState([]); const [elapsedTime, setElapsedTime] = useState(0); // @snippet:end @@ -65,7 +65,7 @@ export default function ComfyImageToVideoPage() { setElapsedTime(0); }; - const getImageURL = (result: Result) => { + const getImageURL = (result: ComfyOutput) => { return result.outputs[10].images[0]; }; @@ -75,7 +75,7 @@ export default function ComfyImageToVideoPage() { setLoading(true); const start = Date.now(); try { - const result: Result = await fal.subscribe( + const { data } = await fal.subscribe( "comfy/fal-ai/image-to-video", { input: { @@ -93,7 +93,7 @@ export default function ComfyImageToVideoPage() { }, }, ); - setResult(getImageURL(result)); + setResult(getImageURL(data)); } catch (error: any) { setError(error); } finally { diff --git a/apps/demo-nextjs-app-router/app/comfy/text-to-image/page.tsx b/apps/demo-nextjs-app-router/app/comfy/text-to-image/page.tsx index 8c9d0f9..0545844 100644 --- a/apps/demo-nextjs-app-router/app/comfy/text-to-image/page.tsx +++ b/apps/demo-nextjs-app-router/app/comfy/text-to-image/page.tsx @@ -15,7 +15,7 @@ type Image = { url: string; }; -type Result = { +type ComfyOutput = { url: string; outputs: Record[]; images: Image[]; @@ -49,7 +49,7 @@ export default function ComfyTextToImagePage() { // Result state const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [result, setResult] = useState(null); + const [result, setResult] = useState(null); const [logs, setLogs] = useState([]); const [elapsedTime, setElapsedTime] = useState(0); // @snippet:end @@ -68,7 +68,7 @@ export default function ComfyTextToImagePage() { setElapsedTime(0); }; - const getImageURL = (result: Result) => { + const getImageURL = (result: ComfyOutput) => { return result.outputs[9].images[0]; }; @@ -78,22 +78,25 @@ export default function ComfyTextToImagePage() { setLoading(true); const start = Date.now(); try { - const result: Result = await fal.subscribe("comfy/fal-ai/text-to-image", { - input: { - prompt: prompt, + const { data } = await fal.subscribe( + "comfy/fal-ai/text-to-image", + { + input: { + prompt: prompt, + }, + logs: true, + onQueueUpdate(update) { + setElapsedTime(Date.now() - start); + if ( + update.status === "IN_PROGRESS" || + update.status === "COMPLETED" + ) { + setLogs((update.logs || []).map((log) => log.message)); + } + }, }, - logs: true, - onQueueUpdate(update) { - setElapsedTime(Date.now() - start); - if ( - update.status === "IN_PROGRESS" || - update.status === "COMPLETED" - ) { - setLogs((update.logs || []).map((log) => log.message)); - } - }, - }); - setResult(getImageURL(result)); + ); + setResult(getImageURL(data)); } catch (error: any) { setError(error); } finally { diff --git a/apps/demo-nextjs-app-router/app/page.tsx b/apps/demo-nextjs-app-router/app/page.tsx index a1ac1ce..9d841e3 100644 --- a/apps/demo-nextjs-app-router/app/page.tsx +++ b/apps/demo-nextjs-app-router/app/page.tsx @@ -1,23 +1,20 @@ "use client"; -import { fal } from "@fal-ai/client"; +import { createFalClient } from "@fal-ai/client"; import { useMemo, useState } from "react"; -// @snippet:start(client.config) -fal.config({ +const fal = createFalClient({ // credentials: 'FAL_KEY_ID:FAL_KEY_SECRET', proxyUrl: "/api/fal/proxy", // the built-int nextjs proxy // proxyUrl: 'http://localhost:3333/api/fal/proxy', // or your own external proxy }); -// @snippet:end -// @snippet:start(client.result.type) type Image = { url: string; file_name: string; file_size: number; }; -type Result = { +type Output = { image: Image; }; // @snippet:end @@ -51,7 +48,7 @@ export default function Home() { // Result state const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [result, setResult] = useState(null); + const [result, setResult] = useState(null); const [logs, setLogs] = useState([]); const [elapsedTime, setElapsedTime] = useState(0); // @snippet:end @@ -79,7 +76,7 @@ export default function Home() { setLoading(true); const start = Date.now(); try { - const result: Result = await fal.subscribe("fal-ai/illusion-diffusion", { + const result = await fal.subscribe("fal-ai/illusion-diffusion", { input: { prompt, image_url: imageFile, @@ -96,7 +93,7 @@ export default function Home() { } }, }); - setResult(result); + setResult(result.data); } catch (error: any) { setError(error); } finally { diff --git a/apps/demo-nextjs-app-router/app/queue/page.tsx b/apps/demo-nextjs-app-router/app/queue/page.tsx index ac477f7..eb81cd0 100644 --- a/apps/demo-nextjs-app-router/app/queue/page.tsx +++ b/apps/demo-nextjs-app-router/app/queue/page.tsx @@ -1,9 +1,9 @@ "use client"; -import { createFalClient } from "@fal-ai/client"; +import { fal } from "@fal-ai/client"; import { useState } from "react"; -const fal = createFalClient({ +fal.config({ proxyUrl: "/api/fal/proxy", }); @@ -54,7 +54,7 @@ export default function Home() { setLoading(true); const start = Date.now(); try { - const result: any = await fal.subscribe(endpointId, { + const result = await fal.subscribe(endpointId, { input: JSON.parse(input), logs: true, // mode: "streaming", diff --git a/apps/demo-nextjs-app-router/app/streaming/page.tsx b/apps/demo-nextjs-app-router/app/streaming/page.tsx index 42e9cf9..245e7f2 100644 --- a/apps/demo-nextjs-app-router/app/streaming/page.tsx +++ b/apps/demo-nextjs-app-router/app/streaming/page.tsx @@ -29,7 +29,7 @@ export default function StreamingDemo() { const [streamStatus, setStreamStatus] = useState("idle"); const runInference = async () => { - const stream = await fal.stream( + const stream = await fal.stream( "fal-ai/llavav15-13b", { input: { diff --git a/apps/demo-nextjs-page-router/pages/index.tsx b/apps/demo-nextjs-page-router/pages/index.tsx index f0219e1..5d277e3 100644 --- a/apps/demo-nextjs-page-router/pages/index.tsx +++ b/apps/demo-nextjs-page-router/pages/index.tsx @@ -1,8 +1,8 @@ -import { fal } from "@fal-ai/client"; +import { createFalClient } from "@fal-ai/client"; import { useMemo, useState } from "react"; // @snippet:start(client.config) -fal.config({ +const fal = createFalClient({ proxyUrl: "/api/fal/proxy", // the built-int nextjs proxy // proxyUrl: 'http://localhost:3333/api/fal/proxy', // or your own external proxy }); @@ -14,7 +14,7 @@ type Image = { file_name: string; file_size: number; }; -type Result = { +type Output = { images: Image[]; }; // @snippet:end @@ -47,7 +47,7 @@ export function Index() { // Result state const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [result, setResult] = useState(null); + const [result, setResult] = useState(null); const [logs, setLogs] = useState([]); const [elapsedTime, setElapsedTime] = useState(0); // @snippet:end @@ -72,7 +72,7 @@ export function Index() { setLoading(true); const start = Date.now(); try { - const result: Result = await fal.subscribe("fal-ai/lora", { + const result = await fal.subscribe("fal-ai/lora", { input: { prompt, model_name: "stabilityai/stable-diffusion-xl-base-1.0", @@ -89,7 +89,7 @@ export function Index() { } }, }); - setResult(result); + setResult(result.data); } catch (error: any) { setError(error); } finally { From 4a593408669e08405112f5c3058666f5189f809d Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Sat, 28 Sep 2024 14:19:57 -0700 Subject: [PATCH 06/11] chore: updated reference docs --- docs/reference/assets/icons.js | 37 +- docs/reference/assets/main.js | 2163 +---------------- docs/reference/assets/navigation.js | 3 +- docs/reference/assets/search.js | 3 +- docs/reference/classes/ApiError.html | 4 +- docs/reference/classes/ValidationError.html | 4 +- docs/reference/functions/config.html | 3 - docs/reference/functions/createFalClient.html | 4 + docs/reference/functions/getConfig.html | 3 - .../functions/isCompletedQueueStatus.html | 1 + docs/reference/functions/isQueueStatus.html | 1 + docs/reference/functions/parseAppId.html | 1 - docs/reference/functions/parseEndpointId.html | 1 + docs/reference/functions/run.html | 4 - docs/reference/functions/stream.html | 7 - docs/reference/functions/subscribe.html | 5 - docs/reference/functions/withMiddleware.html | 4 +- docs/reference/functions/withProxy.html | 2 +- docs/reference/hierarchy.html | 2 +- docs/reference/index.html | 33 +- .../interfaces/CompletedQueueStatus.html | 6 + docs/reference/interfaces/FalClient.html | 40 + .../interfaces/InProgressQueueStatus.html | 5 + .../interfaces/InQueueQueueStatus.html | 5 + docs/reference/interfaces/QueueClient.html | 36 + docs/reference/interfaces/RealtimeClient.html | 6 + docs/reference/interfaces/StorageClient.html | 14 + .../reference/interfaces/StreamingClient.html | 9 + docs/reference/types/Metrics.html | 1 + docs/reference/types/QueueStatus.html | 2 +- docs/reference/types/RequestLog.html | 1 + docs/reference/types/RequestMiddleware.html | 2 +- docs/reference/types/ResponseHandler.html | 2 +- docs/reference/types/Result.html | 3 + docs/reference/types/RunOptions.html | 6 + docs/reference/types/UrlOptions.html | 6 + docs/reference/types/ValidationErrorInfo.html | 2 +- docs/reference/types/WebHookResponse.html | 4 +- docs/reference/variables/fal.html | 3 + docs/reference/variables/queue.html | 4 - docs/reference/variables/realtime.html | 2 - docs/reference/variables/storage.html | 1 - 42 files changed, 207 insertions(+), 2238 deletions(-) delete mode 100644 docs/reference/functions/config.html create mode 100644 docs/reference/functions/createFalClient.html delete mode 100644 docs/reference/functions/getConfig.html create mode 100644 docs/reference/functions/isCompletedQueueStatus.html create mode 100644 docs/reference/functions/isQueueStatus.html delete mode 100644 docs/reference/functions/parseAppId.html create mode 100644 docs/reference/functions/parseEndpointId.html delete mode 100644 docs/reference/functions/run.html delete mode 100644 docs/reference/functions/stream.html delete mode 100644 docs/reference/functions/subscribe.html create mode 100644 docs/reference/interfaces/CompletedQueueStatus.html create mode 100644 docs/reference/interfaces/FalClient.html create mode 100644 docs/reference/interfaces/InProgressQueueStatus.html create mode 100644 docs/reference/interfaces/InQueueQueueStatus.html create mode 100644 docs/reference/interfaces/QueueClient.html create mode 100644 docs/reference/interfaces/RealtimeClient.html create mode 100644 docs/reference/interfaces/StorageClient.html create mode 100644 docs/reference/interfaces/StreamingClient.html create mode 100644 docs/reference/types/Metrics.html create mode 100644 docs/reference/types/RequestLog.html create mode 100644 docs/reference/types/Result.html create mode 100644 docs/reference/types/RunOptions.html create mode 100644 docs/reference/types/UrlOptions.html create mode 100644 docs/reference/variables/fal.html delete mode 100644 docs/reference/variables/queue.html delete mode 100644 docs/reference/variables/realtime.html delete mode 100644 docs/reference/variables/storage.html diff --git a/docs/reference/assets/icons.js b/docs/reference/assets/icons.js index c27b1d1..e88e8ca 100644 --- a/docs/reference/assets/icons.js +++ b/docs/reference/assets/icons.js @@ -1,21 +1,18 @@ -(function () { - addIcons(); - function addIcons() { - if (document.readyState === "loading") - return document.addEventListener("DOMContentLoaded", addIcons); - const svg = document.body.appendChild( - document.createElementNS("http://www.w3.org/2000/svg", "svg"), - ); - svg.innerHTML = `""`; - svg.style.display = "none"; - if (location.protocol === "file:") updateUseElements(); - } +(function() { + addIcons(); + function addIcons() { + if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); + const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); + svg.innerHTML = `""`; + svg.style.display = "none"; + if (location.protocol === "file:") updateUseElements(); + } - function updateUseElements() { - document.querySelectorAll("use").forEach((el) => { - if (el.getAttribute("href").includes("#icon-")) { - el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); - } - }); - } -})(); + function updateUseElements() { + document.querySelectorAll("use").forEach(el => { + if (el.getAttribute("href").includes("#icon-")) { + el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); + } + }); + } +})() \ No newline at end of file diff --git a/docs/reference/assets/main.js b/docs/reference/assets/main.js index 22969f9..21a5d74 100644 --- a/docs/reference/assets/main.js +++ b/docs/reference/assets/main.js @@ -1,2162 +1,9 @@ "use strict"; -window.translations = { - copy: "Copy", - copied: "Copied!", - normally_hidden: - "This member is normally hidden due to your filter settings.", -}; -("use strict"); -(() => { - var Pe = Object.create; - var ie = Object.defineProperty; - var Oe = Object.getOwnPropertyDescriptor; - var _e = Object.getOwnPropertyNames; - var Re = Object.getPrototypeOf, - Me = Object.prototype.hasOwnProperty; - var Fe = (t, e) => () => ( - e || t((e = { exports: {} }).exports, e), e.exports - ); - var De = (t, e, n, r) => { - if ((e && typeof e == "object") || typeof e == "function") - for (let i of _e(e)) - !Me.call(t, i) && - i !== n && - ie(t, i, { - get: () => e[i], - enumerable: !(r = Oe(e, i)) || r.enumerable, - }); - return t; - }; - var Ae = (t, e, n) => ( - (n = t != null ? Pe(Re(t)) : {}), - De( - e || !t || !t.__esModule - ? ie(n, "default", { value: t, enumerable: !0 }) - : n, - t, - ) - ); - var ue = Fe((ae, le) => { - (function () { - var t = function (e) { - var n = new t.Builder(); - return ( - n.pipeline.add(t.trimmer, t.stopWordFilter, t.stemmer), - n.searchPipeline.add(t.stemmer), - e.call(n, n), - n.build() - ); - }; - t.version = "2.3.9"; - (t.utils = {}), - (t.utils.warn = (function (e) { - return function (n) { - e.console && console.warn && console.warn(n); - }; - })(this)), - (t.utils.asString = function (e) { - return e == null ? "" : e.toString(); - }), - (t.utils.clone = function (e) { - if (e == null) return e; - for ( - var n = Object.create(null), r = Object.keys(e), i = 0; - i < r.length; - i++ - ) { - var s = r[i], - o = e[s]; - if (Array.isArray(o)) { - n[s] = o.slice(); - continue; - } - if ( - typeof o == "string" || - typeof o == "number" || - typeof o == "boolean" - ) { - n[s] = o; - continue; - } - throw new TypeError( - "clone is not deep and does not support nested objects", - ); - } - return n; - }), - (t.FieldRef = function (e, n, r) { - (this.docRef = e), (this.fieldName = n), (this._stringValue = r); - }), - (t.FieldRef.joiner = "/"), - (t.FieldRef.fromString = function (e) { - var n = e.indexOf(t.FieldRef.joiner); - if (n === -1) throw "malformed field ref string"; - var r = e.slice(0, n), - i = e.slice(n + 1); - return new t.FieldRef(i, r, e); - }), - (t.FieldRef.prototype.toString = function () { - return ( - this._stringValue == null && - (this._stringValue = - this.fieldName + t.FieldRef.joiner + this.docRef), - this._stringValue - ); - }); - (t.Set = function (e) { - if (((this.elements = Object.create(null)), e)) { - this.length = e.length; - for (var n = 0; n < this.length; n++) this.elements[e[n]] = !0; - } else this.length = 0; - }), - (t.Set.complete = { - intersect: function (e) { - return e; - }, - union: function () { - return this; - }, - contains: function () { - return !0; - }, - }), - (t.Set.empty = { - intersect: function () { - return this; - }, - union: function (e) { - return e; - }, - contains: function () { - return !1; - }, - }), - (t.Set.prototype.contains = function (e) { - return !!this.elements[e]; - }), - (t.Set.prototype.intersect = function (e) { - var n, - r, - i, - s = []; - if (e === t.Set.complete) return this; - if (e === t.Set.empty) return e; - this.length < e.length - ? ((n = this), (r = e)) - : ((n = e), (r = this)), - (i = Object.keys(n.elements)); - for (var o = 0; o < i.length; o++) { - var a = i[o]; - a in r.elements && s.push(a); - } - return new t.Set(s); - }), - (t.Set.prototype.union = function (e) { - return e === t.Set.complete - ? t.Set.complete - : e === t.Set.empty - ? this - : new t.Set( - Object.keys(this.elements).concat(Object.keys(e.elements)), - ); - }), - (t.idf = function (e, n) { - var r = 0; - for (var i in e) i != "_index" && (r += Object.keys(e[i]).length); - var s = (n - r + 0.5) / (r + 0.5); - return Math.log(1 + Math.abs(s)); - }), - (t.Token = function (e, n) { - (this.str = e || ""), (this.metadata = n || {}); - }), - (t.Token.prototype.toString = function () { - return this.str; - }), - (t.Token.prototype.update = function (e) { - return (this.str = e(this.str, this.metadata)), this; - }), - (t.Token.prototype.clone = function (e) { - return ( - (e = - e || - function (n) { - return n; - }), - new t.Token(e(this.str, this.metadata), this.metadata) - ); - }); - (t.tokenizer = function (e, n) { - if (e == null || e == null) return []; - if (Array.isArray(e)) - return e.map(function (m) { - return new t.Token( - t.utils.asString(m).toLowerCase(), - t.utils.clone(n), - ); - }); - for ( - var r = e.toString().toLowerCase(), - i = r.length, - s = [], - o = 0, - a = 0; - o <= i; - o++ - ) { - var l = r.charAt(o), - u = o - a; - if (l.match(t.tokenizer.separator) || o == i) { - if (u > 0) { - var d = t.utils.clone(n) || {}; - (d.position = [a, u]), - (d.index = s.length), - s.push(new t.Token(r.slice(a, o), d)); - } - a = o + 1; - } - } - return s; - }), - (t.tokenizer.separator = /[\s\-]+/); - (t.Pipeline = function () { - this._stack = []; - }), - (t.Pipeline.registeredFunctions = Object.create(null)), - (t.Pipeline.registerFunction = function (e, n) { - n in this.registeredFunctions && - t.utils.warn("Overwriting existing registered function: " + n), - (e.label = n), - (t.Pipeline.registeredFunctions[e.label] = e); - }), - (t.Pipeline.warnIfFunctionNotRegistered = function (e) { - var n = e.label && e.label in this.registeredFunctions; - n || - t.utils.warn( - `Function is not registered with pipeline. This may cause problems when serialising the index. -`, - e, - ); - }), - (t.Pipeline.load = function (e) { - var n = new t.Pipeline(); - return ( - e.forEach(function (r) { - var i = t.Pipeline.registeredFunctions[r]; - if (i) n.add(i); - else throw new Error("Cannot load unregistered function: " + r); - }), - n - ); - }), - (t.Pipeline.prototype.add = function () { - var e = Array.prototype.slice.call(arguments); - e.forEach(function (n) { - t.Pipeline.warnIfFunctionNotRegistered(n), this._stack.push(n); - }, this); - }), - (t.Pipeline.prototype.after = function (e, n) { - t.Pipeline.warnIfFunctionNotRegistered(n); - var r = this._stack.indexOf(e); - if (r == -1) throw new Error("Cannot find existingFn"); - (r = r + 1), this._stack.splice(r, 0, n); - }), - (t.Pipeline.prototype.before = function (e, n) { - t.Pipeline.warnIfFunctionNotRegistered(n); - var r = this._stack.indexOf(e); - if (r == -1) throw new Error("Cannot find existingFn"); - this._stack.splice(r, 0, n); - }), - (t.Pipeline.prototype.remove = function (e) { - var n = this._stack.indexOf(e); - n != -1 && this._stack.splice(n, 1); - }), - (t.Pipeline.prototype.run = function (e) { - for (var n = this._stack.length, r = 0; r < n; r++) { - for (var i = this._stack[r], s = [], o = 0; o < e.length; o++) { - var a = i(e[o], o, e); - if (!(a == null || a === "")) - if (Array.isArray(a)) - for (var l = 0; l < a.length; l++) s.push(a[l]); - else s.push(a); - } - e = s; - } - return e; - }), - (t.Pipeline.prototype.runString = function (e, n) { - var r = new t.Token(e, n); - return this.run([r]).map(function (i) { - return i.toString(); - }); - }), - (t.Pipeline.prototype.reset = function () { - this._stack = []; - }), - (t.Pipeline.prototype.toJSON = function () { - return this._stack.map(function (e) { - return t.Pipeline.warnIfFunctionNotRegistered(e), e.label; - }); - }); - (t.Vector = function (e) { - (this._magnitude = 0), (this.elements = e || []); - }), - (t.Vector.prototype.positionForIndex = function (e) { - if (this.elements.length == 0) return 0; - for ( - var n = 0, - r = this.elements.length / 2, - i = r - n, - s = Math.floor(i / 2), - o = this.elements[s * 2]; - i > 1 && (o < e && (n = s), o > e && (r = s), o != e); - - ) - (i = r - n), - (s = n + Math.floor(i / 2)), - (o = this.elements[s * 2]); - if (o == e || o > e) return s * 2; - if (o < e) return (s + 1) * 2; - }), - (t.Vector.prototype.insert = function (e, n) { - this.upsert(e, n, function () { - throw "duplicate index"; - }); - }), - (t.Vector.prototype.upsert = function (e, n, r) { - this._magnitude = 0; - var i = this.positionForIndex(e); - this.elements[i] == e - ? (this.elements[i + 1] = r(this.elements[i + 1], n)) - : this.elements.splice(i, 0, e, n); - }), - (t.Vector.prototype.magnitude = function () { - if (this._magnitude) return this._magnitude; - for (var e = 0, n = this.elements.length, r = 1; r < n; r += 2) { - var i = this.elements[r]; - e += i * i; - } - return (this._magnitude = Math.sqrt(e)); - }), - (t.Vector.prototype.dot = function (e) { - for ( - var n = 0, - r = this.elements, - i = e.elements, - s = r.length, - o = i.length, - a = 0, - l = 0, - u = 0, - d = 0; - u < s && d < o; - - ) - (a = r[u]), - (l = i[d]), - a < l - ? (u += 2) - : a > l - ? (d += 2) - : a == l && ((n += r[u + 1] * i[d + 1]), (u += 2), (d += 2)); - return n; - }), - (t.Vector.prototype.similarity = function (e) { - return this.dot(e) / this.magnitude() || 0; - }), - (t.Vector.prototype.toArray = function () { - for ( - var e = new Array(this.elements.length / 2), n = 1, r = 0; - n < this.elements.length; - n += 2, r++ - ) - e[r] = this.elements[n]; - return e; - }), - (t.Vector.prototype.toJSON = function () { - return this.elements; - }); - (t.stemmer = (function () { - var e = { - ational: "ate", - tional: "tion", - enci: "ence", - anci: "ance", - izer: "ize", - bli: "ble", - alli: "al", - entli: "ent", - eli: "e", - ousli: "ous", - ization: "ize", - ation: "ate", - ator: "ate", - alism: "al", - iveness: "ive", - fulness: "ful", - ousness: "ous", - aliti: "al", - iviti: "ive", - biliti: "ble", - logi: "log", - }, - n = { - icate: "ic", - ative: "", - alize: "al", - iciti: "ic", - ical: "ic", - ful: "", - ness: "", - }, - r = "[^aeiou]", - i = "[aeiouy]", - s = r + "[^aeiouy]*", - o = i + "[aeiou]*", - a = "^(" + s + ")?" + o + s, - l = "^(" + s + ")?" + o + s + "(" + o + ")?$", - u = "^(" + s + ")?" + o + s + o + s, - d = "^(" + s + ")?" + i, - m = new RegExp(a), - p = new RegExp(u), - b = new RegExp(l), - g = new RegExp(d), - L = /^(.+?)(ss|i)es$/, - f = /^(.+?)([^s])s$/, - y = /^(.+?)eed$/, - S = /^(.+?)(ed|ing)$/, - w = /.$/, - k = /(at|bl|iz)$/, - _ = new RegExp("([^aeiouylsz])\\1$"), - B = new RegExp("^" + s + i + "[^aeiouwxy]$"), - A = /^(.+?[^aeiou])y$/, - j = - /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/, - q = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/, - V = - /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/, - $ = /^(.+?)(s|t)(ion)$/, - C = /^(.+?)e$/, - z = /ll$/, - W = new RegExp("^" + s + i + "[^aeiouwxy]$"), - H = function (c) { - var v, P, T, h, x, O, M; - if (c.length < 3) return c; - if ( - ((T = c.substr(0, 1)), - T == "y" && (c = T.toUpperCase() + c.substr(1)), - (h = L), - (x = f), - h.test(c) - ? (c = c.replace(h, "$1$2")) - : x.test(c) && (c = c.replace(x, "$1$2")), - (h = y), - (x = S), - h.test(c)) - ) { - var E = h.exec(c); - (h = m), h.test(E[1]) && ((h = w), (c = c.replace(h, ""))); - } else if (x.test(c)) { - var E = x.exec(c); - (v = E[1]), - (x = g), - x.test(v) && - ((c = v), - (x = k), - (O = _), - (M = B), - x.test(c) - ? (c = c + "e") - : O.test(c) - ? ((h = w), (c = c.replace(h, ""))) - : M.test(c) && (c = c + "e")); - } - if (((h = A), h.test(c))) { - var E = h.exec(c); - (v = E[1]), (c = v + "i"); - } - if (((h = j), h.test(c))) { - var E = h.exec(c); - (v = E[1]), (P = E[2]), (h = m), h.test(v) && (c = v + e[P]); - } - if (((h = q), h.test(c))) { - var E = h.exec(c); - (v = E[1]), (P = E[2]), (h = m), h.test(v) && (c = v + n[P]); - } - if (((h = V), (x = $), h.test(c))) { - var E = h.exec(c); - (v = E[1]), (h = p), h.test(v) && (c = v); - } else if (x.test(c)) { - var E = x.exec(c); - (v = E[1] + E[2]), (x = p), x.test(v) && (c = v); - } - if (((h = C), h.test(c))) { - var E = h.exec(c); - (v = E[1]), - (h = p), - (x = b), - (O = W), - (h.test(v) || (x.test(v) && !O.test(v))) && (c = v); - } - return ( - (h = z), - (x = p), - h.test(c) && x.test(c) && ((h = w), (c = c.replace(h, ""))), - T == "y" && (c = T.toLowerCase() + c.substr(1)), - c - ); - }; - return function (R) { - return R.update(H); - }; - })()), - t.Pipeline.registerFunction(t.stemmer, "stemmer"); - (t.generateStopWordFilter = function (e) { - var n = e.reduce(function (r, i) { - return (r[i] = i), r; - }, {}); - return function (r) { - if (r && n[r.toString()] !== r.toString()) return r; - }; - }), - (t.stopWordFilter = t.generateStopWordFilter([ - "a", - "able", - "about", - "across", - "after", - "all", - "almost", - "also", - "am", - "among", - "an", - "and", - "any", - "are", - "as", - "at", - "be", - "because", - "been", - "but", - "by", - "can", - "cannot", - "could", - "dear", - "did", - "do", - "does", - "either", - "else", - "ever", - "every", - "for", - "from", - "get", - "got", - "had", - "has", - "have", - "he", - "her", - "hers", - "him", - "his", - "how", - "however", - "i", - "if", - "in", - "into", - "is", - "it", - "its", - "just", - "least", - "let", - "like", - "likely", - "may", - "me", - "might", - "most", - "must", - "my", - "neither", - "no", - "nor", - "not", - "of", - "off", - "often", - "on", - "only", - "or", - "other", - "our", - "own", - "rather", - "said", - "say", - "says", - "she", - "should", - "since", - "so", - "some", - "than", - "that", - "the", - "their", - "them", - "then", - "there", - "these", - "they", - "this", - "tis", - "to", - "too", - "twas", - "us", - "wants", - "was", - "we", - "were", - "what", - "when", - "where", - "which", - "while", - "who", - "whom", - "why", - "will", - "with", - "would", - "yet", - "you", - "your", - ])), - t.Pipeline.registerFunction(t.stopWordFilter, "stopWordFilter"); - (t.trimmer = function (e) { - return e.update(function (n) { - return n.replace(/^\W+/, "").replace(/\W+$/, ""); - }); - }), - t.Pipeline.registerFunction(t.trimmer, "trimmer"); - (t.TokenSet = function () { - (this.final = !1), - (this.edges = {}), - (this.id = t.TokenSet._nextId), - (t.TokenSet._nextId += 1); - }), - (t.TokenSet._nextId = 1), - (t.TokenSet.fromArray = function (e) { - for ( - var n = new t.TokenSet.Builder(), r = 0, i = e.length; - r < i; - r++ - ) - n.insert(e[r]); - return n.finish(), n.root; - }), - (t.TokenSet.fromClause = function (e) { - return "editDistance" in e - ? t.TokenSet.fromFuzzyString(e.term, e.editDistance) - : t.TokenSet.fromString(e.term); - }), - (t.TokenSet.fromFuzzyString = function (e, n) { - for ( - var r = new t.TokenSet(), - i = [{ node: r, editsRemaining: n, str: e }]; - i.length; - - ) { - var s = i.pop(); - if (s.str.length > 0) { - var o = s.str.charAt(0), - a; - o in s.node.edges - ? (a = s.node.edges[o]) - : ((a = new t.TokenSet()), (s.node.edges[o] = a)), - s.str.length == 1 && (a.final = !0), - i.push({ - node: a, - editsRemaining: s.editsRemaining, - str: s.str.slice(1), - }); - } - if (s.editsRemaining != 0) { - if ("*" in s.node.edges) var l = s.node.edges["*"]; - else { - var l = new t.TokenSet(); - s.node.edges["*"] = l; - } - if ( - (s.str.length == 0 && (l.final = !0), - i.push({ - node: l, - editsRemaining: s.editsRemaining - 1, - str: s.str, - }), - s.str.length > 1 && - i.push({ - node: s.node, - editsRemaining: s.editsRemaining - 1, - str: s.str.slice(1), - }), - s.str.length == 1 && (s.node.final = !0), - s.str.length >= 1) - ) { - if ("*" in s.node.edges) var u = s.node.edges["*"]; - else { - var u = new t.TokenSet(); - s.node.edges["*"] = u; - } - s.str.length == 1 && (u.final = !0), - i.push({ - node: u, - editsRemaining: s.editsRemaining - 1, - str: s.str.slice(1), - }); - } - if (s.str.length > 1) { - var d = s.str.charAt(0), - m = s.str.charAt(1), - p; - m in s.node.edges - ? (p = s.node.edges[m]) - : ((p = new t.TokenSet()), (s.node.edges[m] = p)), - s.str.length == 1 && (p.final = !0), - i.push({ - node: p, - editsRemaining: s.editsRemaining - 1, - str: d + s.str.slice(2), - }); - } - } - } - return r; - }), - (t.TokenSet.fromString = function (e) { - for ( - var n = new t.TokenSet(), r = n, i = 0, s = e.length; - i < s; - i++ - ) { - var o = e[i], - a = i == s - 1; - if (o == "*") (n.edges[o] = n), (n.final = a); - else { - var l = new t.TokenSet(); - (l.final = a), (n.edges[o] = l), (n = l); - } - } - return r; - }), - (t.TokenSet.prototype.toArray = function () { - for (var e = [], n = [{ prefix: "", node: this }]; n.length; ) { - var r = n.pop(), - i = Object.keys(r.node.edges), - s = i.length; - r.node.final && (r.prefix.charAt(0), e.push(r.prefix)); - for (var o = 0; o < s; o++) { - var a = i[o]; - n.push({ prefix: r.prefix.concat(a), node: r.node.edges[a] }); - } - } - return e; - }), - (t.TokenSet.prototype.toString = function () { - if (this._str) return this._str; - for ( - var e = this.final ? "1" : "0", - n = Object.keys(this.edges).sort(), - r = n.length, - i = 0; - i < r; - i++ - ) { - var s = n[i], - o = this.edges[s]; - e = e + s + o.id; - } - return e; - }), - (t.TokenSet.prototype.intersect = function (e) { - for ( - var n = new t.TokenSet(), - r = void 0, - i = [{ qNode: e, output: n, node: this }]; - i.length; - - ) { - r = i.pop(); - for ( - var s = Object.keys(r.qNode.edges), - o = s.length, - a = Object.keys(r.node.edges), - l = a.length, - u = 0; - u < o; - u++ - ) - for (var d = s[u], m = 0; m < l; m++) { - var p = a[m]; - if (p == d || d == "*") { - var b = r.node.edges[p], - g = r.qNode.edges[d], - L = b.final && g.final, - f = void 0; - p in r.output.edges - ? ((f = r.output.edges[p]), (f.final = f.final || L)) - : ((f = new t.TokenSet()), - (f.final = L), - (r.output.edges[p] = f)), - i.push({ qNode: g, output: f, node: b }); - } - } - } - return n; - }), - (t.TokenSet.Builder = function () { - (this.previousWord = ""), - (this.root = new t.TokenSet()), - (this.uncheckedNodes = []), - (this.minimizedNodes = {}); - }), - (t.TokenSet.Builder.prototype.insert = function (e) { - var n, - r = 0; - if (e < this.previousWord) - throw new Error("Out of order word insertion"); - for ( - var i = 0; - i < e.length && - i < this.previousWord.length && - e[i] == this.previousWord[i]; - i++ - ) - r++; - this.minimize(r), - this.uncheckedNodes.length == 0 - ? (n = this.root) - : (n = this.uncheckedNodes[this.uncheckedNodes.length - 1].child); - for (var i = r; i < e.length; i++) { - var s = new t.TokenSet(), - o = e[i]; - (n.edges[o] = s), - this.uncheckedNodes.push({ parent: n, char: o, child: s }), - (n = s); - } - (n.final = !0), (this.previousWord = e); - }), - (t.TokenSet.Builder.prototype.finish = function () { - this.minimize(0); - }), - (t.TokenSet.Builder.prototype.minimize = function (e) { - for (var n = this.uncheckedNodes.length - 1; n >= e; n--) { - var r = this.uncheckedNodes[n], - i = r.child.toString(); - i in this.minimizedNodes - ? (r.parent.edges[r.char] = this.minimizedNodes[i]) - : ((r.child._str = i), (this.minimizedNodes[i] = r.child)), - this.uncheckedNodes.pop(); - } - }); - (t.Index = function (e) { - (this.invertedIndex = e.invertedIndex), - (this.fieldVectors = e.fieldVectors), - (this.tokenSet = e.tokenSet), - (this.fields = e.fields), - (this.pipeline = e.pipeline); - }), - (t.Index.prototype.search = function (e) { - return this.query(function (n) { - var r = new t.QueryParser(e, n); - r.parse(); - }); - }), - (t.Index.prototype.query = function (e) { - for ( - var n = new t.Query(this.fields), - r = Object.create(null), - i = Object.create(null), - s = Object.create(null), - o = Object.create(null), - a = Object.create(null), - l = 0; - l < this.fields.length; - l++ - ) - i[this.fields[l]] = new t.Vector(); - e.call(n, n); - for (var l = 0; l < n.clauses.length; l++) { - var u = n.clauses[l], - d = null, - m = t.Set.empty; - u.usePipeline - ? (d = this.pipeline.runString(u.term, { fields: u.fields })) - : (d = [u.term]); - for (var p = 0; p < d.length; p++) { - var b = d[p]; - u.term = b; - var g = t.TokenSet.fromClause(u), - L = this.tokenSet.intersect(g).toArray(); - if (L.length === 0 && u.presence === t.Query.presence.REQUIRED) { - for (var f = 0; f < u.fields.length; f++) { - var y = u.fields[f]; - o[y] = t.Set.empty; - } - break; - } - for (var S = 0; S < L.length; S++) - for ( - var w = L[S], k = this.invertedIndex[w], _ = k._index, f = 0; - f < u.fields.length; - f++ - ) { - var y = u.fields[f], - B = k[y], - A = Object.keys(B), - j = w + "/" + y, - q = new t.Set(A); - if ( - (u.presence == t.Query.presence.REQUIRED && - ((m = m.union(q)), - o[y] === void 0 && (o[y] = t.Set.complete)), - u.presence == t.Query.presence.PROHIBITED) - ) { - a[y] === void 0 && (a[y] = t.Set.empty), - (a[y] = a[y].union(q)); - continue; - } - if ( - (i[y].upsert(_, u.boost, function (Ie, Ce) { - return Ie + Ce; - }), - !s[j]) - ) { - for (var V = 0; V < A.length; V++) { - var $ = A[V], - C = new t.FieldRef($, y), - z = B[$], - W; - (W = r[C]) === void 0 - ? (r[C] = new t.MatchData(w, y, z)) - : W.add(w, y, z); - } - s[j] = !0; - } - } - } - if (u.presence === t.Query.presence.REQUIRED) - for (var f = 0; f < u.fields.length; f++) { - var y = u.fields[f]; - o[y] = o[y].intersect(m); - } - } - for ( - var H = t.Set.complete, R = t.Set.empty, l = 0; - l < this.fields.length; - l++ - ) { - var y = this.fields[l]; - o[y] && (H = H.intersect(o[y])), a[y] && (R = R.union(a[y])); - } - var c = Object.keys(r), - v = [], - P = Object.create(null); - if (n.isNegated()) { - c = Object.keys(this.fieldVectors); - for (var l = 0; l < c.length; l++) { - var C = c[l], - T = t.FieldRef.fromString(C); - r[C] = new t.MatchData(); - } - } - for (var l = 0; l < c.length; l++) { - var T = t.FieldRef.fromString(c[l]), - h = T.docRef; - if (H.contains(h) && !R.contains(h)) { - var x = this.fieldVectors[T], - O = i[T.fieldName].similarity(x), - M; - if ((M = P[h]) !== void 0) - (M.score += O), M.matchData.combine(r[T]); - else { - var E = { ref: h, score: O, matchData: r[T] }; - (P[h] = E), v.push(E); - } - } - } - return v.sort(function (ke, Qe) { - return Qe.score - ke.score; - }); - }), - (t.Index.prototype.toJSON = function () { - var e = Object.keys(this.invertedIndex) - .sort() - .map(function (r) { - return [r, this.invertedIndex[r]]; - }, this), - n = Object.keys(this.fieldVectors).map(function (r) { - return [r, this.fieldVectors[r].toJSON()]; - }, this); - return { - version: t.version, - fields: this.fields, - fieldVectors: n, - invertedIndex: e, - pipeline: this.pipeline.toJSON(), - }; - }), - (t.Index.load = function (e) { - var n = {}, - r = {}, - i = e.fieldVectors, - s = Object.create(null), - o = e.invertedIndex, - a = new t.TokenSet.Builder(), - l = t.Pipeline.load(e.pipeline); - e.version != t.version && - t.utils.warn( - "Version mismatch when loading serialised index. Current version of lunr '" + - t.version + - "' does not match serialized index '" + - e.version + - "'", - ); - for (var u = 0; u < i.length; u++) { - var d = i[u], - m = d[0], - p = d[1]; - r[m] = new t.Vector(p); - } - for (var u = 0; u < o.length; u++) { - var d = o[u], - b = d[0], - g = d[1]; - a.insert(b), (s[b] = g); - } - return ( - a.finish(), - (n.fields = e.fields), - (n.fieldVectors = r), - (n.invertedIndex = s), - (n.tokenSet = a.root), - (n.pipeline = l), - new t.Index(n) - ); - }); - (t.Builder = function () { - (this._ref = "id"), - (this._fields = Object.create(null)), - (this._documents = Object.create(null)), - (this.invertedIndex = Object.create(null)), - (this.fieldTermFrequencies = {}), - (this.fieldLengths = {}), - (this.tokenizer = t.tokenizer), - (this.pipeline = new t.Pipeline()), - (this.searchPipeline = new t.Pipeline()), - (this.documentCount = 0), - (this._b = 0.75), - (this._k1 = 1.2), - (this.termIndex = 0), - (this.metadataWhitelist = []); - }), - (t.Builder.prototype.ref = function (e) { - this._ref = e; - }), - (t.Builder.prototype.field = function (e, n) { - if (/\//.test(e)) - throw new RangeError( - "Field '" + e + "' contains illegal character '/'", - ); - this._fields[e] = n || {}; - }), - (t.Builder.prototype.b = function (e) { - e < 0 ? (this._b = 0) : e > 1 ? (this._b = 1) : (this._b = e); - }), - (t.Builder.prototype.k1 = function (e) { - this._k1 = e; - }), - (t.Builder.prototype.add = function (e, n) { - var r = e[this._ref], - i = Object.keys(this._fields); - (this._documents[r] = n || {}), (this.documentCount += 1); - for (var s = 0; s < i.length; s++) { - var o = i[s], - a = this._fields[o].extractor, - l = a ? a(e) : e[o], - u = this.tokenizer(l, { fields: [o] }), - d = this.pipeline.run(u), - m = new t.FieldRef(r, o), - p = Object.create(null); - (this.fieldTermFrequencies[m] = p), - (this.fieldLengths[m] = 0), - (this.fieldLengths[m] += d.length); - for (var b = 0; b < d.length; b++) { - var g = d[b]; - if ( - (p[g] == null && (p[g] = 0), - (p[g] += 1), - this.invertedIndex[g] == null) - ) { - var L = Object.create(null); - (L._index = this.termIndex), (this.termIndex += 1); - for (var f = 0; f < i.length; f++) - L[i[f]] = Object.create(null); - this.invertedIndex[g] = L; - } - this.invertedIndex[g][o][r] == null && - (this.invertedIndex[g][o][r] = Object.create(null)); - for (var y = 0; y < this.metadataWhitelist.length; y++) { - var S = this.metadataWhitelist[y], - w = g.metadata[S]; - this.invertedIndex[g][o][r][S] == null && - (this.invertedIndex[g][o][r][S] = []), - this.invertedIndex[g][o][r][S].push(w); - } - } - } - }), - (t.Builder.prototype.calculateAverageFieldLengths = function () { - for ( - var e = Object.keys(this.fieldLengths), - n = e.length, - r = {}, - i = {}, - s = 0; - s < n; - s++ - ) { - var o = t.FieldRef.fromString(e[s]), - a = o.fieldName; - i[a] || (i[a] = 0), - (i[a] += 1), - r[a] || (r[a] = 0), - (r[a] += this.fieldLengths[o]); - } - for (var l = Object.keys(this._fields), s = 0; s < l.length; s++) { - var u = l[s]; - r[u] = r[u] / i[u]; - } - this.averageFieldLength = r; - }), - (t.Builder.prototype.createFieldVectors = function () { - for ( - var e = {}, - n = Object.keys(this.fieldTermFrequencies), - r = n.length, - i = Object.create(null), - s = 0; - s < r; - s++ - ) { - for ( - var o = t.FieldRef.fromString(n[s]), - a = o.fieldName, - l = this.fieldLengths[o], - u = new t.Vector(), - d = this.fieldTermFrequencies[o], - m = Object.keys(d), - p = m.length, - b = this._fields[a].boost || 1, - g = this._documents[o.docRef].boost || 1, - L = 0; - L < p; - L++ - ) { - var f = m[L], - y = d[f], - S = this.invertedIndex[f]._index, - w, - k, - _; - i[f] === void 0 - ? ((w = t.idf(this.invertedIndex[f], this.documentCount)), - (i[f] = w)) - : (w = i[f]), - (k = - (w * ((this._k1 + 1) * y)) / - (this._k1 * - (1 - this._b + this._b * (l / this.averageFieldLength[a])) + - y)), - (k *= b), - (k *= g), - (_ = Math.round(k * 1e3) / 1e3), - u.insert(S, _); - } - e[o] = u; - } - this.fieldVectors = e; - }), - (t.Builder.prototype.createTokenSet = function () { - this.tokenSet = t.TokenSet.fromArray( - Object.keys(this.invertedIndex).sort(), - ); - }), - (t.Builder.prototype.build = function () { - return ( - this.calculateAverageFieldLengths(), - this.createFieldVectors(), - this.createTokenSet(), - new t.Index({ - invertedIndex: this.invertedIndex, - fieldVectors: this.fieldVectors, - tokenSet: this.tokenSet, - fields: Object.keys(this._fields), - pipeline: this.searchPipeline, - }) - ); - }), - (t.Builder.prototype.use = function (e) { - var n = Array.prototype.slice.call(arguments, 1); - n.unshift(this), e.apply(this, n); - }), - (t.MatchData = function (e, n, r) { - for ( - var i = Object.create(null), s = Object.keys(r || {}), o = 0; - o < s.length; - o++ - ) { - var a = s[o]; - i[a] = r[a].slice(); - } - (this.metadata = Object.create(null)), - e !== void 0 && - ((this.metadata[e] = Object.create(null)), - (this.metadata[e][n] = i)); - }), - (t.MatchData.prototype.combine = function (e) { - for (var n = Object.keys(e.metadata), r = 0; r < n.length; r++) { - var i = n[r], - s = Object.keys(e.metadata[i]); - this.metadata[i] == null && - (this.metadata[i] = Object.create(null)); - for (var o = 0; o < s.length; o++) { - var a = s[o], - l = Object.keys(e.metadata[i][a]); - this.metadata[i][a] == null && - (this.metadata[i][a] = Object.create(null)); - for (var u = 0; u < l.length; u++) { - var d = l[u]; - this.metadata[i][a][d] == null - ? (this.metadata[i][a][d] = e.metadata[i][a][d]) - : (this.metadata[i][a][d] = this.metadata[i][a][d].concat( - e.metadata[i][a][d], - )); - } - } - } - }), - (t.MatchData.prototype.add = function (e, n, r) { - if (!(e in this.metadata)) { - (this.metadata[e] = Object.create(null)), (this.metadata[e][n] = r); - return; - } - if (!(n in this.metadata[e])) { - this.metadata[e][n] = r; - return; - } - for (var i = Object.keys(r), s = 0; s < i.length; s++) { - var o = i[s]; - o in this.metadata[e][n] - ? (this.metadata[e][n][o] = this.metadata[e][n][o].concat(r[o])) - : (this.metadata[e][n][o] = r[o]); - } - }), - (t.Query = function (e) { - (this.clauses = []), (this.allFields = e); - }), - (t.Query.wildcard = new String("*")), - (t.Query.wildcard.NONE = 0), - (t.Query.wildcard.LEADING = 1), - (t.Query.wildcard.TRAILING = 2), - (t.Query.presence = { OPTIONAL: 1, REQUIRED: 2, PROHIBITED: 3 }), - (t.Query.prototype.clause = function (e) { - return ( - "fields" in e || (e.fields = this.allFields), - "boost" in e || (e.boost = 1), - "usePipeline" in e || (e.usePipeline = !0), - "wildcard" in e || (e.wildcard = t.Query.wildcard.NONE), - e.wildcard & t.Query.wildcard.LEADING && - e.term.charAt(0) != t.Query.wildcard && - (e.term = "*" + e.term), - e.wildcard & t.Query.wildcard.TRAILING && - e.term.slice(-1) != t.Query.wildcard && - (e.term = "" + e.term + "*"), - "presence" in e || (e.presence = t.Query.presence.OPTIONAL), - this.clauses.push(e), - this - ); - }), - (t.Query.prototype.isNegated = function () { - for (var e = 0; e < this.clauses.length; e++) - if (this.clauses[e].presence != t.Query.presence.PROHIBITED) - return !1; - return !0; - }), - (t.Query.prototype.term = function (e, n) { - if (Array.isArray(e)) - return ( - e.forEach(function (i) { - this.term(i, t.utils.clone(n)); - }, this), - this - ); - var r = n || {}; - return (r.term = e.toString()), this.clause(r), this; - }), - (t.QueryParseError = function (e, n, r) { - (this.name = "QueryParseError"), - (this.message = e), - (this.start = n), - (this.end = r); - }), - (t.QueryParseError.prototype = new Error()), - (t.QueryLexer = function (e) { - (this.lexemes = []), - (this.str = e), - (this.length = e.length), - (this.pos = 0), - (this.start = 0), - (this.escapeCharPositions = []); - }), - (t.QueryLexer.prototype.run = function () { - for (var e = t.QueryLexer.lexText; e; ) e = e(this); - }), - (t.QueryLexer.prototype.sliceString = function () { - for ( - var e = [], n = this.start, r = this.pos, i = 0; - i < this.escapeCharPositions.length; - i++ - ) - (r = this.escapeCharPositions[i]), - e.push(this.str.slice(n, r)), - (n = r + 1); - return ( - e.push(this.str.slice(n, this.pos)), - (this.escapeCharPositions.length = 0), - e.join("") - ); - }), - (t.QueryLexer.prototype.emit = function (e) { - this.lexemes.push({ - type: e, - str: this.sliceString(), - start: this.start, - end: this.pos, - }), - (this.start = this.pos); - }), - (t.QueryLexer.prototype.escapeCharacter = function () { - this.escapeCharPositions.push(this.pos - 1), (this.pos += 1); - }), - (t.QueryLexer.prototype.next = function () { - if (this.pos >= this.length) return t.QueryLexer.EOS; - var e = this.str.charAt(this.pos); - return (this.pos += 1), e; - }), - (t.QueryLexer.prototype.width = function () { - return this.pos - this.start; - }), - (t.QueryLexer.prototype.ignore = function () { - this.start == this.pos && (this.pos += 1), (this.start = this.pos); - }), - (t.QueryLexer.prototype.backup = function () { - this.pos -= 1; - }), - (t.QueryLexer.prototype.acceptDigitRun = function () { - var e, n; - do (e = this.next()), (n = e.charCodeAt(0)); - while (n > 47 && n < 58); - e != t.QueryLexer.EOS && this.backup(); - }), - (t.QueryLexer.prototype.more = function () { - return this.pos < this.length; - }), - (t.QueryLexer.EOS = "EOS"), - (t.QueryLexer.FIELD = "FIELD"), - (t.QueryLexer.TERM = "TERM"), - (t.QueryLexer.EDIT_DISTANCE = "EDIT_DISTANCE"), - (t.QueryLexer.BOOST = "BOOST"), - (t.QueryLexer.PRESENCE = "PRESENCE"), - (t.QueryLexer.lexField = function (e) { - return ( - e.backup(), - e.emit(t.QueryLexer.FIELD), - e.ignore(), - t.QueryLexer.lexText - ); - }), - (t.QueryLexer.lexTerm = function (e) { - if ( - (e.width() > 1 && (e.backup(), e.emit(t.QueryLexer.TERM)), - e.ignore(), - e.more()) - ) - return t.QueryLexer.lexText; - }), - (t.QueryLexer.lexEditDistance = function (e) { - return ( - e.ignore(), - e.acceptDigitRun(), - e.emit(t.QueryLexer.EDIT_DISTANCE), - t.QueryLexer.lexText - ); - }), - (t.QueryLexer.lexBoost = function (e) { - return ( - e.ignore(), - e.acceptDigitRun(), - e.emit(t.QueryLexer.BOOST), - t.QueryLexer.lexText - ); - }), - (t.QueryLexer.lexEOS = function (e) { - e.width() > 0 && e.emit(t.QueryLexer.TERM); - }), - (t.QueryLexer.termSeparator = t.tokenizer.separator), - (t.QueryLexer.lexText = function (e) { - for (;;) { - var n = e.next(); - if (n == t.QueryLexer.EOS) return t.QueryLexer.lexEOS; - if (n.charCodeAt(0) == 92) { - e.escapeCharacter(); - continue; - } - if (n == ":") return t.QueryLexer.lexField; - if (n == "~") - return ( - e.backup(), - e.width() > 0 && e.emit(t.QueryLexer.TERM), - t.QueryLexer.lexEditDistance - ); - if (n == "^") - return ( - e.backup(), - e.width() > 0 && e.emit(t.QueryLexer.TERM), - t.QueryLexer.lexBoost - ); - if ((n == "+" && e.width() === 1) || (n == "-" && e.width() === 1)) - return e.emit(t.QueryLexer.PRESENCE), t.QueryLexer.lexText; - if (n.match(t.QueryLexer.termSeparator)) - return t.QueryLexer.lexTerm; - } - }), - (t.QueryParser = function (e, n) { - (this.lexer = new t.QueryLexer(e)), - (this.query = n), - (this.currentClause = {}), - (this.lexemeIdx = 0); - }), - (t.QueryParser.prototype.parse = function () { - this.lexer.run(), (this.lexemes = this.lexer.lexemes); - for (var e = t.QueryParser.parseClause; e; ) e = e(this); - return this.query; - }), - (t.QueryParser.prototype.peekLexeme = function () { - return this.lexemes[this.lexemeIdx]; - }), - (t.QueryParser.prototype.consumeLexeme = function () { - var e = this.peekLexeme(); - return (this.lexemeIdx += 1), e; - }), - (t.QueryParser.prototype.nextClause = function () { - var e = this.currentClause; - this.query.clause(e), (this.currentClause = {}); - }), - (t.QueryParser.parseClause = function (e) { - var n = e.peekLexeme(); - if (n != null) - switch (n.type) { - case t.QueryLexer.PRESENCE: - return t.QueryParser.parsePresence; - case t.QueryLexer.FIELD: - return t.QueryParser.parseField; - case t.QueryLexer.TERM: - return t.QueryParser.parseTerm; - default: - var r = "expected either a field or a term, found " + n.type; - throw ( - (n.str.length >= 1 && (r += " with value '" + n.str + "'"), - new t.QueryParseError(r, n.start, n.end)) - ); - } - }), - (t.QueryParser.parsePresence = function (e) { - var n = e.consumeLexeme(); - if (n != null) { - switch (n.str) { - case "-": - e.currentClause.presence = t.Query.presence.PROHIBITED; - break; - case "+": - e.currentClause.presence = t.Query.presence.REQUIRED; - break; - default: - var r = "unrecognised presence operator'" + n.str + "'"; - throw new t.QueryParseError(r, n.start, n.end); - } - var i = e.peekLexeme(); - if (i == null) { - var r = "expecting term or field, found nothing"; - throw new t.QueryParseError(r, n.start, n.end); - } - switch (i.type) { - case t.QueryLexer.FIELD: - return t.QueryParser.parseField; - case t.QueryLexer.TERM: - return t.QueryParser.parseTerm; - default: - var r = "expecting term or field, found '" + i.type + "'"; - throw new t.QueryParseError(r, i.start, i.end); - } - } - }), - (t.QueryParser.parseField = function (e) { - var n = e.consumeLexeme(); - if (n != null) { - if (e.query.allFields.indexOf(n.str) == -1) { - var r = e.query.allFields - .map(function (o) { - return "'" + o + "'"; - }) - .join(", "), - i = "unrecognised field '" + n.str + "', possible fields: " + r; - throw new t.QueryParseError(i, n.start, n.end); - } - e.currentClause.fields = [n.str]; - var s = e.peekLexeme(); - if (s == null) { - var i = "expecting term, found nothing"; - throw new t.QueryParseError(i, n.start, n.end); - } - switch (s.type) { - case t.QueryLexer.TERM: - return t.QueryParser.parseTerm; - default: - var i = "expecting term, found '" + s.type + "'"; - throw new t.QueryParseError(i, s.start, s.end); - } - } - }), - (t.QueryParser.parseTerm = function (e) { - var n = e.consumeLexeme(); - if (n != null) { - (e.currentClause.term = n.str.toLowerCase()), - n.str.indexOf("*") != -1 && (e.currentClause.usePipeline = !1); - var r = e.peekLexeme(); - if (r == null) { - e.nextClause(); - return; - } - switch (r.type) { - case t.QueryLexer.TERM: - return e.nextClause(), t.QueryParser.parseTerm; - case t.QueryLexer.FIELD: - return e.nextClause(), t.QueryParser.parseField; - case t.QueryLexer.EDIT_DISTANCE: - return t.QueryParser.parseEditDistance; - case t.QueryLexer.BOOST: - return t.QueryParser.parseBoost; - case t.QueryLexer.PRESENCE: - return e.nextClause(), t.QueryParser.parsePresence; - default: - var i = "Unexpected lexeme type '" + r.type + "'"; - throw new t.QueryParseError(i, r.start, r.end); - } - } - }), - (t.QueryParser.parseEditDistance = function (e) { - var n = e.consumeLexeme(); - if (n != null) { - var r = parseInt(n.str, 10); - if (isNaN(r)) { - var i = "edit distance must be numeric"; - throw new t.QueryParseError(i, n.start, n.end); - } - e.currentClause.editDistance = r; - var s = e.peekLexeme(); - if (s == null) { - e.nextClause(); - return; - } - switch (s.type) { - case t.QueryLexer.TERM: - return e.nextClause(), t.QueryParser.parseTerm; - case t.QueryLexer.FIELD: - return e.nextClause(), t.QueryParser.parseField; - case t.QueryLexer.EDIT_DISTANCE: - return t.QueryParser.parseEditDistance; - case t.QueryLexer.BOOST: - return t.QueryParser.parseBoost; - case t.QueryLexer.PRESENCE: - return e.nextClause(), t.QueryParser.parsePresence; - default: - var i = "Unexpected lexeme type '" + s.type + "'"; - throw new t.QueryParseError(i, s.start, s.end); - } - } - }), - (t.QueryParser.parseBoost = function (e) { - var n = e.consumeLexeme(); - if (n != null) { - var r = parseInt(n.str, 10); - if (isNaN(r)) { - var i = "boost must be numeric"; - throw new t.QueryParseError(i, n.start, n.end); - } - e.currentClause.boost = r; - var s = e.peekLexeme(); - if (s == null) { - e.nextClause(); - return; - } - switch (s.type) { - case t.QueryLexer.TERM: - return e.nextClause(), t.QueryParser.parseTerm; - case t.QueryLexer.FIELD: - return e.nextClause(), t.QueryParser.parseField; - case t.QueryLexer.EDIT_DISTANCE: - return t.QueryParser.parseEditDistance; - case t.QueryLexer.BOOST: - return t.QueryParser.parseBoost; - case t.QueryLexer.PRESENCE: - return e.nextClause(), t.QueryParser.parsePresence; - default: - var i = "Unexpected lexeme type '" + s.type + "'"; - throw new t.QueryParseError(i, s.start, s.end); - } - } - }), - (function (e, n) { - typeof define == "function" && define.amd - ? define(n) - : typeof ae == "object" - ? (le.exports = n()) - : (e.lunr = n()); - })(this, function () { - return t; - }); - })(); - }); - var se = []; - function G(t, e) { - se.push({ selector: e, constructor: t }); - } - var U = class { - constructor() { - this.alwaysVisibleMember = null; - this.createComponents(document.body), - this.ensureFocusedElementVisible(), - this.listenForCodeCopies(), - window.addEventListener("hashchange", () => - this.ensureFocusedElementVisible(), - ), - document.body.style.display || - (this.ensureFocusedElementVisible(), - this.updateIndexVisibility(), - this.scrollToHash()); - } - createComponents(e) { - se.forEach((n) => { - e.querySelectorAll(n.selector).forEach((r) => { - r.dataset.hasInstance || - (new n.constructor({ el: r, app: this }), - (r.dataset.hasInstance = String(!0))); - }); - }); - } - filterChanged() { - this.ensureFocusedElementVisible(); - } - showPage() { - document.body.style.display && - (document.body.style.removeProperty("display"), - this.ensureFocusedElementVisible(), - this.updateIndexVisibility(), - this.scrollToHash()); - } - scrollToHash() { - if (location.hash) { - let e = document.getElementById(location.hash.substring(1)); - if (!e) return; - e.scrollIntoView({ behavior: "instant", block: "start" }); - } - } - ensureActivePageVisible() { - let e = document.querySelector(".tsd-navigation .current"), - n = e?.parentElement; - for (; n && !n.classList.contains(".tsd-navigation"); ) - n instanceof HTMLDetailsElement && (n.open = !0), (n = n.parentElement); - if (e && !Ve(e)) { - let r = - e.getBoundingClientRect().top - - document.documentElement.clientHeight / 4; - (document.querySelector(".site-menu").scrollTop = r), - (document.querySelector(".col-sidebar").scrollTop = r); - } - } - updateIndexVisibility() { - let e = document.querySelector(".tsd-index-content"), - n = e?.open; - e && (e.open = !0), - document.querySelectorAll(".tsd-index-section").forEach((r) => { - r.style.display = "block"; - let i = Array.from(r.querySelectorAll(".tsd-index-link")).every( - (s) => s.offsetParent == null, - ); - r.style.display = i ? "none" : "block"; - }), - e && (e.open = n); - } - ensureFocusedElementVisible() { - if ( - (this.alwaysVisibleMember && - (this.alwaysVisibleMember.classList.remove("always-visible"), - this.alwaysVisibleMember.firstElementChild.remove(), - (this.alwaysVisibleMember = null)), - !location.hash) - ) - return; - let e = document.getElementById(location.hash.substring(1)); - if (!e) return; - let n = e.parentElement; - for (; n && n.tagName !== "SECTION"; ) n = n.parentElement; - if (!n) return; - let r = n.offsetParent == null, - i = n; - for (; i !== document.body; ) - i instanceof HTMLDetailsElement && (i.open = !0), (i = i.parentElement); - if (n.offsetParent == null) { - (this.alwaysVisibleMember = n), n.classList.add("always-visible"); - let s = document.createElement("p"); - s.classList.add("warning"), - (s.textContent = window.translations.normally_hidden), - n.prepend(s); - } - r && e.scrollIntoView(); - } - listenForCodeCopies() { - document.querySelectorAll("pre > button").forEach((e) => { - let n; - e.addEventListener("click", () => { - e.previousElementSibling instanceof HTMLElement && - navigator.clipboard.writeText( - e.previousElementSibling.innerText.trim(), - ), - (e.textContent = window.translations.copied), - e.classList.add("visible"), - clearTimeout(n), - (n = setTimeout(() => { - e.classList.remove("visible"), - (n = setTimeout(() => { - e.textContent = window.translations.copy; - }, 100)); - }, 1e3)); - }); - }); - } - }; - function Ve(t) { - let e = t.getBoundingClientRect(), - n = Math.max(document.documentElement.clientHeight, window.innerHeight); - return !(e.bottom < 0 || e.top - n >= 0); - } - var oe = (t, e = 100) => { - let n; - return () => { - clearTimeout(n), (n = setTimeout(() => t(), e)); - }; - }; - var pe = Ae(ue()); - async function ce(t, e) { - if (!window.searchData) return; - let n = await fetch(window.searchData), - r = new Blob([await n.arrayBuffer()]) - .stream() - .pipeThrough(new DecompressionStream("gzip")), - i = await new Response(r).json(); - (t.data = i), - (t.index = pe.Index.load(i.index)), - e.classList.remove("loading"), - e.classList.add("ready"); - } - function fe() { - let t = document.getElementById("tsd-search"); - if (!t) return; - let e = { base: t.dataset.base + "/" }, - n = document.getElementById("tsd-search-script"); - t.classList.add("loading"), - n && - (n.addEventListener("error", () => { - t.classList.remove("loading"), t.classList.add("failure"); - }), - n.addEventListener("load", () => { - ce(e, t); - }), - ce(e, t)); - let r = document.querySelector("#tsd-search input"), - i = document.querySelector("#tsd-search .results"); - if (!r || !i) - throw new Error( - "The input field or the result list wrapper was not found", - ); - i.addEventListener("mouseup", () => { - te(t); - }), - r.addEventListener("focus", () => t.classList.add("has-focus")), - He(t, i, r, e); - } - function He(t, e, n, r) { - n.addEventListener( - "input", - oe(() => { - Ne(t, e, n, r); - }, 200), - ), - n.addEventListener("keydown", (i) => { - i.key == "Enter" - ? Be(e, t) - : i.key == "ArrowUp" - ? (de(e, n, -1), i.preventDefault()) - : i.key === "ArrowDown" && (de(e, n, 1), i.preventDefault()); - }), - document.body.addEventListener("keypress", (i) => { - i.altKey || - i.ctrlKey || - i.metaKey || - (!n.matches(":focus") && - i.key === "/" && - (i.preventDefault(), n.focus())); - }), - document.body.addEventListener("keyup", (i) => { - t.classList.contains("has-focus") && - (i.key === "Escape" || - (!e.matches(":focus-within") && !n.matches(":focus"))) && - (n.blur(), te(t)); - }); - } - function te(t) { - t.classList.remove("has-focus"); - } - function Ne(t, e, n, r) { - if (!r.index || !r.data) return; - e.textContent = ""; - let i = n.value.trim(), - s; - if (i) { - let o = i - .split(" ") - .map((a) => (a.length ? `*${a}*` : "")) - .join(" "); - s = r.index.search(o); - } else s = []; - for (let o = 0; o < s.length; o++) { - let a = s[o], - l = r.data.rows[Number(a.ref)], - u = 1; - l.name.toLowerCase().startsWith(i.toLowerCase()) && - (u *= 1 + 1 / (1 + Math.abs(l.name.length - i.length))), - (a.score *= u); - } - if (s.length === 0) { - let o = document.createElement("li"); - o.classList.add("no-results"); - let a = document.createElement("span"); - (a.textContent = "No results found"), o.appendChild(a), e.appendChild(o); - } - s.sort((o, a) => a.score - o.score); - for (let o = 0, a = Math.min(10, s.length); o < a; o++) { - let l = r.data.rows[Number(s[o].ref)], - u = ``, - d = he(l.name, i); - globalThis.DEBUG_SEARCH_WEIGHTS && - (d += ` (score: ${s[o].score.toFixed(2)})`), - l.parent && - (d = ` - ${he(l.parent, i)}.${d}`); - let m = document.createElement("li"); - m.classList.value = l.classes ?? ""; - let p = document.createElement("a"); - (p.href = r.base + l.url), - (p.innerHTML = u + d), - m.append(p), - p.addEventListener("focus", () => { - e.querySelector(".current")?.classList.remove("current"), - m.classList.add("current"); - }), - e.appendChild(m); - } - } - function de(t, e, n) { - let r = t.querySelector(".current"); - if (!r) - (r = t.querySelector(n == 1 ? "li:first-child" : "li:last-child")), - r && r.classList.add("current"); - else { - let i = r; - if (n === 1) - do i = i.nextElementSibling ?? void 0; - while (i instanceof HTMLElement && i.offsetParent == null); - else - do i = i.previousElementSibling ?? void 0; - while (i instanceof HTMLElement && i.offsetParent == null); - i - ? (r.classList.remove("current"), i.classList.add("current")) - : n === -1 && (r.classList.remove("current"), e.focus()); - } - } - function Be(t, e) { - let n = t.querySelector(".current"); - if ((n || (n = t.querySelector("li:first-child")), n)) { - let r = n.querySelector("a"); - r && (window.location.href = r.href), te(e); - } - } - function he(t, e) { - if (e === "") return t; - let n = t.toLocaleLowerCase(), - r = e.toLocaleLowerCase(), - i = [], - s = 0, - o = n.indexOf(r); - for (; o != -1; ) - i.push( - ee(t.substring(s, o)), - `${ee(t.substring(o, o + r.length))}`, - ), - (s = o + r.length), - (o = n.indexOf(r, s)); - return i.push(ee(t.substring(s))), i.join(""); - } - var je = { - "&": "&", - "<": "<", - ">": ">", - "'": "'", - '"': """, - }; - function ee(t) { - return t.replace(/[&<>"'"]/g, (e) => je[e]); - } - var I = class { - constructor(e) { - (this.el = e.el), (this.app = e.app); - } - }; - var F = "mousedown", - ye = "mousemove", - N = "mouseup", - J = { x: 0, y: 0 }, - me = !1, - ne = !1, - qe = !1, - D = !1, - ve = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent, - ); - document.documentElement.classList.add(ve ? "is-mobile" : "not-mobile"); - ve && - "ontouchstart" in document.documentElement && - ((qe = !0), (F = "touchstart"), (ye = "touchmove"), (N = "touchend")); - document.addEventListener(F, (t) => { - (ne = !0), (D = !1); - let e = F == "touchstart" ? t.targetTouches[0] : t; - (J.y = e.pageY || 0), (J.x = e.pageX || 0); - }); - document.addEventListener(ye, (t) => { - if (ne && !D) { - let e = F == "touchstart" ? t.targetTouches[0] : t, - n = J.x - (e.pageX || 0), - r = J.y - (e.pageY || 0); - D = Math.sqrt(n * n + r * r) > 10; - } - }); - document.addEventListener(N, () => { - ne = !1; - }); - document.addEventListener("click", (t) => { - me && (t.preventDefault(), t.stopImmediatePropagation(), (me = !1)); - }); - var X = class extends I { - constructor(e) { - super(e), - (this.className = this.el.dataset.toggle || ""), - this.el.addEventListener(N, (n) => this.onPointerUp(n)), - this.el.addEventListener("click", (n) => n.preventDefault()), - document.addEventListener(F, (n) => this.onDocumentPointerDown(n)), - document.addEventListener(N, (n) => this.onDocumentPointerUp(n)); - } - setActive(e) { - if (this.active == e) return; - (this.active = e), - document.documentElement.classList.toggle("has-" + this.className, e), - this.el.classList.toggle("active", e); - let n = (this.active ? "to-has-" : "from-has-") + this.className; - document.documentElement.classList.add(n), - setTimeout(() => document.documentElement.classList.remove(n), 500); - } - onPointerUp(e) { - D || (this.setActive(!0), e.preventDefault()); - } - onDocumentPointerDown(e) { - if (this.active) { - if (e.target.closest(".col-sidebar, .tsd-filter-group")) return; - this.setActive(!1); - } - } - onDocumentPointerUp(e) { - if (!D && this.active && e.target.closest(".col-sidebar")) { - let n = e.target.closest("a"); - if (n) { - let r = window.location.href; - r.indexOf("#") != -1 && (r = r.substring(0, r.indexOf("#"))), - n.href.substring(0, r.length) == r && - setTimeout(() => this.setActive(!1), 250); - } - } - } - }; - var re; - try { - re = localStorage; - } catch { - re = { - getItem() { - return null; - }, - setItem() {}, - }; - } - var Q = re; - var ge = document.head.appendChild(document.createElement("style")); - ge.dataset.for = "filters"; - var Y = class extends I { - constructor(e) { - super(e), - (this.key = `filter-${this.el.name}`), - (this.value = this.el.checked), - this.el.addEventListener("change", () => { - this.setLocalStorage(this.el.checked); - }), - this.setLocalStorage(this.fromLocalStorage()), - (ge.innerHTML += `html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } -`), - this.app.updateIndexVisibility(); - } - fromLocalStorage() { - let e = Q.getItem(this.key); - return e ? e === "true" : this.el.checked; - } - setLocalStorage(e) { - Q.setItem(this.key, e.toString()), - (this.value = e), - this.handleValueChange(); - } - handleValueChange() { - (this.el.checked = this.value), - document.documentElement.classList.toggle(this.key, this.value), - this.app.filterChanged(), - this.app.updateIndexVisibility(); - } - }; - var Z = class extends I { - constructor(e) { - super(e), - (this.summary = this.el.querySelector(".tsd-accordion-summary")), - (this.icon = this.summary.querySelector("svg")), - (this.key = `tsd-accordion-${this.summary.dataset.key ?? this.summary.textContent.trim().replace(/\s+/g, "-").toLowerCase()}`); - let n = Q.getItem(this.key); - (this.el.open = n ? n === "true" : this.el.open), - this.el.addEventListener("toggle", () => this.update()); - let r = this.summary.querySelector("a"); - r && - r.addEventListener("click", () => { - location.assign(r.href); - }), - this.update(); - } - update() { - (this.icon.style.transform = `rotate(${this.el.open ? 0 : -90}deg)`), - Q.setItem(this.key, this.el.open.toString()); - } - }; - function Ee(t) { - let e = Q.getItem("tsd-theme") || "os"; - (t.value = e), - xe(e), - t.addEventListener("change", () => { - Q.setItem("tsd-theme", t.value), xe(t.value); - }); - } - function xe(t) { - document.documentElement.dataset.theme = t; - } - var K; - function we() { - let t = document.getElementById("tsd-nav-script"); - t && (t.addEventListener("load", Le), Le()); - } - async function Le() { - let t = document.getElementById("tsd-nav-container"); - if (!t || !window.navigationData) return; - let n = await (await fetch(window.navigationData)).arrayBuffer(), - r = new Blob([n]).stream().pipeThrough(new DecompressionStream("gzip")), - i = await new Response(r).json(); - (K = t.dataset.base), K.endsWith("/") || (K += "/"), (t.innerHTML = ""); - for (let s of i) Se(s, t, []); - window.app.createComponents(t), - window.app.showPage(), - window.app.ensureActivePageVisible(); - } - function Se(t, e, n) { - let r = e.appendChild(document.createElement("li")); - if (t.children) { - let i = [...n, t.text], - s = r.appendChild(document.createElement("details")); - s.className = t.class ? `${t.class} tsd-accordion` : "tsd-accordion"; - let o = s.appendChild(document.createElement("summary")); - (o.className = "tsd-accordion-summary"), - (o.dataset.key = i.join("$")), - (o.innerHTML = - ''), - be(t, o); - let a = s.appendChild(document.createElement("div")); - a.className = "tsd-accordion-details"; - let l = a.appendChild(document.createElement("ul")); - l.className = "tsd-nested-navigation"; - for (let u of t.children) Se(u, l, i); - } else be(t, r, t.class); - } - function be(t, e, n) { - if (t.path) { - let r = e.appendChild(document.createElement("a")); - (r.href = K + t.path), - n && (r.className = n), - location.pathname === r.pathname && - !r.href.includes("#") && - r.classList.add("current"), - t.kind && - (r.innerHTML = ``), - (r.appendChild(document.createElement("span")).textContent = t.text); - } else e.appendChild(document.createElement("span")).textContent = t.text; - } - G(X, "a[data-toggle]"); - G(Z, ".tsd-accordion"); - G(Y, ".tsd-filter-item input[type=checkbox]"); - var Te = document.getElementById("tsd-theme"); - Te && Ee(Te); - var $e = new U(); - Object.defineProperty(window, "app", { value: $e }); - fe(); - we(); -})(); +window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings."}; +"use strict";(()=>{var Pe=Object.create;var ie=Object.defineProperty;var Oe=Object.getOwnPropertyDescriptor;var _e=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,Me=Object.prototype.hasOwnProperty;var Fe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of _e(e))!Me.call(t,i)&&i!==n&&ie(t,i,{get:()=>e[i],enumerable:!(r=Oe(e,i))||r.enumerable});return t};var Ae=(t,e,n)=>(n=t!=null?Pe(Re(t)):{},De(e||!t||!t.__esModule?ie(n,"default",{value:t,enumerable:!0}):n,t));var ue=Fe((ae,le)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,u],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(n+=r[u+1]*i[d+1],u+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}s.str.length==1&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),m=s.str.charAt(1),p;m in s.node.edges?p=s.node.edges[m]:(p=new t.TokenSet,s.node.edges[m]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof ae=="object"?le.exports=n():e.lunr=n()}(this,function(){return t})})()});var se=[];function G(t,e){se.push({selector:e,constructor:t})}var U=class{constructor(){this.alwaysVisibleMember=null;this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){se.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!Ve(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function Ve(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var oe=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var pe=Ae(ue());async function ce(t,e){if(!window.searchData)return;let n=await fetch(window.searchData),r=new Blob([await n.arrayBuffer()]).stream().pipeThrough(new DecompressionStream("gzip")),i=await new Response(r).json();t.data=i,t.index=pe.Index.load(i.index),e.classList.remove("loading"),e.classList.add("ready")}function fe(){let t=document.getElementById("tsd-search");if(!t)return;let e={base:t.dataset.base+"/"},n=document.getElementById("tsd-search-script");t.classList.add("loading"),n&&(n.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),n.addEventListener("load",()=>{ce(e,t)}),ce(e,t));let r=document.querySelector("#tsd-search input"),i=document.querySelector("#tsd-search .results");if(!r||!i)throw new Error("The input field or the result list wrapper was not found");i.addEventListener("mouseup",()=>{te(t)}),r.addEventListener("focus",()=>t.classList.add("has-focus")),He(t,i,r,e)}function He(t,e,n,r){n.addEventListener("input",oe(()=>{Ne(t,e,n,r)},200)),n.addEventListener("keydown",i=>{i.key=="Enter"?Be(e,t):i.key=="ArrowUp"?(de(e,n,-1),i.preventDefault()):i.key==="ArrowDown"&&(de(e,n,1),i.preventDefault())}),document.body.addEventListener("keypress",i=>{i.altKey||i.ctrlKey||i.metaKey||!n.matches(":focus")&&i.key==="/"&&(i.preventDefault(),n.focus())}),document.body.addEventListener("keyup",i=>{t.classList.contains("has-focus")&&(i.key==="Escape"||!e.matches(":focus-within")&&!n.matches(":focus"))&&(n.blur(),te(t))})}function te(t){t.classList.remove("has-focus")}function Ne(t,e,n,r){if(!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s;if(i){let o=i.split(" ").map(a=>a.length?`*${a}*`:"").join(" ");s=r.index.search(o)}else s=[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o`,d=he(l.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(d+=` (score: ${s[o].score.toFixed(2)})`),l.parent&&(d=` + ${he(l.parent,i)}.${d}`);let m=document.createElement("li");m.classList.value=l.classes??"";let p=document.createElement("a");p.href=r.base+l.url,p.innerHTML=u+d,m.append(p),p.addEventListener("focus",()=>{e.querySelector(".current")?.classList.remove("current"),m.classList.add("current")}),e.appendChild(m)}}function de(t,e,n){let r=t.querySelector(".current");if(!r)r=t.querySelector(n==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let i=r;if(n===1)do i=i.nextElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);else do i=i.previousElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);i?(r.classList.remove("current"),i.classList.add("current")):n===-1&&(r.classList.remove("current"),e.focus())}}function Be(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),te(e)}}function he(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ee(t.substring(s,o)),`${ee(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ee(t.substring(s))),i.join("")}var je={"&":"&","<":"<",">":">","'":"'",'"':"""};function ee(t){return t.replace(/[&<>"'"]/g,e=>je[e])}var I=class{constructor(e){this.el=e.el,this.app=e.app}};var F="mousedown",ye="mousemove",N="mouseup",J={x:0,y:0},me=!1,ne=!1,qe=!1,D=!1,ve=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(ve?"is-mobile":"not-mobile");ve&&"ontouchstart"in document.documentElement&&(qe=!0,F="touchstart",ye="touchmove",N="touchend");document.addEventListener(F,t=>{ne=!0,D=!1;let e=F=="touchstart"?t.targetTouches[0]:t;J.y=e.pageY||0,J.x=e.pageX||0});document.addEventListener(ye,t=>{if(ne&&!D){let e=F=="touchstart"?t.targetTouches[0]:t,n=J.x-(e.pageX||0),r=J.y-(e.pageY||0);D=Math.sqrt(n*n+r*r)>10}});document.addEventListener(N,()=>{ne=!1});document.addEventListener("click",t=>{me&&(t.preventDefault(),t.stopImmediatePropagation(),me=!1)});var X=class extends I{constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener(N,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(F,n=>this.onDocumentPointerDown(n)),document.addEventListener(N,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){D||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!D&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var re;try{re=localStorage}catch{re={getItem(){return null},setItem(){}}}var Q=re;var ge=document.head.appendChild(document.createElement("style"));ge.dataset.for="filters";var Y=class extends I{constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),ge.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } +`,this.app.updateIndexVisibility()}fromLocalStorage(){let e=Q.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){Q.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),this.app.updateIndexVisibility()}};var Z=class extends I{constructor(e){super(e),this.summary=this.el.querySelector(".tsd-accordion-summary"),this.icon=this.summary.querySelector("svg"),this.key=`tsd-accordion-${this.summary.dataset.key??this.summary.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`;let n=Q.getItem(this.key);this.el.open=n?n==="true":this.el.open,this.el.addEventListener("toggle",()=>this.update());let r=this.summary.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)}),this.update()}update(){this.icon.style.transform=`rotate(${this.el.open?0:-90}deg)`,Q.setItem(this.key,this.el.open.toString())}};function Ee(t){let e=Q.getItem("tsd-theme")||"os";t.value=e,xe(e),t.addEventListener("change",()=>{Q.setItem("tsd-theme",t.value),xe(t.value)})}function xe(t){document.documentElement.dataset.theme=t}var K;function we(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Le),Le())}async function Le(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let n=await(await fetch(window.navigationData)).arrayBuffer(),r=new Blob([n]).stream().pipeThrough(new DecompressionStream("gzip")),i=await new Response(r).json();K=t.dataset.base,K.endsWith("/")||(K+="/"),t.innerHTML="";for(let s of i)Se(s,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Se(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',be(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let l=a.appendChild(document.createElement("ul"));l.className="tsd-nested-navigation";for(let u of t.children)Se(u,l,i)}else be(t,r,t.class)}function be(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));r.href=K+t.path,n&&(r.className=n),location.pathname===r.pathname&&!r.href.includes("#")&&r.classList.add("current"),t.kind&&(r.innerHTML=``),r.appendChild(document.createElement("span")).textContent=t.text}else e.appendChild(document.createElement("span")).textContent=t.text}G(X,"a[data-toggle]");G(Z,".tsd-accordion");G(Y,".tsd-filter-item input[type=checkbox]");var Te=document.getElementById("tsd-theme");Te&&Ee(Te);var $e=new U;Object.defineProperty(window,"app",{value:$e});fe();we();})(); /*! Bundled license information: lunr/lunr.js: diff --git a/docs/reference/assets/navigation.js b/docs/reference/assets/navigation.js index bb4e05f..374fdac 100644 --- a/docs/reference/assets/navigation.js +++ b/docs/reference/assets/navigation.js @@ -1,2 +1 @@ -window.navigationData = - "data:application/octet-stream;base64,H4sIAAAAAAAAE4WTS2sCMRRG/0vW0qn27U6koItCH9AuSheZmasGYxLvvalK6X8vasd5xcz6fOcQLuTzRzBsWQzFyKlHRIuiJ5zkhRiKTEsioKQgFwteadETS2VyMewP7n97J/tdapVLVtaciTQGsdaLBw9vLNlT2eGdA0oqqF4YXD7c9W8GlcorrD0QP6k817CRCM1Wa9BdJGcNwUSaXAO2ezXcVWscZGpmtlkMTLqqH5BOrF0Wb2kWG7irtt6fu2x8S1Qy1UDJAdTtq6qIIDWrVdAtWEQntijnQfsfReTMmpmal+7Mm2x/Q0qOpK7eXlfUOfD4rH2CkYCTSDBybpqHCiWNJNCbkIveRCRiBLkKeUcSU31KGaoUgnYBI4GN4kXol5WV+qIj9Yx2uztXOcBW4OsPsjwAYcYEAAA="; +window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE43U0U7CMBQG4HfpNXGCgsqdIRhIJCJGvTBeHLYzaCjtbM8EYnx3Iwrbuq7ldv9/vmynWd++GOGWWJ/dZnyotdKsxTKgJeuzWIAxaKJDcraktWAttuIyYf125/q7dZx+AcETIK5kA2IVfNZArTOBhMljjjk+EVBuCpBLQp1CjCZyFatwp9srwXcgBoKjJKd2TH3EWE61Wmg0JvRyzqaf3hfDrl3zofua56NLuY+ZIQjia59UrfiwJ1IaFj6r0vBTGmHN5cKLVTo+boKkeVzaO+0yNNH/Y2vy/Oaq3e3Yu7ZP7k9oPq2aMsOPHA3dq4WNFMmJxoQnicANaGygikJYNJmSBkcgE4G67lXiE7RckAPJhX0+9dlcPmS/90htyUUSMp61aDCKJGRYF9pYpsrGHJWQ+orzkVKrwz5t0YpDWgqiED5Bc5gLNFEKojp5UR6KNQKh465McxnvVxNZlSrWuyxh3Phv88J0N710UDwNykAbHMokU1zSOHFRVsWDbTgtXf9dYVUbAWqq1XbXpOzDGvD+A5P5Ic/UBwAA" \ No newline at end of file diff --git a/docs/reference/assets/search.js b/docs/reference/assets/search.js index 285a820..51702cb 100644 --- a/docs/reference/assets/search.js +++ b/docs/reference/assets/search.js @@ -1,2 +1 @@ -window.searchData = - "data:application/octet-stream;base64,H4sIAAAAAAAAE62ZXW+jOBSG/4vn1k1zDiEhuRutdjW9WGk/pNkLFFUkuA0qAcZAO6Oo/31lIGDjQ2umc9UK+33PwX58jJ0Lk/lLyXbhhT0lWcx26xVnWXQWbMeOefaQPDLOapmyHXuos2OV5Fl52zYsTtU5ZZwd06gsRcl2jL1ywuZRVL9NOfVtb5p52Jt9q0UteqPnSCbRIRXlbfPcNSNZZ0Quss5cDcr6UB5lchCETd/mavaSVKc/kzhOxUskKUezwxzbv2T+/ceEY9P2phkutxvwh7H/R3yrRVkRuVY/ClHeWu1vp+r73rr3vr9XHk6Gn/q+mi9nRSRFVpFpkiRJEaVVcqZguja9mT9g0Ht9LpLfpcxl79Upbq8Nbzr5gPqaKytZH6v3zD6ZPemh6PPS0l6ixnEVVXX5TqC+08/FOOTxj3ciqC43OCuCNvhfozSJIwU2PQej9l8yFZSn04yMk9VW2xphNQzbQyLSuOljzw8Z3RTMjr5cBXrF/mNueEszNwMXMMnIBJ9VGd8k5U2SnYRMKhH/RAYktmT8rudHo9vVtizyrBRfoixOhbRKo9H68Upr271fZ80EySpbVrmMHqki27U4b7mVFNGZ2m+bhlkb2d/qY+FfE7F2HLSWWY6jab3LHvKRM9Hjo5M2ZfnexFHJTqyCc/k4L+iiVTgHXnS5TsRP8+PM+K3iV8WfP+qLuUNvZzCG6z9x+JLnT9f1Nspn1Oq6nopIluJzUdzFxJoaGim7PWdJFovvbHdhz0KWSZ6xHcOFt9gy3m5c6kTRRuLsmJ/P6u05i/Nj3fy777p9FWqzVJ3b3rdLxsMl95aLLez3PLxqm+fNg6vF8KTRAeMhEDqwdGDokPEQCR1aOjR0HuOhR+g8S+cZuhXj4YrQrSzdytD5jIc+ofMtnW/o1oyHa0K3tnRrQ7dhPNwQuo2l2xi6gPEw4IiLlbcxhIElDAzhlvFwSwTcWrqtOfGKA6CQAZsZGEHTUAMc/UWwRFNMgGOSAwoIQFJs0wMmPqCoAI8U2wiByRAoNICiCGyMwOQI/OlXtlkCEyZQjACFIdg8gQkUKE6AQhFspsCECoLpoba5AhMs2E4PtQ0XmHRhQxe1ENCmC026EKbWAtpw4agsNXAFVFyiMplsYcMWtZbQRgtNtFDRgtRqQhstNNFCBQtSRRhtstAkC9eTY2WThSZZqGBBsojbZKFJFipWkCrkaIOFJlioUEFqGaLNFZpceQoVpFaSZ3PlmVx5ChWkVpJnc9U9anbsZyErEd+1O3cY9t+UF3bfbef9CfDCAra7vHKG0P1dq7+vw2bePO33c9Wm4kZFItoz+GAKy8EVlk427YlKs/A0C68VwdbJ6nqHOZhp6bhlYxzrtaRAS6obJvCdHJtvH9EdlDVHX3N0G/BHUdlvqCfm6jKV0lrz2jiZNR/ggwNqc4eBk0NzhNAcUHNwy6H5fo2KIokNI+1lPLeR6a6bNQ8tF3eH6xWF5qNhiCsnp+HCcrDZDi5u60G2l6Nn7Q53cNsMbm7jLLtDx+l6R6GRo5mh20JrruUHBw0cz0lvjzJo0wXYrVE3CPuLC81Nq4/oNvvXWwvNRJsydHyv4deGwWc12LjhMy72qDmgGzzP/enVrvKaG7glNHJLmjsTLT+tjKFbXX0Rh1OeP8n+oKy5abXVc8NR/UhCrxPNyzGxpDoV7W8xg4tWjRyK/Z6zIilEmmSC7cL96+v/kPat4robAAA="; +window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAE72cWY/bRhKA/wvnVZanT1LztggSxMAGm83uZh8EY8CRODNEJFIhKTuG4f++aB5iFatabI6MfZI97Dq6+qvqg8fXqCo/19HD9mv0R17sowclV1GRHrPoIXpOD9EqOleH6CH6lFZ5+nTI6vfP6WH92hzdpd0hreusjh6i6NtqUGD1RcGuytIm+yk9/HDIs6K5KHs+F7smL4v6/aTFVcXS2ItmqjMvmqx6TndZ/T5MnbiXo6d/nrNzNq/rbmgHNK6iU1o5T6BXHjNVlh6a/BhiCTR9o7G6Kav0JcTW2PLNpqosPebFS5CxsW24OXmvkzGM5yIkgm2rN5qoz0/1rsqfgsIH2t4UwODoLTJkjVFj5jw+Nl9OYVA4S+tL+xmD694zthB8zpvXX/L9/pB9TquMqQO4QWh9cVK/VuVfXzwa22vXa8r9JhZmLHm/ZX+es7phfHVRqN+T69ddvRb5awrvZqJO3eTL5D9dufIXSnB5Jko4M455kLq7S1O+F9A7n7Embc51mLGh6Q3GHMH/WmISCbzd8FBA/l0usc5IvdmFKqvPh7BRvTR9s7FdWuyyQ5CxS9NFxkAK/NbPo/4swC3CE2FXFkW2C1V5Nzb35TRyFEwUcrT5t1P+Y1WV1cVor+v9cOGq+0ZI6H3dVOddM6fsDrfknb/45Z3fENa8oZkMnrPxVO6/zFhwTd7JRRZA8H9PD/k+ddMLPwaT699lKDidQSMydRZAbKXQY9ie8+ywb9vQ8WGtY4HF1mEKvWTNT0vNE5mlHoSAyVpm+Gzq/bu8fpcXr1mVN9n+DR6w2LL2+5a3WqdrnvpUFnX2c1rsD1lFFijo6u3rHapufrWDHeQL/b+6TYy/zqMG4VvD8+lQpvswhXeXxnxPsI/Ll+hei/0y/Uptw6K9n54uN1Va1M9ldfxQnM6BsbwjQv+3EGDLczsWrGDitQ+tfst6DS7UZMFy2rvz41TO7f+mnvqzvl05TVabXYqCK4v2TpOK86F4LieamRa31hOfyrmawjnryYZj/bLM6LqTCDY8EOuxfyh3C+13Et/L/vKor5eGnnowheu/2dPPZfnHMBVM/JlcDT0/OKVVnf1Y7E9lXjQfxho/niJMWoQqzmsuvUa16Hq40h/K4+mQNdl+TjvXcOE5CNoOXubu8+F6aQtbAQxaAiZ+54WHy33apLO6132rawZm+K+6wxbAh9cYbLrMIhmAc/GPUzucU6uXCzcPBNY0OxijR55A5Wi1cM3Ium36TgQYmxmcY9a8lmRkeKOXtsttTofnP9WBH57xwq3DM9E0NzzAI99W5/y0L49pXgQZWsPmcyZnBunPc1Z9CbM6NL3R4iltXsMM9i2X2/McHf+9nK4Wxgvf6bB40BR4Suw88mZPXcNbRNfMrMfGc+bmFjTZJ3D6d9Xm0PRGi3V5rnaB3by0vdGmO8urm/R4CjMLmy+3PIXxl6yp8t20OPV/vRVDqGaOwcER75TxnFVZscse0W1Rr501EbhqlgkU2NZ9KNrlEbeYAtsw2ir87MB/+8Kjde4MlHH52h3tx1NZ565+LXGBSN7qStUvyx+dCwscmcjd7kabP485e5jjdwJIhR29XXUJA/hrVb5UWc1uF5BzTMPvg6FP8TyJnO9vJsDrRiAEC5w5lC9LY9GLfJdIXIfwShzewOF1xyCKV7eWwMPFO8tAEL165zhkHX8jhn4ngihc4ooPQr8LVxlcYvo4WRsEWR+FvscwXMuBa4OwOAV4tz6uorzYZ39FD1+jT1lVu2nuIZJrtd5Eq+6GmHskrvN3Fe3K47E79t2Xu3P7z499s98zdxPONe5av7+PVtv7lRZrYdXHj6vtINxeaP8w6Bj/0gqKaLUVnKAgggIJymi1lZygJIISCapotVWcoCKCCgnqaLXVnKAmghoJmmi1NZygIYIGCdpotbWcoCWCFgnG0Wobc4IxEYyRYBKttgknmBDBBAluotV2s1J2fW8MEtwQwQ0GwPEg7lfyfh1vNEaAwiMm9LT48PwwAGGChONCsAwJCpHAFAnHhmA5EhQkgUkS2t9nCpPANAnHiGBBFBQogYkSjhPBwigoVAJTJRwrwq6kXeMxFhQrgbkSjhbBIikoWgKzJRwxgsVSULwE5ku2fLFoSsqXxHxJR4xkC5ukfMlJhWpLFAunZIoU5ks6YiRf4ChfEvMlHTGShVNSviTmSzpipGYDRvmSmC9pPYhISpfEdElHjDSsXcqXxHxJR4xka6WkfEnMl9z4e0z5kpgv5YiRLNmK8qUwX6rliyVbUb4U5ktJT6wVpUtN5kDljbVipkFMl2rp2rBOU7oUpksZb+lTlC6F6VKOGMWmo6J8KcyXcsQoNh0V5UthvlTid5vypTBfyhGj2FxWlC+F+dL++VFTvjTmSztiFFsINOVLY7609M3pmgKmMWC6XWXxqyUKmJ4stBwyip2kNLPWwoBpP2CaAqYxYLoFjC0jmgKmMWC6BYytBJoCpjFg2iGj2EqgKWAaA6ZbwNiM1BQwjQEzDhnNJpWhgBkMmHHIaDapDAXMYMCMY0azeWEoYQYTZpR3XjeUMIMJM/4FmKGEmcly3jGj2aQyzIoeE2YcM5pNDEMJM5gw45jR/FaCEmYwYcZfwgwlzGDCjGNGs4lhKGEGE2ZbwtjEsJQwiwmzLWFsYlhKmMWEWents6WEWUyYdcxoNqssJcxiwqxjxrBZZSlhFhNm2x0jm1WWEmYnm0bHjGGzyjL7RkyYjf0Bo4RZTJh1zBg2MSwlzGLCrGPGsIlhKWEWExY7ZgybGDElLMaExY4Zw7IdU8JiTFjsmDExV4ZiSliMCYuVN9oxJSzGhMUtYWxixJSwGBMWt4SxbMeUsBgTFvsW+THlK54cTDhiLJsWMXM2gfmKHTFWrJRc2w2WpXjFGK/YAWMlJ0vpijFdiePFslwnlK4E05UIT7ASylaC2Uqkr78JRSvBaCUOFsvuaRKKVoLRSrQvWAklK8FkJY4Vy6ZiQslKMFmJj6yEkpVgspLYGywKVjI59Ur8wWIOvjBZycZbARKKVoLR2tz7Ir2hZPV/ao9xP2VVk+0/dMe52+3ljvXX6LE/4xX3w8nz10jcRw9fv60iobtfZfrfpPvV/XXd/9307Ux/3cr+N+5+Y+V+v41nxO1fL8fE7przPD3lWffKyOiWVKNbUgep6V4AACoMUNF7pMI8Gt6rAspAmKQIUzKcrre3jIcbNaNKC/xLTKDK/i0p4JgEjgX2Dr4gAzRpoKkfYbkJ09i+qP6cHnb9I+CAL4BXkK7ukcRRgQYkmLAwta/mjxrA0N2HytOOwDiHaXG3QLL+PRygJx4VqTB/XrLGpywBysLiO31GZFRmgLI4LOfy4tTfRPVBDkYvCett/6AkIMACAsKAzIvWH49TZgM6GoZUXs+lswaAmDBCcl/UNMgaEzas/bNgoJMgnW1Y1Non40cNCpCq40ANL3j4gROJ6oSSJEjV5YE60CUAkw3V0j3VCmILemXDiLzc3gWuAC1xP+0lYVFu338AUQZ0axukoX3uPuufu89x70CxM2G9657xBF0D8Nmw7OifSwU6gBs2rJT0Hw8BgQGlLVwDeDQLYAjcicNIbpUx0xkAWgQHh8txBTTpsA6OXz0BAw60LFLCzHBg4GVYBYMPHYBwg1IYb/rk6NeKmzAoe8UTuEFnTViq9HoOJco5Axy0ixQdwbcvABWAVRE6lvCZGRA7MAhxv65OhgIThu6g+nV4ZRUMMZj5VCgv3csuoLdAiey3BCaw12eUl7CIhsqXw5sFgAuwkjZhiobnmAETQEkcBilNagGKuej3HNJ2v6ofxbj/fyL63zAAL58hAi6Dbi/RQVNfgQKpQv3p3rUclQAs+qTXYVUEfPQIJAJI0WVamO6B/NRh66lOGzPCgFkRtgYB74oAZkG0bFjyDB+MAc4AAkRglMbPMY16wMI/uEudlqZkQgTUibA1EXi+H+ANEIjDRu3ywjLZRChQ8lWgU5OjEgX6pcPiNLzxDpSA+q7CatW5OnBFD3hjw6Lz6fJuKT1vAbGWYX2baMvbN5pBRwGbOmyq+Zw9vZblH9XlNVagDaSLDhs+940rz3QNDyXCQueUnbqPaQE9gCkRUOo+rqJTfsoOeZFFD9uP3779D9KIs1xgUAAA"; \ No newline at end of file diff --git a/docs/reference/classes/ApiError.html b/docs/reference/classes/ApiError.html index 1c13bbb..8ea3d94 100644 --- a/docs/reference/classes/ApiError.html +++ b/docs/reference/classes/ApiError.html @@ -1,4 +1,4 @@ -ApiError | @fal-ai/serverless-client - v0.14.2

Type Parameters

  • Body

Hierarchy (view full)

Constructors

constructor +ApiError | @fal-ai/client - v1.0.0

Class ApiError<Body>

Type Parameters

  • Body

Hierarchy (view full)

Constructors

Properties

Constructors

Properties

body: Body
status: number
+

Constructors

Properties

body: Body
status: number
diff --git a/docs/reference/classes/ValidationError.html b/docs/reference/classes/ValidationError.html index a1d0bfd..6ab0728 100644 --- a/docs/reference/classes/ValidationError.html +++ b/docs/reference/classes/ValidationError.html @@ -1,6 +1,6 @@ -ValidationError | @fal-ai/serverless-client - v0.14.2

Hierarchy (view full)

  • ApiError<ValidationErrorBody>
    • ValidationError

Constructors

constructor +ValidationError | @fal-ai/client - v1.0.0

Class ValidationError

Hierarchy (view full)

  • ApiError<ValidationErrorBody>
    • ValidationError

Constructors

Properties

Accessors

Methods

Constructors

Properties

body: ValidationErrorBody
status: number

Accessors

Methods

+

Constructors

Properties

body: ValidationErrorBody
status: number

Accessors

Methods

diff --git a/docs/reference/functions/config.html b/docs/reference/functions/config.html deleted file mode 100644 index 60b5af6..0000000 --- a/docs/reference/functions/config.html +++ /dev/null @@ -1,3 +0,0 @@ -config | @fal-ai/serverless-client - v0.14.2
  • Configures the fal serverless client.

    -

    Parameters

    • config: Config

      the new configuration.

      -

    Returns void

diff --git a/docs/reference/functions/createFalClient.html b/docs/reference/functions/createFalClient.html new file mode 100644 index 0000000..0bc5e2a --- /dev/null +++ b/docs/reference/functions/createFalClient.html @@ -0,0 +1,4 @@ +createFalClient | @fal-ai/client - v1.0.0

Function createFalClient

  • Creates a new reference of the FalClient.

    +

    Parameters

    • userConfig: Config = {}

      Optional configuration to override the default settings.

      +

    Returns FalClient

    a new instance of the FalClient.

    +
diff --git a/docs/reference/functions/getConfig.html b/docs/reference/functions/getConfig.html deleted file mode 100644 index 11f6992..0000000 --- a/docs/reference/functions/getConfig.html +++ /dev/null @@ -1,3 +0,0 @@ -getConfig | @fal-ai/serverless-client - v0.14.2
  • Get the current fal serverless client configuration.

    -

    Returns RequiredConfig

    the current client configuration.

    -
diff --git a/docs/reference/functions/isCompletedQueueStatus.html b/docs/reference/functions/isCompletedQueueStatus.html new file mode 100644 index 0000000..628ff7a --- /dev/null +++ b/docs/reference/functions/isCompletedQueueStatus.html @@ -0,0 +1 @@ +isCompletedQueueStatus | @fal-ai/client - v1.0.0

Function isCompletedQueueStatus

diff --git a/docs/reference/functions/isQueueStatus.html b/docs/reference/functions/isQueueStatus.html new file mode 100644 index 0000000..7c750d7 --- /dev/null +++ b/docs/reference/functions/isQueueStatus.html @@ -0,0 +1 @@ +isQueueStatus | @fal-ai/client - v1.0.0

Function isQueueStatus

diff --git a/docs/reference/functions/parseAppId.html b/docs/reference/functions/parseAppId.html deleted file mode 100644 index d1b7a51..0000000 --- a/docs/reference/functions/parseAppId.html +++ /dev/null @@ -1 +0,0 @@ -parseAppId | @fal-ai/serverless-client - v0.14.2
  • Parameters

    • id: string

    Returns AppId

diff --git a/docs/reference/functions/parseEndpointId.html b/docs/reference/functions/parseEndpointId.html new file mode 100644 index 0000000..dc3a82f --- /dev/null +++ b/docs/reference/functions/parseEndpointId.html @@ -0,0 +1 @@ +parseEndpointId | @fal-ai/client - v1.0.0

Function parseEndpointId

  • Parameters

    • id: string

    Returns EndpointId

diff --git a/docs/reference/functions/run.html b/docs/reference/functions/run.html deleted file mode 100644 index 033d81a..0000000 --- a/docs/reference/functions/run.html +++ /dev/null @@ -1,4 +0,0 @@ -run | @fal-ai/serverless-client - v0.14.2
  • Runs a fal serverless function identified by its id.

    -

    Type Parameters

    • Input
    • Output

    Parameters

    • id: string

      the registered function revision id or alias.

      -
    • options: RunOptions<Input> = {}

    Returns Promise<Output>

    the remote function output

    -
diff --git a/docs/reference/functions/stream.html b/docs/reference/functions/stream.html deleted file mode 100644 index f5620cc..0000000 --- a/docs/reference/functions/stream.html +++ /dev/null @@ -1,7 +0,0 @@ -stream | @fal-ai/serverless-client - v0.14.2
  • Calls a fal app that supports streaming and provides a streaming-capable -object as a result, that can be used to get partial results through either -AsyncIterator or through an event listener.

    -

    Type Parameters

    • Input = Record<string, any>
    • Output = any

    Parameters

    • endpointId: string

      the endpoint id, e.g. fal-ai/llavav15-13b.

      -
    • options: StreamOptions<Input>

      the request options, including the input payload.

      -

    Returns Promise<FalStream<Input, Output>>

    the FalStream instance.

    -
diff --git a/docs/reference/functions/subscribe.html b/docs/reference/functions/subscribe.html deleted file mode 100644 index 1f4f2ef..0000000 --- a/docs/reference/functions/subscribe.html +++ /dev/null @@ -1,5 +0,0 @@ -subscribe | @fal-ai/serverless-client - v0.14.2
  • Subscribes to updates for a specific request in the queue.

    -

    Type Parameters

    • Input
    • Output

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      -
    • options: RunOptions<Input> & QueueSubscribeOptions = {}

      Options to configure how the request is run and how updates are received.

      -

    Returns Promise<Output>

    A promise that resolves to the result of the request once it's completed.

    -
diff --git a/docs/reference/functions/withMiddleware.html b/docs/reference/functions/withMiddleware.html index 97c1617..c0ba81c 100644 --- a/docs/reference/functions/withMiddleware.html +++ b/docs/reference/functions/withMiddleware.html @@ -1,4 +1,4 @@ -withMiddleware | @fal-ai/serverless-client - v0.14.2
diff --git a/docs/reference/functions/withProxy.html b/docs/reference/functions/withProxy.html index 2323c1c..fd53354 100644 --- a/docs/reference/functions/withProxy.html +++ b/docs/reference/functions/withProxy.html @@ -1 +1 @@ -withProxy | @fal-ai/serverless-client - v0.14.2
+withProxy | @fal-ai/client - v1.0.0

Function withProxy

diff --git a/docs/reference/hierarchy.html b/docs/reference/hierarchy.html index b918b08..9976d41 100644 --- a/docs/reference/hierarchy.html +++ b/docs/reference/hierarchy.html @@ -1 +1 @@ -@fal-ai/serverless-client - v0.14.2

@fal-ai/serverless-client - v0.14.2

Class Hierarchy

+@fal-ai/client - v1.0.0

@fal-ai/client - v1.0.0

Class Hierarchy

diff --git a/docs/reference/index.html b/docs/reference/index.html index 56eabef..09b9ec8 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -1,19 +1,28 @@ -@fal-ai/serverless-client - v0.14.2

@fal-ai/serverless-client - v0.14.2

Index

Classes

ApiError +@fal-ai/client - v1.0.0
+
diff --git a/docs/reference/interfaces/CompletedQueueStatus.html b/docs/reference/interfaces/CompletedQueueStatus.html new file mode 100644 index 0000000..184fef2 --- /dev/null +++ b/docs/reference/interfaces/CompletedQueueStatus.html @@ -0,0 +1,6 @@ +CompletedQueueStatus | @fal-ai/client - v1.0.0

Interface CompletedQueueStatus

interface CompletedQueueStatus {
    logs: RequestLog[];
    metrics?: Metrics;
    request_id: string;
    response_url: string;
    status: "COMPLETED";
}

Hierarchy

  • BaseQueueStatus
    • CompletedQueueStatus

Properties

logs: RequestLog[]
metrics?: Metrics
request_id: string
response_url: string
status: "COMPLETED"
diff --git a/docs/reference/interfaces/FalClient.html b/docs/reference/interfaces/FalClient.html new file mode 100644 index 0000000..bc916df --- /dev/null +++ b/docs/reference/interfaces/FalClient.html @@ -0,0 +1,40 @@ +FalClient | @fal-ai/client - v1.0.0

Interface FalClient

The main client type, it provides access to simple API model usage, +as well as access to the queue and storage APIs.

+

createFalClient

+
interface FalClient {
    queue: QueueClient;
    realtime: RealtimeClient;
    storage: StorageClient;
    stream: (<Output, Input>(endpointId: string, options: StreamOptions<Input>) => Promise<FalStream<Input, Output>>);
    streaming: StreamingClient;
    run<Output, Input>(id: string, options: RunOptions<Input>): Promise<Result<Output>>;
    subscribe<Output, Input>(endpointId: string, options: RunOptions<Input> & QueueSubscribeOptions): Promise<Result<Output>>;
}

Properties

The queue client to interact with the queue API.

+
realtime: RealtimeClient

The realtime client to interact with the realtime API +and receive updates in real-time.

+
    +
  • #RealtimeClient
  • +
  • #RealtimeClient.connect
  • +
+
storage: StorageClient

The storage client to interact with the storage API.

+
stream: (<Output, Input>(endpointId: string, options: StreamOptions<Input>) => Promise<FalStream<Input, Output>>)

Calls a fal app that supports streaming and provides a streaming-capable +object as a result, that can be used to get partial results through either +AsyncIterator or through an event listener.

+

Type declaration

    • <Output, Input>(endpointId, options): Promise<FalStream<Input, Output>>
    • Calls a fal app that supports streaming and provides a streaming-capable +object as a result, that can be used to get partial results through either +AsyncIterator or through an event listener.

      +

      Type Parameters

      • Output = any
      • Input = Record<string, any>

      Parameters

      • endpointId: string

        the endpoint id, e.g. fal-ai/llavav15-13b.

        +
      • options: StreamOptions<Input>

        the request options, including the input payload.

        +

      Returns Promise<FalStream<Input, Output>>

      the FalStream instance.

      +

the endpoint id, e.g. fal-ai/llavav15-13b.

+

the request options, including the input payload.

+

the FalStream instance.

+
streaming: StreamingClient

The streaming client to interact with the streaming API.

+

#stream

+

Methods

  • Subscribes to updates for a specific request in the queue.

    +

    Type Parameters

    • Output = any
    • Input = Record<string, any>

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      +
    • options: RunOptions<Input> & QueueSubscribeOptions

      Options to configure how the request is run and how updates are received.

      +

    Returns Promise<Result<Output>>

    A promise that resolves to the result of the request once it's completed.

    +
diff --git a/docs/reference/interfaces/InProgressQueueStatus.html b/docs/reference/interfaces/InProgressQueueStatus.html new file mode 100644 index 0000000..83a1375 --- /dev/null +++ b/docs/reference/interfaces/InProgressQueueStatus.html @@ -0,0 +1,5 @@ +InProgressQueueStatus | @fal-ai/client - v1.0.0

Interface InProgressQueueStatus

interface InProgressQueueStatus {
    logs: RequestLog[];
    request_id: string;
    response_url: string;
    status: "IN_PROGRESS";
}

Hierarchy

  • BaseQueueStatus
    • InProgressQueueStatus

Properties

logs: RequestLog[]
request_id: string
response_url: string
status: "IN_PROGRESS"
diff --git a/docs/reference/interfaces/InQueueQueueStatus.html b/docs/reference/interfaces/InQueueQueueStatus.html new file mode 100644 index 0000000..c5d07ee --- /dev/null +++ b/docs/reference/interfaces/InQueueQueueStatus.html @@ -0,0 +1,5 @@ +InQueueQueueStatus | @fal-ai/client - v1.0.0

Interface InQueueQueueStatus

interface InQueueQueueStatus {
    queue_position: number;
    request_id: string;
    response_url: string;
    status: "IN_QUEUE";
}

Hierarchy

  • BaseQueueStatus
    • InQueueQueueStatus

Properties

queue_position: number
request_id: string
response_url: string
status: "IN_QUEUE"
diff --git a/docs/reference/interfaces/QueueClient.html b/docs/reference/interfaces/QueueClient.html new file mode 100644 index 0000000..4a2469b --- /dev/null +++ b/docs/reference/interfaces/QueueClient.html @@ -0,0 +1,36 @@ +QueueClient | @fal-ai/client - v1.0.0

Interface QueueClient

Represents a request queue with methods for submitting requests, +checking their status, retrieving results, and subscribing to updates.

+
interface QueueClient {
    cancel(endpointId: string, options: BaseQueueOptions): Promise<void>;
    result<Output>(endpointId: string, options: BaseQueueOptions): Promise<Result<Output>>;
    status(endpointId: string, options: QueueStatusOptions): Promise<QueueStatus>;
    streamStatus(endpointId: string, options: QueueStatusStreamOptions): Promise<FalStream<unknown, QueueStatus>>;
    submit<Input>(endpointId: string, options: SubmitOptions<Input>): Promise<InQueueQueueStatus>;
    subscribeToStatus(endpointId: string, options: QueueStatusSubscriptionOptions): Promise<CompletedQueueStatus>;
}

Methods

  • Cancels a request in the queue.

    +

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      +
    • options: BaseQueueOptions

      Options to configure how the request +is run and how updates are received.

      +

    Returns Promise<void>

    A promise that resolves once the request is cancelled.

    +

    If the request cannot be cancelled.

    +
  • Retrieves the result of a specific request from the queue.

    +

    Type Parameters

    • Output

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      +
    • options: BaseQueueOptions

      Options to configure how the request is run.

      +

    Returns Promise<Result<Output>>

    A promise that resolves to the result of the request.

    +
  • Retrieves the status of a specific request in the queue.

    +

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      +
    • options: QueueStatusOptions

      Options to configure how the request is run.

      +

    Returns Promise<QueueStatus>

    A promise that resolves to the status of the request.

    +
  • Subscribes to updates for a specific request in the queue using HTTP streaming events.

    +

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      +
    • options: QueueStatusStreamOptions

      Options to configure how the request is run and how updates are received.

      +

    Returns Promise<FalStream<unknown, QueueStatus>>

    The streaming object that can be used to listen for updates.

    +
  • Submits a request to the queue.

    +

    Type Parameters

    • Input

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      +
    • options: SubmitOptions<Input>

      Options to configure how the request is run.

      +

    Returns Promise<InQueueQueueStatus>

    A promise that resolves to the result of enqueuing the request.

    +
  • Subscribes to updates for a specific request in the queue using polling or streaming. +See options.mode for more details.

    +

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      +
    • options: QueueStatusSubscriptionOptions

      Options to configure how the request is run and how updates are received.

      +

    Returns Promise<CompletedQueueStatus>

    A promise that resolves to the final status of the request.

    +
diff --git a/docs/reference/interfaces/RealtimeClient.html b/docs/reference/interfaces/RealtimeClient.html new file mode 100644 index 0000000..4fde46e --- /dev/null +++ b/docs/reference/interfaces/RealtimeClient.html @@ -0,0 +1,6 @@ +RealtimeClient | @fal-ai/client - v1.0.0

Interface RealtimeClient

interface RealtimeClient {
    connect<Input, Output>(app: string, handler: RealtimeConnectionHandler<Output>): RealtimeConnection<Input>;
}

Methods

Methods

  • Connect to the realtime endpoint. The default implementation uses +WebSockets to connect to fal function endpoints that support WSS.

    +

    Type Parameters

    • Input = any
    • Output = any

    Parameters

    • app: string

      the app alias or identifier.

      +
    • handler: RealtimeConnectionHandler<Output>

      the connection handler.

      +

    Returns RealtimeConnection<Input>

diff --git a/docs/reference/interfaces/StorageClient.html b/docs/reference/interfaces/StorageClient.html new file mode 100644 index 0000000..e8041f6 --- /dev/null +++ b/docs/reference/interfaces/StorageClient.html @@ -0,0 +1,14 @@ +StorageClient | @fal-ai/client - v1.0.0

Interface StorageClient

File support for the client. This interface establishes the contract for +uploading files to the server and transforming the input to replace file +objects with URLs.

+
interface StorageClient {
    transformInput: ((input: Record<string, any>) => Promise<Record<string, any>>);
    upload: ((file: Blob) => Promise<string>);
}

Properties

Properties

transformInput: ((input: Record<string, any>) => Promise<Record<string, any>>)

Transform the input to replace file objects with URLs. This is used +to transform the input before sending it to the server and ensures +that the server receives URLs instead of file objects.

+

Type declaration

upload: ((file: Blob) => Promise<string>)

Upload a file to the server. Returns the URL of the uploaded file.

+

Type declaration

    • (file): Promise<string>
    • Parameters

      • file: Blob

        the file to upload

        +

      Returns Promise<string>

      the URL of the uploaded file

      +
diff --git a/docs/reference/interfaces/StreamingClient.html b/docs/reference/interfaces/StreamingClient.html new file mode 100644 index 0000000..a4fcb88 --- /dev/null +++ b/docs/reference/interfaces/StreamingClient.html @@ -0,0 +1,9 @@ +StreamingClient | @fal-ai/client - v1.0.0

Interface StreamingClient

The streaming client interface.

+
interface StreamingClient {
    stream<Output, Input>(endpointId: string, options: StreamOptions<Input>): Promise<FalStream<Input, Output>>;
}

Methods

Methods

  • Calls a fal app that supports streaming and provides a streaming-capable +object as a result, that can be used to get partial results through either +AsyncIterator or through an event listener.

    +

    Type Parameters

    • Output = any
    • Input = Record<string, any>

    Parameters

    • endpointId: string

      the endpoint id, e.g. fal-ai/llavav15-13b.

      +
    • options: StreamOptions<Input>

      the request options, including the input payload.

      +

    Returns Promise<FalStream<Input, Output>>

    the FalStream instance.

    +
diff --git a/docs/reference/types/Metrics.html b/docs/reference/types/Metrics.html new file mode 100644 index 0000000..02926ed --- /dev/null +++ b/docs/reference/types/Metrics.html @@ -0,0 +1 @@ +Metrics | @fal-ai/client - v1.0.0

Type Alias Metrics

Metrics: {
    inference_time: number | null;
}
diff --git a/docs/reference/types/QueueStatus.html b/docs/reference/types/QueueStatus.html index 56a07ec..3c4371c 100644 --- a/docs/reference/types/QueueStatus.html +++ b/docs/reference/types/QueueStatus.html @@ -1 +1 @@ -QueueStatus | @fal-ai/serverless-client - v0.14.2
QueueStatus: InProgressQueueStatus | CompletedQueueStatus | EnqueuedQueueStatus
+QueueStatus | @fal-ai/client - v1.0.0
diff --git a/docs/reference/types/RequestLog.html b/docs/reference/types/RequestLog.html new file mode 100644 index 0000000..c617714 --- /dev/null +++ b/docs/reference/types/RequestLog.html @@ -0,0 +1 @@ +RequestLog | @fal-ai/client - v1.0.0

Type Alias RequestLog

RequestLog: {
    level:
        | "STDERR"
        | "STDOUT"
        | "ERROR"
        | "INFO"
        | "WARN"
        | "DEBUG";
    message: string;
    source: "USER";
    timestamp: string;
}
diff --git a/docs/reference/types/RequestMiddleware.html b/docs/reference/types/RequestMiddleware.html index 5a8201b..53b1467 100644 --- a/docs/reference/types/RequestMiddleware.html +++ b/docs/reference/types/RequestMiddleware.html @@ -1 +1 @@ -RequestMiddleware | @fal-ai/serverless-client - v0.14.2

Type Alias RequestMiddleware

RequestMiddleware: ((request: RequestConfig) => Promise<RequestConfig>)
+RequestMiddleware | @fal-ai/client - v1.0.0

Type Alias RequestMiddleware

RequestMiddleware: ((request: RequestConfig) => Promise<RequestConfig>)
diff --git a/docs/reference/types/ResponseHandler.html b/docs/reference/types/ResponseHandler.html index e044200..a007073 100644 --- a/docs/reference/types/ResponseHandler.html +++ b/docs/reference/types/ResponseHandler.html @@ -1 +1 @@ -ResponseHandler | @fal-ai/serverless-client - v0.14.2

Type Alias ResponseHandler<Output>

ResponseHandler<Output>: ((response: Response) => Promise<Output>)

Type Parameters

  • Output
+ResponseHandler | @fal-ai/client - v1.0.0

Type Alias ResponseHandler<Output>

ResponseHandler<Output>: ((response: Response) => Promise<Output>)

Type Parameters

  • Output
diff --git a/docs/reference/types/Result.html b/docs/reference/types/Result.html new file mode 100644 index 0000000..1b7eecd --- /dev/null +++ b/docs/reference/types/Result.html @@ -0,0 +1,3 @@ +Result | @fal-ai/client - v1.0.0

Type Alias Result<T>

Result<T>: {
    data: T;
    requestId: string;
}

Represents an API result, containing the data, +the request ID and any other relevant information.

+

Type Parameters

  • T
diff --git a/docs/reference/types/RunOptions.html b/docs/reference/types/RunOptions.html new file mode 100644 index 0000000..acb72f4 --- /dev/null +++ b/docs/reference/types/RunOptions.html @@ -0,0 +1,6 @@ +RunOptions | @fal-ai/client - v1.0.0

Type Alias RunOptions<Input>

RunOptions<Input>: {
    input?: Input;
    method?:
        | "get"
        | "post"
        | "put"
        | "delete"
        | string;
}

The function input and other configuration when running +the function, such as the HTTP method to use.

+

Type Parameters

  • Input

Type declaration

  • Optional Readonlyinput?: Input

    The function input. It will be submitted either as query params +or the body payload, depending on the method.

    +
  • Optional Readonlymethod?:
        | "get"
        | "post"
        | "put"
        | "delete"
        | string

    The HTTP method, defaults to post;

    +
diff --git a/docs/reference/types/UrlOptions.html b/docs/reference/types/UrlOptions.html new file mode 100644 index 0000000..8683f61 --- /dev/null +++ b/docs/reference/types/UrlOptions.html @@ -0,0 +1,6 @@ +UrlOptions | @fal-ai/client - v1.0.0

Type Alias UrlOptions

UrlOptions: {
    path?: string;
    query?: Record<string, string>;
    subdomain?: string;
}

Type declaration

  • Optionalpath?: string

    The path to append to the function URL.

    +
  • Optional Readonlyquery?: Record<string, string>

    The query parameters to include in the URL.

    +
  • Optional Readonlysubdomain?: string

    If true, the function will use the queue to run the function +asynchronously and return the result in a separate call. This +influences how the URL is built.

    +
diff --git a/docs/reference/types/ValidationErrorInfo.html b/docs/reference/types/ValidationErrorInfo.html index 871b1ed..bedcb42 100644 --- a/docs/reference/types/ValidationErrorInfo.html +++ b/docs/reference/types/ValidationErrorInfo.html @@ -1 +1 @@ -ValidationErrorInfo | @fal-ai/serverless-client - v0.14.2

Type Alias ValidationErrorInfo

ValidationErrorInfo: {
    loc: (string | number)[];
    msg: string;
    type: string;
}
+ValidationErrorInfo | @fal-ai/client - v1.0.0

Type Alias ValidationErrorInfo

ValidationErrorInfo: {
    loc: (string | number)[];
    msg: string;
    type: string;
}
diff --git a/docs/reference/types/WebHookResponse.html b/docs/reference/types/WebHookResponse.html index 5b72639..acb6115 100644 --- a/docs/reference/types/WebHookResponse.html +++ b/docs/reference/types/WebHookResponse.html @@ -1,4 +1,4 @@ -WebHookResponse | @fal-ai/serverless-client - v0.14.2

Type Alias WebHookResponse<Payload>

WebHookResponse<Payload>: {
    error: never;
    payload: Payload;
    request_id: string;
    status: "OK";
} | {
    error: string;
    payload: Payload;
    request_id: string;
    status: "ERROR";
}

Represents the response from a WebHook request. +WebHookResponse | @fal-ai/client - v1.0.0

Type Alias WebHookResponse<Payload>

WebHookResponse<Payload>: {
    error: never;
    payload: Payload;
    request_id: string;
    status: "OK";
} | {
    error: string;
    payload: Payload;
    request_id: string;
    status: "ERROR";
}

Represents the response from a WebHook request. This is a union type that varies based on the status property.

Type Parameters

  • Payload = any

    The type of the payload in the response. It defaults to any, allowing for flexibility in specifying the structure of the payload.

    @@ -10,4 +10,4 @@
  • payload: Payload

    The payload of the response, structure determined by the Payload type.

  • request_id: string

    The unique identifier for the request.

  • status: "ERROR"

    Indicates an unsuccessful response.

    -
+
diff --git a/docs/reference/variables/fal.html b/docs/reference/variables/fal.html new file mode 100644 index 0000000..7b45315 --- /dev/null +++ b/docs/reference/variables/fal.html @@ -0,0 +1,3 @@ +fal | @fal-ai/client - v1.0.0

Variable falConst

fal: SingletonFalClient = ...

Creates a singleton instance of the client. This is useful as a compatibility +layer for existing code that uses the clients version prior to 1.0.0.

+
diff --git a/docs/reference/variables/queue.html b/docs/reference/variables/queue.html deleted file mode 100644 index 68d1ce1..0000000 --- a/docs/reference/variables/queue.html +++ /dev/null @@ -1,4 +0,0 @@ -queue | @fal-ai/serverless-client - v0.14.2
queue: Queue = ...

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 .

-
diff --git a/docs/reference/variables/realtime.html b/docs/reference/variables/realtime.html deleted file mode 100644 index a6fdc0f..0000000 --- a/docs/reference/variables/realtime.html +++ /dev/null @@ -1,2 +0,0 @@ -realtime | @fal-ai/serverless-client - v0.14.2

Variable realtimeConst

realtime: RealtimeClient = ...

The default implementation of the realtime client.

-
diff --git a/docs/reference/variables/storage.html b/docs/reference/variables/storage.html deleted file mode 100644 index d406ad9..0000000 --- a/docs/reference/variables/storage.html +++ /dev/null @@ -1 +0,0 @@ -storage | @fal-ai/serverless-client - v0.14.2
storage: StorageSupport = ...
From 11cb40ce221080f51bdb8290e90a2f9ac6d6907d Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Mon, 30 Sep 2024 12:54:33 -0700 Subject: [PATCH 07/11] chore: update proxy code --- README.md | 6 +++--- libs/proxy/README.md | 14 +++++++------- libs/proxy/package.json | 1 - libs/proxy/src/index.ts | 2 +- libs/proxy/src/nextjs.ts | 8 ++------ libs/proxy/src/svelte.ts | 5 ++--- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 753af46..af5c01c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # The fal.ai JS client ![@fal-ai/client npm package](https://img.shields.io/npm/v/@fal-ai/client?color=%237527D7&label=client&style=flat-square) -![@fal-ai/serverless-proxy npm package](https://img.shields.io/npm/v/@fal-ai/serverless-proxy?color=%237527D7&label=proxy&style=flat-square) +![@fal-ai/server-proxy npm package](https://img.shields.io/npm/v/@fal-ai/server-proxy?color=%237527D7&label=proxy&style=flat-square) ![Build](https://img.shields.io/github/actions/workflow/status/fal-ai/fal-js/build.yml?style=flat-square) ![License](https://img.shields.io/github/license/fal-ai/fal-js?style=flat-square) @@ -52,11 +52,11 @@ For example, if you are using Next.js, you can: 1. Instal the proxy library ```sh - npm install --save @fal-ai/serverless-proxy + npm install --save @fal-ai/server-proxy ``` 2. Add the proxy as an API endpoint of your app, see an example here in [pages/api/fal/proxy.ts](https://github.com/fal-ai/fal-js/blob/main/apps/demo-nextjs-page-router/pages/api/fal/proxy.ts) ```ts - export { handler as default } from "@fal-ai/serverless-proxy/nextjs"; + export { handler as default } from "@fal-ai/server-proxy/nextjs"; ``` 3. Configure the client to use the proxy: ```ts diff --git a/libs/proxy/README.md b/libs/proxy/README.md index 10318e1..0f8cbbb 100644 --- a/libs/proxy/README.md +++ b/libs/proxy/README.md @@ -1,15 +1,15 @@ # fal.ai proxy library -![@fal-ai/serverless-proxy npm package](https://img.shields.io/npm/v/@fal-ai/serverless-proxy?color=%237527D7&label=%40fal-ai%2Fserverless-proxy&style=flat-square) +![@fal-ai/server-proxy npm package](https://img.shields.io/npm/v/@fal-ai/server-proxy?color=%237527D7&label=%40fal-ai%2Fserver-proxy&style=flat-square) ## Introduction -The `@fal-ai/serverless-proxy` library enables you to route client requests through your own server, therefore safeguarding sensitive credentials. With built-in support for popular frameworks like Next.js and Express, setting up the proxy becomes a breeze. +The `@fal-ai/server-proxy` library enables you to route client requests through your own server, therefore safeguarding sensitive credentials. With built-in support for popular frameworks like Next.js and Express, setting up the proxy becomes a breeze. ### Install the proxy library: ``` -npm install --save @fal-ai/serverless-proxy +npm install --save @fal-ai/server-proxy ``` ## Next.js page router integration @@ -19,7 +19,7 @@ For Next.js applications using the page router: 1. Create an API route in your Next.js app, as a convention we suggest using `pages/api/fal/proxy.js` (or `.ts` if you're using TypeScript): 2. Re-export the proxy handler from the library as the default export: ```ts - export { handler as default } from "@fal-ai/serverless-proxy/nextjs"; + export { handler as default } from "@fal-ai/server-proxy/nextjs"; ``` 3. Ensure you've set the `FAL_KEY` as an environment variable in your server, containing a valid API Key. @@ -31,9 +31,9 @@ For Next.js applications using the app router: 2. Re-export the proxy handler from the library as the default export: ```ts - import { route } from "@fal-ai/serverless-proxy/nextjs"; + import { route } from "@fal-ai/server-proxy/nextjs"; - export const { GET, POST } = route; + export const { GET, POST, PUT } = route; ``` 3. Ensure you've set the `FAL_KEY` as an environment variable in your server, containing a valid API Key. @@ -49,7 +49,7 @@ For Express applications: 2. Add the proxy route and its handler. Note that if your client lives outside of the express app (i.e. the express app is solely used as an external API for other clients), you will need to allow CORS on the proxy route: ```ts - import * as falProxy from "@fal-ai/serverless-proxy/express"; + import * as falProxy from "@fal-ai/server-proxy/express"; app.all( falProxy.route, // '/api/fal/proxy' or you can use your own diff --git a/libs/proxy/package.json b/libs/proxy/package.json index 0b6ed82..c01dd33 100644 --- a/libs/proxy/package.json +++ b/libs/proxy/package.json @@ -9,7 +9,6 @@ }, "keywords": [ "fal", - "serverless", "client", "next", "nextjs", diff --git a/libs/proxy/src/index.ts b/libs/proxy/src/index.ts index 59d6a62..089dd4a 100644 --- a/libs/proxy/src/index.ts +++ b/libs/proxy/src/index.ts @@ -93,7 +93,7 @@ export async function handleRequest( } }); - const proxyUserAgent = `@fal-ai/serverless-proxy/${behavior.id}`; + const proxyUserAgent = `@fal-ai/server-proxy/${behavior.id}`; const userAgent = singleHeaderValue(behavior.getHeader("user-agent")); const res = await fetch(targetUrl, { method: behavior.method, diff --git a/libs/proxy/src/nextjs.ts b/libs/proxy/src/nextjs.ts index 373fe59..d4d4eca 100644 --- a/libs/proxy/src/nextjs.ts +++ b/libs/proxy/src/nextjs.ts @@ -47,18 +47,14 @@ export const handler: NextApiHandler = async (request, response) => { * @returns a promise that resolves when the request is handled. */ async function routeHandler(request: NextRequest) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const responseHeaders: Record = {}; - - // check if response if from a streaming request - + const responseHeaders = new Headers(); return await handleRequest({ id: "nextjs-app-router", method: request.method, getRequestBody: async () => request.text(), getHeaders: () => fromHeaders(request.headers), getHeader: (name) => request.headers.get(name), - sendHeader: (name, value) => (responseHeaders[name] = value), + sendHeader: (name, value) => responseHeaders.set(name, value), respondWith: (status, data) => NextResponse.json(data, { status, diff --git a/libs/proxy/src/svelte.ts b/libs/proxy/src/svelte.ts index d1ee5b8..9c63e3d 100644 --- a/libs/proxy/src/svelte.ts +++ b/libs/proxy/src/svelte.ts @@ -21,10 +21,9 @@ export const createRequestHandler = ({ }: RequestHandlerParams = {}) => { const handler: RequestHandler = async ({ request }) => { const FAL_KEY = credentials || process.env.FAL_KEY || ""; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const responseHeaders: Record = { + const responseHeaders = new Headers({ "Content-Type": "application/json", - }; + }); return await handleRequest({ id: "svelte-app-router", method: request.method, From 74a853380f47001f01c3bbb228f474bbf9058534 Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Tue, 1 Oct 2024 13:05:29 -0700 Subject: [PATCH 08/11] chore: alpha release --- libs/client/package.json | 2 +- libs/proxy/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/client/package.json b/libs/client/package.json index ce307bf..9a1ed71 100644 --- a/libs/client/package.json +++ b/libs/client/package.json @@ -1,7 +1,7 @@ { "name": "@fal-ai/client", "description": "The fal.ai client for JavaScript and TypeScript", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "license": "MIT", "repository": { "type": "git", diff --git a/libs/proxy/package.json b/libs/proxy/package.json index c01dd33..8d00802 100644 --- a/libs/proxy/package.json +++ b/libs/proxy/package.json @@ -1,6 +1,6 @@ { "name": "@fal-ai/server-proxy", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "license": "MIT", "repository": { "type": "git", From 27ced191a6092076b11f73f39cb958a1b9c91a1c Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Tue, 1 Oct 2024 13:06:44 -0700 Subject: [PATCH 09/11] chore: fix lint staged rule --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61ffb15..0d6cb19 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "cspell", "prettier --write" ], - "libs/client/src/**/*.{ts}": [ + "libs/client/src/**/*.ts": [ "npm run docs:typedoc" ] }, From c44136942ec1a9025b4453d6bd2a2a6b54677d19 Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Mon, 7 Oct 2024 12:45:22 -0700 Subject: [PATCH 10/11] chore: clean-up docs --- README.md | 6 ++--- libs/client/README.md | 4 +-- libs/client/package.json | 6 ++--- libs/client/src/client.ts | 12 ++++----- libs/client/src/config.ts | 23 +++++++++------- libs/client/src/index.ts | 5 ++-- libs/client/src/middleware.ts | 25 ++++++++++------- libs/client/src/request.ts | 6 ++--- libs/proxy/package.json | 12 ++++++++- libs/proxy/src/hono.ts | 51 +++++++++++++++++++++++++++++++++++ libs/proxy/src/index.ts | 4 +-- package-lock.json | 10 +++++++ package.json | 1 + 13 files changed, 122 insertions(+), 43 deletions(-) create mode 100644 libs/proxy/src/hono.ts diff --git a/README.md b/README.md index af5c01c..cc40934 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ ## About the Project -The fal serverless JavaScript/TypeScript Client is a robust and user-friendly library designed for seamless integration of fal serverless functions in Web, Node.js, and React Native applications. Developed in TypeScript, it provides developers with type safety right from the start. +The fal JavaScript/TypeScript Client is a robust and user-friendly library designed for seamless integration of fal endpoints in Web, Node.js, and React Native applications. Developed in TypeScript, it provides developers with type safety right from the start. ## Getting Started -The `@fal-ai/client` library serves as a client for fal serverless Python functions. For guidance on creating your functions, refer to the [quickstart guide](https://fal.ai/docs). +The `@fal-ai/client` library serves as a client for fal apps hosted on fal. For guidance on consuming and creating apps, refer to the [quickstart guide](https://fal.ai/docs). ### Client Library @@ -46,7 +46,7 @@ See the available [model APIs](https://fal.ai/models) for more details. ### The fal client proxy -Although the fal client is designed to work in any JS environment, including directly in your browser, **it is not recommended** to store your credentials in your client source code. The common practice is to use your own server to serve as a proxy to serverless APIs. Luckily fal supports that out-of-the-box with plug-and-play proxy functions for the most common engines/frameworks. +Although the fal client is designed to work in any JS environment, including directly in your browser, **it is not recommended** to store your credentials in your client source code. The common practice is to use your own server to serve as a proxy to fal APIs. Luckily fal supports that out-of-the-box with plug-and-play proxy functions for the most common engines/frameworks. For example, if you are using Next.js, you can: diff --git a/libs/client/README.md b/libs/client/README.md index d348fa8..ec7e8b7 100644 --- a/libs/client/README.md +++ b/libs/client/README.md @@ -4,7 +4,7 @@ ## Introduction -The `fal.ai` JavaScript Client Library provides a seamless way to interact with `fal` serverless functions from your JavaScript or TypeScript applications. With built-in support for various platforms, it ensures consistent behavior across web, Node.js, and React Native environments. +The `fal.ai` JavaScript Client Library provides a seamless way to interact with `fal` endpoints from your JavaScript or TypeScript applications. With built-in support for various platforms, it ensures consistent behavior across web, Node.js, and React Native environments. ## Getting started @@ -49,4 +49,4 @@ const result = await fal.subscribe("my-function-id", { ## More features -The client library offers a plethora of features designed to simplify your serverless journey with `fal.ai`. Dive into the [official documentation](https://fal.ai/docs) for a comprehensive guide. +The client library offers a plethora of features designed to simplify your journey with `fal.ai`. Dive into the [official documentation](https://fal.ai/docs) for a comprehensive guide. diff --git a/libs/client/package.json b/libs/client/package.json index 9a1ed71..803daa5 100644 --- a/libs/client/package.json +++ b/libs/client/package.json @@ -1,7 +1,7 @@ { "name": "@fal-ai/client", "description": "The fal.ai client for JavaScript and TypeScript", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "license": "MIT", "repository": { "type": "git", @@ -10,10 +10,10 @@ }, "keywords": [ "fal", - "serverless", "client", "ai", - "ml" + "ml", + "typescript" ], "dependencies": { "@msgpack/msgpack": "^3.0.0-beta2", diff --git a/libs/client/src/client.ts b/libs/client/src/client.ts index 651d39b..4cd241a 100644 --- a/libs/client/src/client.ts +++ b/libs/client/src/client.ts @@ -39,20 +39,20 @@ export interface FalClient { readonly streaming: StreamingClient; /** - * Runs a fal serverless function identified by its `id`. + * Runs a fal endpoints identified by its `endpointId`. * - * @param id the registered function revision id or alias. + * @param endpointId the registered function revision id or alias. * @returns the remote function output */ run>( - id: string, + endpointId: string, options: RunOptions, ): Promise>; /** * Subscribes to updates for a specific request in the queue. * - * @param endpointId - The ID of the function web endpoint. + * @param endpointId - The ID of the API 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. */ @@ -91,7 +91,7 @@ export function createFalClient(userConfig: Config = {}): FalClient { streaming, stream: streaming.stream, async run( - id: string, + endpointId: string, options: RunOptions = {}, ): Promise> { const input = options.input @@ -99,7 +99,7 @@ export function createFalClient(userConfig: Config = {}): FalClient { : undefined; return dispatchRequest>({ method: options.method, - targetUrl: buildUrl(id, options), + targetUrl: buildUrl(endpointId, options), input: input as Input, config: { ...config, diff --git a/libs/client/src/config.ts b/libs/client/src/config.ts index 9e17651..19309f8 100644 --- a/libs/client/src/config.ts +++ b/libs/client/src/config.ts @@ -1,4 +1,8 @@ -import { withProxy, type RequestMiddleware } from "./middleware"; +import { + withMiddleware, + withProxy, + type RequestMiddleware, +} from "./middleware"; import type { ResponseHandler } from "./response"; import { defaultResponseHandler } from "./response"; @@ -17,7 +21,7 @@ export function resolveDefaultFetch(): FetchType { export type Config = { /** - * The credentials to use for the fal serverless client. When using the + * The credentials to use for the fal client. When using the * client in the browser, it's recommended to use a proxy server to avoid * exposing the credentials in the client's environment. * @@ -36,7 +40,7 @@ export type Config = { suppressLocalCredentialsWarning?: boolean; /** * The URL of the proxy server to use for the client requests. The proxy - * server should forward the requests to the fal serverless rest api. + * server should forward the requests to the fal api. */ proxyUrl?: string; /** @@ -94,7 +98,7 @@ const DEFAULT_CONFIG: Partial = { }; /** - * Configures the fal serverless client. + * Configures the fal client. * * @param config the new configuration. */ @@ -107,11 +111,10 @@ export function createConfig(config: Config): RequiredConfig { if (config.proxyUrl) { configuration = { ...configuration, - requestMiddleware: withProxy({ targetUrl: config.proxyUrl }), - // requestMiddleware: withMiddleware( - // withProxy({ targetUrl: config.proxyUrl }), - // configuration.requestMiddleware, - // ), + requestMiddleware: withMiddleware( + configuration.requestMiddleware, + withProxy({ targetUrl: config.proxyUrl }), + ), }; } const { credentials: resolveCredentials, suppressLocalCredentialsWarning } = @@ -134,7 +137,7 @@ export function createConfig(config: Config): RequiredConfig { } /** - * @returns the URL of the fal serverless rest api endpoint. + * @returns the URL of the fal REST api endpoint. */ export function getRestApiUrl(): string { return "https://rest.alpha.fal.ai"; diff --git a/libs/client/src/index.ts b/libs/client/src/index.ts index bf97ab7..e76efa3 100644 --- a/libs/client/src/index.ts +++ b/libs/client/src/index.ts @@ -1,5 +1,5 @@ import { createFalClient, type FalClient } from "./client"; -import { Config, createConfig } from "./config"; +import { Config } from "./config"; import { StreamOptions } from "./streaming"; import { RunOptions } from "./types"; @@ -32,8 +32,7 @@ export const fal: SingletonFalClient = (function createSingletonFalClient() { let currentInstance: FalClient = createFalClient(); return { config(config: Config) { - console.log(config.requestMiddleware); - currentInstance = createFalClient(createConfig(config)); + currentInstance = createFalClient(config); }, get queue() { return currentInstance.queue; diff --git a/libs/client/src/middleware.ts b/libs/client/src/middleware.ts index b87e2ff..2d4ad43 100644 --- a/libs/client/src/middleware.ts +++ b/libs/client/src/middleware.ts @@ -30,7 +30,7 @@ export function withMiddleware( typeof middleware === "function"; return async (config: RequestConfig) => { - let currentConfig = config; + let currentConfig = { ...config }; for (const middleware of middlewares.filter(isDefined)) { currentConfig = await middleware(currentConfig); } @@ -45,17 +45,22 @@ export type RequestProxyConfig = { export const TARGET_URL_HEADER = "x-fal-target-url"; export function withProxy(config: RequestProxyConfig): RequestMiddleware { + const passthrough = (requestConfig: RequestConfig) => + Promise.resolve(requestConfig); // when running on the server, we don't need to proxy the request if (typeof window === "undefined") { - return (requestConfig) => Promise.resolve(requestConfig); + return passthrough; } + // if x-fal-target-url is already set, we skip it return (requestConfig) => - Promise.resolve({ - ...requestConfig, - url: config.targetUrl, - headers: { - ...(requestConfig.headers || {}), - [TARGET_URL_HEADER]: requestConfig.url, - }, - }); + requestConfig.headers && TARGET_URL_HEADER in requestConfig + ? passthrough(requestConfig) + : Promise.resolve({ + ...requestConfig, + url: config.targetUrl, + headers: { + ...(requestConfig.headers || {}), + [TARGET_URL_HEADER]: requestConfig.url, + }, + }); } diff --git a/libs/client/src/request.ts b/libs/client/src/request.ts index 2d1e8f6..db663c5 100644 --- a/libs/client/src/request.ts +++ b/libs/client/src/request.ts @@ -23,7 +23,7 @@ type RequestParams = { export async function dispatchRequest( params: RequestParams, ): Promise { - const { method = "POST", targetUrl, input, config, options = {} } = params; + const { targetUrl, input, config, options = {} } = params; const { credentials: credentialsValue, requestMiddleware, @@ -36,9 +36,9 @@ export async function dispatchRequest( ? credentialsValue() : credentialsValue; - const { url, headers } = await requestMiddleware({ + const { method, url, headers } = await requestMiddleware({ + method: (params.method ?? options.method ?? "post").toUpperCase(), url: targetUrl, - method: method.toUpperCase(), }); const authHeader = credentials ? { Authorization: `Key ${credentials}` } : {}; const requestHeaders = { diff --git a/libs/proxy/package.json b/libs/proxy/package.json index 8d00802..7fc56cd 100644 --- a/libs/proxy/package.json +++ b/libs/proxy/package.json @@ -1,6 +1,7 @@ { "name": "@fal-ai/server-proxy", - "version": "1.0.0-alpha.0", + "description": "The fal.ai server proxy adapter for JavaScript and TypeScript Web frameworks", + "version": "1.0.0", "license": "MIT", "repository": { "type": "git", @@ -13,11 +14,13 @@ "next", "nextjs", "express", + "hono", "proxy" ], "exports": { ".": "./src/index.js", "./express": "./src/express.js", + "./hono": "./src/hono.js", "./nextjs": "./src/nextjs.js", "./remix": "./src/remix.js", "./svelte": "./src/svelte.js" @@ -27,6 +30,9 @@ "express": [ "src/express.d.ts" ], + "hono": [ + "src/hono.d.ts" + ], "nextjs": [ "src/nextjs.d.ts" ], @@ -44,6 +50,7 @@ "@remix-run/dev": "^2.0.0", "@sveltejs/kit": "^2.0.0", "express": "^4.0.0", + "hono": "^4.0.0", "next": "13.4 - 14", "react": "^18.0.0", "react-dom": "^18.0.0" @@ -58,6 +65,9 @@ "express": { "optional": true }, + "hono": { + "optional": true + }, "next": { "optional": true }, diff --git a/libs/proxy/src/hono.ts b/libs/proxy/src/hono.ts new file mode 100644 index 0000000..bf8f9cf --- /dev/null +++ b/libs/proxy/src/hono.ts @@ -0,0 +1,51 @@ +import { Context } from "hono"; +import { type StatusCode } from "hono/utils/http-status"; +import { + handleRequest, + HeaderValue, + resolveApiKeyFromEnv, + responsePassthrough, +} from "./index"; + +export type FalHonoProxyOptions = { + /** + * A function to resolve the API key used by the proxy. + * By default, it uses the `FAL_KEY` environment variable. + */ + resolveApiKey?: () => Promise; +}; + +type RouteHandler = (context: Context) => Promise; + +/** + * Creates a route handler that proxies requests to the fal API. + * + * This is a drop-in handler for Hono applications so that the client can be called + * directly from the client-side code while keeping API keys safe. + * + * @param param the proxy options. + * @returns a Hono route handler function. + */ +export function createRouteHandler({ + resolveApiKey = resolveApiKeyFromEnv, +}: FalHonoProxyOptions): RouteHandler { + const routeHandler: RouteHandler = async (context) => { + const responseHeaders: Record = {}; + const response = await handleRequest({ + id: "hono", + method: context.req.method, + respondWith: (status, data) => { + return context.json(data, status as StatusCode, responseHeaders); + }, + getHeaders: () => responseHeaders, + getHeader: (name) => context.req.header[name], + sendHeader: (name, value) => (responseHeaders[name] = value), + getRequestBody: async () => JSON.stringify(await context.req.json()), + sendResponse: responsePassthrough, + resolveApiKey, + }); + return response; + }; + + return routeHandler; +} diff --git a/libs/proxy/src/index.ts b/libs/proxy/src/index.ts index 089dd4a..0b01966 100644 --- a/libs/proxy/src/index.ts +++ b/libs/proxy/src/index.ts @@ -57,8 +57,8 @@ function getFalKey(): string | undefined { const EXCLUDED_HEADERS = ["content-length", "content-encoding"]; /** - * A request handler that proxies the request to the fal-serverless - * endpoint. This is useful so client-side calls to the fal-serverless endpoint + * A request handler that proxies the request to the fal API + * endpoint. This is useful so client-side calls to the fal endpoint * can be made without CORS issues and the correct credentials can be added * effortlessly. * diff --git a/package-lock.json b/package-lock.json index f4ed031..73d41ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,6 +89,7 @@ "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "fs-extra": "^11.1.0", + "hono": "^4.6.3", "husky": "^8.0.0", "jest": "29.4.3", "jest-environment-jsdom": "29.4.3", @@ -17458,6 +17459,15 @@ "tslib": "^2.0.3" } }, + "node_modules/hono": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.3.tgz", + "integrity": "sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ==", + "dev": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hook-std": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", diff --git a/package.json b/package.json index 0d6cb19..3d6231f 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "fs-extra": "^11.1.0", + "hono": "^4.6.3", "husky": "^8.0.0", "jest": "29.4.3", "jest-environment-jsdom": "29.4.3", From b4e8b68efee691349dea4153ebf8e6ca76d4da31 Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Mon, 7 Oct 2024 12:46:08 -0700 Subject: [PATCH 11/11] chore: reference docs updated --- docs/reference/classes/ApiError.html | 4 ++-- docs/reference/classes/ValidationError.html | 4 ++-- docs/reference/functions/createFalClient.html | 2 +- .../functions/isCompletedQueueStatus.html | 2 +- docs/reference/functions/isQueueStatus.html | 2 +- docs/reference/functions/parseEndpointId.html | 2 +- docs/reference/functions/withMiddleware.html | 2 +- docs/reference/functions/withProxy.html | 2 +- .../interfaces/CompletedQueueStatus.html | 4 ++-- docs/reference/interfaces/FalClient.html | 20 +++++++++---------- .../interfaces/InProgressQueueStatus.html | 4 ++-- .../interfaces/InQueueQueueStatus.html | 4 ++-- docs/reference/interfaces/QueueClient.html | 14 ++++++------- docs/reference/interfaces/RealtimeClient.html | 4 ++-- docs/reference/interfaces/StorageClient.html | 6 +++--- .../reference/interfaces/StreamingClient.html | 4 ++-- docs/reference/types/Metrics.html | 2 +- docs/reference/types/QueueStatus.html | 2 +- docs/reference/types/RequestLog.html | 2 +- docs/reference/types/RequestMiddleware.html | 2 +- docs/reference/types/ResponseHandler.html | 2 +- docs/reference/types/Result.html | 2 +- docs/reference/types/RunOptions.html | 2 +- docs/reference/types/UrlOptions.html | 2 +- docs/reference/types/ValidationErrorInfo.html | 2 +- docs/reference/types/WebHookResponse.html | 2 +- docs/reference/variables/fal.html | 2 +- 27 files changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/reference/classes/ApiError.html b/docs/reference/classes/ApiError.html index 8ea3d94..5902f0e 100644 --- a/docs/reference/classes/ApiError.html +++ b/docs/reference/classes/ApiError.html @@ -1,4 +1,4 @@ -ApiError | @fal-ai/client - v1.0.0

Class ApiError<Body>

Type Parameters

  • Body

Hierarchy (view full)

Constructors

constructor +ApiError | @fal-ai/client - v1.0.0

Class ApiError<Body>

Type Parameters

  • Body

Hierarchy (view full)

Constructors

Properties

Constructors

Properties

body: Body
status: number
+

Constructors

Properties

body: Body
status: number
diff --git a/docs/reference/classes/ValidationError.html b/docs/reference/classes/ValidationError.html index 6ab0728..f935c97 100644 --- a/docs/reference/classes/ValidationError.html +++ b/docs/reference/classes/ValidationError.html @@ -1,6 +1,6 @@ -ValidationError | @fal-ai/client - v1.0.0

Class ValidationError

Hierarchy (view full)

  • ApiError<ValidationErrorBody>
    • ValidationError

Constructors

constructor +ValidationError | @fal-ai/client - v1.0.0

Class ValidationError

Hierarchy (view full)

  • ApiError<ValidationErrorBody>
    • ValidationError

Constructors

Properties

Accessors

Methods

Constructors

Properties

body: ValidationErrorBody
status: number

Accessors

Methods

+

Constructors

Properties

body: ValidationErrorBody
status: number

Accessors

Methods

diff --git a/docs/reference/functions/createFalClient.html b/docs/reference/functions/createFalClient.html index 0bc5e2a..c543e63 100644 --- a/docs/reference/functions/createFalClient.html +++ b/docs/reference/functions/createFalClient.html @@ -1,4 +1,4 @@ createFalClient | @fal-ai/client - v1.0.0

Function createFalClient

  • Creates a new reference of the FalClient.

    Parameters

    • userConfig: Config = {}

      Optional configuration to override the default settings.

    Returns FalClient

    a new instance of the FalClient.

    -
+
diff --git a/docs/reference/functions/isCompletedQueueStatus.html b/docs/reference/functions/isCompletedQueueStatus.html index 628ff7a..d0ed935 100644 --- a/docs/reference/functions/isCompletedQueueStatus.html +++ b/docs/reference/functions/isCompletedQueueStatus.html @@ -1 +1 @@ -isCompletedQueueStatus | @fal-ai/client - v1.0.0

Function isCompletedQueueStatus

+isCompletedQueueStatus | @fal-ai/client - v1.0.0

Function isCompletedQueueStatus

diff --git a/docs/reference/functions/isQueueStatus.html b/docs/reference/functions/isQueueStatus.html index 7c750d7..9bee16c 100644 --- a/docs/reference/functions/isQueueStatus.html +++ b/docs/reference/functions/isQueueStatus.html @@ -1 +1 @@ -isQueueStatus | @fal-ai/client - v1.0.0

Function isQueueStatus

+isQueueStatus | @fal-ai/client - v1.0.0

Function isQueueStatus

diff --git a/docs/reference/functions/parseEndpointId.html b/docs/reference/functions/parseEndpointId.html index dc3a82f..642fbc5 100644 --- a/docs/reference/functions/parseEndpointId.html +++ b/docs/reference/functions/parseEndpointId.html @@ -1 +1 @@ -parseEndpointId | @fal-ai/client - v1.0.0

Function parseEndpointId

  • Parameters

    • id: string

    Returns EndpointId

+parseEndpointId | @fal-ai/client - v1.0.0

Function parseEndpointId

  • Parameters

    • id: string

    Returns EndpointId

diff --git a/docs/reference/functions/withMiddleware.html b/docs/reference/functions/withMiddleware.html index c0ba81c..953c273 100644 --- a/docs/reference/functions/withMiddleware.html +++ b/docs/reference/functions/withMiddleware.html @@ -1,4 +1,4 @@ withMiddleware | @fal-ai/client - v1.0.0

Function withMiddleware

+
diff --git a/docs/reference/functions/withProxy.html b/docs/reference/functions/withProxy.html index fd53354..e1e9722 100644 --- a/docs/reference/functions/withProxy.html +++ b/docs/reference/functions/withProxy.html @@ -1 +1 @@ -withProxy | @fal-ai/client - v1.0.0

Function withProxy

+withProxy | @fal-ai/client - v1.0.0

Function withProxy

diff --git a/docs/reference/interfaces/CompletedQueueStatus.html b/docs/reference/interfaces/CompletedQueueStatus.html index 184fef2..fd95083 100644 --- a/docs/reference/interfaces/CompletedQueueStatus.html +++ b/docs/reference/interfaces/CompletedQueueStatus.html @@ -1,6 +1,6 @@ -CompletedQueueStatus | @fal-ai/client - v1.0.0

Interface CompletedQueueStatus

interface CompletedQueueStatus {
    logs: RequestLog[];
    metrics?: Metrics;
    request_id: string;
    response_url: string;
    status: "COMPLETED";
}

Hierarchy

  • BaseQueueStatus
    • CompletedQueueStatus

Properties

logs +CompletedQueueStatus | @fal-ai/client - v1.0.0

Interface CompletedQueueStatus

interface CompletedQueueStatus {
    logs: RequestLog[];
    metrics?: Metrics;
    request_id: string;
    response_url: string;
    status: "COMPLETED";
}

Hierarchy

  • BaseQueueStatus
    • CompletedQueueStatus

Properties

logs: RequestLog[]
metrics?: Metrics
request_id: string
response_url: string
status: "COMPLETED"
+

Properties

logs: RequestLog[]
metrics?: Metrics
request_id: string
response_url: string
status: "COMPLETED"
diff --git a/docs/reference/interfaces/FalClient.html b/docs/reference/interfaces/FalClient.html index bc916df..9946c50 100644 --- a/docs/reference/interfaces/FalClient.html +++ b/docs/reference/interfaces/FalClient.html @@ -1,7 +1,7 @@ FalClient | @fal-ai/client - v1.0.0

Interface FalClient

The main client type, it provides access to simple API model usage, as well as access to the queue and storage APIs.

createFalClient

-
interface FalClient {
    queue: QueueClient;
    realtime: RealtimeClient;
    storage: StorageClient;
    stream: (<Output, Input>(endpointId: string, options: StreamOptions<Input>) => Promise<FalStream<Input, Output>>);
    streaming: StreamingClient;
    run<Output, Input>(id: string, options: RunOptions<Input>): Promise<Result<Output>>;
    subscribe<Output, Input>(endpointId: string, options: RunOptions<Input> & QueueSubscribeOptions): Promise<Result<Output>>;
}

Properties

interface FalClient {
    queue: QueueClient;
    realtime: RealtimeClient;
    storage: StorageClient;
    stream: (<Output, Input>(endpointId: string, options: StreamOptions<Input>) => Promise<FalStream<Input, Output>>);
    streaming: StreamingClient;
    run<Output, Input>(endpointId: string, options: RunOptions<Input>): Promise<Result<Output>>;
    subscribe<Output, Input>(endpointId: string, options: RunOptions<Input> & QueueSubscribeOptions): Promise<Result<Output>>;
}

Properties

queue realtime storage stream @@ -9,14 +9,14 @@

Methods

Properties

The queue client to interact with the queue API.

-
realtime: RealtimeClient

The realtime client to interact with the realtime API +

realtime: RealtimeClient

The realtime client to interact with the realtime API and receive updates in real-time.

  • #RealtimeClient
  • #RealtimeClient.connect
-
storage: StorageClient

The storage client to interact with the storage API.

-
stream: (<Output, Input>(endpointId: string, options: StreamOptions<Input>) => Promise<FalStream<Input, Output>>)

Calls a fal app that supports streaming and provides a streaming-capable +

storage: StorageClient

The storage client to interact with the storage API.

+
stream: (<Output, Input>(endpointId: string, options: StreamOptions<Input>) => Promise<FalStream<Input, Output>>)

Calls a fal app that supports streaming and provides a streaming-capable object as a result, that can be used to get partial results through either AsyncIterator or through an event listener.

Type declaration

    • <Output, Input>(endpointId, options): Promise<FalStream<Input, Output>>
    • Calls a fal app that supports streaming and provides a streaming-capable @@ -28,13 +28,13 @@

the endpoint id, e.g. fal-ai/llavav15-13b.

the request options, including the input payload.

the FalStream instance.

-
streaming: StreamingClient

The streaming client to interact with the streaming API.

+
streaming: StreamingClient

The streaming client to interact with the streaming API.

#stream

-

Methods

  • Runs a fal serverless function identified by its id.

    -

    Type Parameters

    • Output = any
    • Input = Record<string, any>

    Parameters

    • id: string

      the registered function revision id or alias.

      +

Methods

  • Subscribes to updates for a specific request in the queue.

    -

    Type Parameters

    • Output = any
    • Input = Record<string, any>

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

      +
  • Subscribes to updates for a specific request in the queue.

    +

    Type Parameters

    • Output = any
    • Input = Record<string, any>

    Parameters

    • endpointId: string

      The ID of the API endpoint.

    • options: RunOptions<Input> & QueueSubscribeOptions

      Options to configure how the request is run and how updates are received.

    Returns Promise<Result<Output>>

    A promise that resolves to the result of the request once it's completed.

    -
+
diff --git a/docs/reference/interfaces/InProgressQueueStatus.html b/docs/reference/interfaces/InProgressQueueStatus.html index 83a1375..259921d 100644 --- a/docs/reference/interfaces/InProgressQueueStatus.html +++ b/docs/reference/interfaces/InProgressQueueStatus.html @@ -1,5 +1,5 @@ -InProgressQueueStatus | @fal-ai/client - v1.0.0

Interface InProgressQueueStatus

interface InProgressQueueStatus {
    logs: RequestLog[];
    request_id: string;
    response_url: string;
    status: "IN_PROGRESS";
}

Hierarchy

  • BaseQueueStatus
    • InProgressQueueStatus

Properties

logs +InProgressQueueStatus | @fal-ai/client - v1.0.0

Interface InProgressQueueStatus

interface InProgressQueueStatus {
    logs: RequestLog[];
    request_id: string;
    response_url: string;
    status: "IN_PROGRESS";
}

Hierarchy

  • BaseQueueStatus
    • InProgressQueueStatus

Properties

logs: RequestLog[]
request_id: string
response_url: string
status: "IN_PROGRESS"
+

Properties

logs: RequestLog[]
request_id: string
response_url: string
status: "IN_PROGRESS"
diff --git a/docs/reference/interfaces/InQueueQueueStatus.html b/docs/reference/interfaces/InQueueQueueStatus.html index c5d07ee..181f82e 100644 --- a/docs/reference/interfaces/InQueueQueueStatus.html +++ b/docs/reference/interfaces/InQueueQueueStatus.html @@ -1,5 +1,5 @@ -InQueueQueueStatus | @fal-ai/client - v1.0.0

Interface InQueueQueueStatus

interface InQueueQueueStatus {
    queue_position: number;
    request_id: string;
    response_url: string;
    status: "IN_QUEUE";
}

Hierarchy

  • BaseQueueStatus
    • InQueueQueueStatus

Properties

queue_position +InQueueQueueStatus | @fal-ai/client - v1.0.0

Interface InQueueQueueStatus

interface InQueueQueueStatus {
    queue_position: number;
    request_id: string;
    response_url: string;
    status: "IN_QUEUE";
}

Hierarchy

  • BaseQueueStatus
    • InQueueQueueStatus

Properties

queue_position: number
request_id: string
response_url: string
status: "IN_QUEUE"
+

Properties

queue_position: number
request_id: string
response_url: string
status: "IN_QUEUE"
diff --git a/docs/reference/interfaces/QueueClient.html b/docs/reference/interfaces/QueueClient.html index 4a2469b..2934f69 100644 --- a/docs/reference/interfaces/QueueClient.html +++ b/docs/reference/interfaces/QueueClient.html @@ -1,6 +1,6 @@ QueueClient | @fal-ai/client - v1.0.0

Interface QueueClient

Represents a request queue with methods for submitting requests, checking their status, retrieving results, and subscribing to updates.

-
interface QueueClient {
    cancel(endpointId: string, options: BaseQueueOptions): Promise<void>;
    result<Output>(endpointId: string, options: BaseQueueOptions): Promise<Result<Output>>;
    status(endpointId: string, options: QueueStatusOptions): Promise<QueueStatus>;
    streamStatus(endpointId: string, options: QueueStatusStreamOptions): Promise<FalStream<unknown, QueueStatus>>;
    submit<Input>(endpointId: string, options: SubmitOptions<Input>): Promise<InQueueQueueStatus>;
    subscribeToStatus(endpointId: string, options: QueueStatusSubscriptionOptions): Promise<CompletedQueueStatus>;
}

Methods

interface QueueClient {
    cancel(endpointId: string, options: BaseQueueOptions): Promise<void>;
    result<Output>(endpointId: string, options: BaseQueueOptions): Promise<Result<Output>>;
    status(endpointId: string, options: QueueStatusOptions): Promise<QueueStatus>;
    streamStatus(endpointId: string, options: QueueStatusStreamOptions): Promise<FalStream<unknown, QueueStatus>>;
    submit<Input>(endpointId: string, options: SubmitOptions<Input>): Promise<InQueueQueueStatus>;
    subscribeToStatus(endpointId: string, options: QueueStatusSubscriptionOptions): Promise<CompletedQueueStatus>;
}

Methods

cancel result status streamStatus @@ -12,25 +12,25 @@ is run and how updates are received.

Returns Promise<void>

A promise that resolves once the request is cancelled.

If the request cannot be cancelled.

-
  • Retrieves the result of a specific request from the queue.

    Type Parameters

    • Output

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

    • options: BaseQueueOptions

      Options to configure how the request is run.

    Returns Promise<Result<Output>>

    A promise that resolves to the result of the request.

    -
  • Retrieves the status of a specific request in the queue.

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

    • options: QueueStatusOptions

      Options to configure how the request is run.

    Returns Promise<QueueStatus>

    A promise that resolves to the status of the request.

    -
  • Subscribes to updates for a specific request in the queue using HTTP streaming events.

    +
  • Subscribes to updates for a specific request in the queue using HTTP streaming events.

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

    • options: QueueStatusStreamOptions

      Options to configure how the request is run and how updates are received.

    Returns Promise<FalStream<unknown, QueueStatus>>

    The streaming object that can be used to listen for updates.

    -
  • Submits a request to the queue.

    Type Parameters

    • Input

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

    • options: SubmitOptions<Input>

      Options to configure how the request is run.

    Returns Promise<InQueueQueueStatus>

    A promise that resolves to the result of enqueuing the request.

    -
  • Subscribes to updates for a specific request in the queue using polling or streaming. See options.mode for more details.

    Parameters

    • endpointId: string

      The ID of the function web endpoint.

    • options: QueueStatusSubscriptionOptions

      Options to configure how the request is run and how updates are received.

    Returns Promise<CompletedQueueStatus>

    A promise that resolves to the final status of the request.

    -
+
diff --git a/docs/reference/interfaces/RealtimeClient.html b/docs/reference/interfaces/RealtimeClient.html index 4fde46e..3e5d2d8 100644 --- a/docs/reference/interfaces/RealtimeClient.html +++ b/docs/reference/interfaces/RealtimeClient.html @@ -1,6 +1,6 @@ -RealtimeClient | @fal-ai/client - v1.0.0

Interface RealtimeClient

interface RealtimeClient {
    connect<Input, Output>(app: string, handler: RealtimeConnectionHandler<Output>): RealtimeConnection<Input>;
}

Methods

connect +RealtimeClient | @fal-ai/client - v1.0.0

Interface RealtimeClient

interface RealtimeClient {
    connect<Input, Output>(app: string, handler: RealtimeConnectionHandler<Output>): RealtimeConnection<Input>;
}

Methods

Methods

  • Connect to the realtime endpoint. The default implementation uses WebSockets to connect to fal function endpoints that support WSS.

    Type Parameters

    • Input = any
    • Output = any

    Parameters

    • app: string

      the app alias or identifier.

    • handler: RealtimeConnectionHandler<Output>

      the connection handler.

      -

    Returns RealtimeConnection<Input>

+

Returns RealtimeConnection<Input>

diff --git a/docs/reference/interfaces/StorageClient.html b/docs/reference/interfaces/StorageClient.html index e8041f6..0b2b01e 100644 --- a/docs/reference/interfaces/StorageClient.html +++ b/docs/reference/interfaces/StorageClient.html @@ -1,14 +1,14 @@ StorageClient | @fal-ai/client - v1.0.0

Interface StorageClient

File support for the client. This interface establishes the contract for uploading files to the server and transforming the input to replace file objects with URLs.

-
interface StorageClient {
    transformInput: ((input: Record<string, any>) => Promise<Record<string, any>>);
    upload: ((file: Blob) => Promise<string>);
}

Properties

interface StorageClient {
    transformInput: ((input: Record<string, any>) => Promise<Record<string, any>>);
    upload: ((file: Blob) => Promise<string>);
}

Properties

transformInput: ((input: Record<string, any>) => Promise<Record<string, any>>)

Transform the input to replace file objects with URLs. This is used to transform the input before sending it to the server and ensures that the server receives URLs instead of file objects.

Type declaration

upload: ((file: Blob) => Promise<string>)

Upload a file to the server. Returns the URL of the uploaded file.

+
upload: ((file: Blob) => Promise<string>)

Upload a file to the server. Returns the URL of the uploaded file.

Type declaration

    • (file): Promise<string>
    • Parameters

      • file: Blob

        the file to upload

      Returns Promise<string>

      the URL of the uploaded file

      -
+
diff --git a/docs/reference/interfaces/StreamingClient.html b/docs/reference/interfaces/StreamingClient.html index a4fcb88..48ba2ce 100644 --- a/docs/reference/interfaces/StreamingClient.html +++ b/docs/reference/interfaces/StreamingClient.html @@ -1,9 +1,9 @@ StreamingClient | @fal-ai/client - v1.0.0

Interface StreamingClient

The streaming client interface.

-
interface StreamingClient {
    stream<Output, Input>(endpointId: string, options: StreamOptions<Input>): Promise<FalStream<Input, Output>>;
}

Methods

interface StreamingClient {
    stream<Output, Input>(endpointId: string, options: StreamOptions<Input>): Promise<FalStream<Input, Output>>;
}

Methods

Methods

  • Calls a fal app that supports streaming and provides a streaming-capable object as a result, that can be used to get partial results through either AsyncIterator or through an event listener.

    Type Parameters

    • Output = any
    • Input = Record<string, any>

    Parameters

    • endpointId: string

      the endpoint id, e.g. fal-ai/llavav15-13b.

    • options: StreamOptions<Input>

      the request options, including the input payload.

    Returns Promise<FalStream<Input, Output>>

    the FalStream instance.

    -
+
diff --git a/docs/reference/types/Metrics.html b/docs/reference/types/Metrics.html index 02926ed..7640ca1 100644 --- a/docs/reference/types/Metrics.html +++ b/docs/reference/types/Metrics.html @@ -1 +1 @@ -Metrics | @fal-ai/client - v1.0.0

Type Alias Metrics

Metrics: {
    inference_time: number | null;
}
+Metrics | @fal-ai/client - v1.0.0

Type Alias Metrics

Metrics: {
    inference_time: number | null;
}
diff --git a/docs/reference/types/QueueStatus.html b/docs/reference/types/QueueStatus.html index 3c4371c..36bb739 100644 --- a/docs/reference/types/QueueStatus.html +++ b/docs/reference/types/QueueStatus.html @@ -1 +1 @@ -QueueStatus | @fal-ai/client - v1.0.0
+QueueStatus | @fal-ai/client - v1.0.0
diff --git a/docs/reference/types/RequestLog.html b/docs/reference/types/RequestLog.html index c617714..1ad5440 100644 --- a/docs/reference/types/RequestLog.html +++ b/docs/reference/types/RequestLog.html @@ -1 +1 @@ -RequestLog | @fal-ai/client - v1.0.0

Type Alias RequestLog

RequestLog: {
    level:
        | "STDERR"
        | "STDOUT"
        | "ERROR"
        | "INFO"
        | "WARN"
        | "DEBUG";
    message: string;
    source: "USER";
    timestamp: string;
}
+RequestLog | @fal-ai/client - v1.0.0

Type Alias RequestLog

RequestLog: {
    level:
        | "STDERR"
        | "STDOUT"
        | "ERROR"
        | "INFO"
        | "WARN"
        | "DEBUG";
    message: string;
    source: "USER";
    timestamp: string;
}
diff --git a/docs/reference/types/RequestMiddleware.html b/docs/reference/types/RequestMiddleware.html index 53b1467..d324f13 100644 --- a/docs/reference/types/RequestMiddleware.html +++ b/docs/reference/types/RequestMiddleware.html @@ -1 +1 @@ -RequestMiddleware | @fal-ai/client - v1.0.0

Type Alias RequestMiddleware

RequestMiddleware: ((request: RequestConfig) => Promise<RequestConfig>)
+RequestMiddleware | @fal-ai/client - v1.0.0

Type Alias RequestMiddleware

RequestMiddleware: ((request: RequestConfig) => Promise<RequestConfig>)
diff --git a/docs/reference/types/ResponseHandler.html b/docs/reference/types/ResponseHandler.html index a007073..f64caa3 100644 --- a/docs/reference/types/ResponseHandler.html +++ b/docs/reference/types/ResponseHandler.html @@ -1 +1 @@ -ResponseHandler | @fal-ai/client - v1.0.0

Type Alias ResponseHandler<Output>

ResponseHandler<Output>: ((response: Response) => Promise<Output>)

Type Parameters

  • Output
+ResponseHandler | @fal-ai/client - v1.0.0

Type Alias ResponseHandler<Output>

ResponseHandler<Output>: ((response: Response) => Promise<Output>)

Type Parameters

  • Output
diff --git a/docs/reference/types/Result.html b/docs/reference/types/Result.html index 1b7eecd..7145a6a 100644 --- a/docs/reference/types/Result.html +++ b/docs/reference/types/Result.html @@ -1,3 +1,3 @@ Result | @fal-ai/client - v1.0.0

Type Alias Result<T>

Result<T>: {
    data: T;
    requestId: string;
}

Represents an API result, containing the data, the request ID and any other relevant information.

-

Type Parameters

  • T
+

Type Parameters

  • T
diff --git a/docs/reference/types/RunOptions.html b/docs/reference/types/RunOptions.html index acb72f4..2344053 100644 --- a/docs/reference/types/RunOptions.html +++ b/docs/reference/types/RunOptions.html @@ -3,4 +3,4 @@

Type Parameters

  • Input

Type declaration

  • Optional Readonlyinput?: Input

    The function input. It will be submitted either as query params or the body payload, depending on the method.

  • Optional Readonlymethod?:
        | "get"
        | "post"
        | "put"
        | "delete"
        | string

    The HTTP method, defaults to post;

    -
+
diff --git a/docs/reference/types/UrlOptions.html b/docs/reference/types/UrlOptions.html index 8683f61..dcb2988 100644 --- a/docs/reference/types/UrlOptions.html +++ b/docs/reference/types/UrlOptions.html @@ -3,4 +3,4 @@
  • Optional Readonlysubdomain?: string

    If true, the function will use the queue to run the function asynchronously and return the result in a separate call. This influences how the URL is built.

    -
  • +
    diff --git a/docs/reference/types/ValidationErrorInfo.html b/docs/reference/types/ValidationErrorInfo.html index bedcb42..2792527 100644 --- a/docs/reference/types/ValidationErrorInfo.html +++ b/docs/reference/types/ValidationErrorInfo.html @@ -1 +1 @@ -ValidationErrorInfo | @fal-ai/client - v1.0.0

    Type Alias ValidationErrorInfo

    ValidationErrorInfo: {
        loc: (string | number)[];
        msg: string;
        type: string;
    }
    +ValidationErrorInfo | @fal-ai/client - v1.0.0

    Type Alias ValidationErrorInfo

    ValidationErrorInfo: {
        loc: (string | number)[];
        msg: string;
        type: string;
    }
    diff --git a/docs/reference/types/WebHookResponse.html b/docs/reference/types/WebHookResponse.html index acb6115..c229ba5 100644 --- a/docs/reference/types/WebHookResponse.html +++ b/docs/reference/types/WebHookResponse.html @@ -10,4 +10,4 @@
  • payload: Payload

    The payload of the response, structure determined by the Payload type.

  • request_id: string

    The unique identifier for the request.

  • status: "ERROR"

    Indicates an unsuccessful response.

    -
  • +
    diff --git a/docs/reference/variables/fal.html b/docs/reference/variables/fal.html index 7b45315..bcc5d7a 100644 --- a/docs/reference/variables/fal.html +++ b/docs/reference/variables/fal.html @@ -1,3 +1,3 @@ fal | @fal-ai/client - v1.0.0

    Variable falConst

    fal: SingletonFalClient = ...

    Creates a singleton instance of the client. This is useful as a compatibility layer for existing code that uses the clients version prior to 1.0.0.

    -
    +