├── .commitlintrc.json ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .nxreleaserc.json ├── .prettierignore ├── .prettierrc ├── .secretlintrc.json ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── apps ├── .gitkeep ├── demo-express-app │ ├── .env.example │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── assets │ │ │ └── .gitkeep │ │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── webpack.config.js ├── demo-nextjs-app-router │ ├── .env.example │ ├── .eslintrc.json │ ├── app │ │ ├── api │ │ │ └── fal │ │ │ │ └── proxy │ │ │ │ └── route.ts │ │ ├── camera-turbo │ │ │ └── page.tsx │ │ ├── comfy │ │ │ ├── image-to-image │ │ │ │ └── page.tsx │ │ │ ├── image-to-video │ │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ └── text-to-image │ │ │ │ └── page.tsx │ │ ├── global.css │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── queue │ │ │ └── page.tsx │ │ ├── realtime │ │ │ └── page.tsx │ │ ├── streaming │ │ │ └── page.tsx │ │ └── whisper │ │ │ └── page.tsx │ ├── components │ │ ├── drawing.tsx │ │ └── drawingState.json │ ├── index.d.ts │ ├── jest.config.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── postcss.config.js │ ├── project.json │ ├── public │ │ ├── .gitkeep │ │ └── favicon.ico │ ├── tailwind.config.js │ ├── tsconfig.json │ └── tsconfig.spec.json └── demo-nextjs-page-router │ ├── .env.example │ ├── .eslintrc.json │ ├── index.d.ts │ ├── jest.config.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── pages │ ├── _app.tsx │ ├── api │ │ └── fal │ │ │ └── proxy.ts │ ├── index.module.css │ ├── index.tsx │ └── styles.css │ ├── postcss.config.js │ ├── project.json │ ├── public │ ├── .gitkeep │ └── placeholder@2x.jpg │ ├── specs │ └── index.spec.tsx │ ├── tailwind.config.js │ ├── tsconfig.json │ └── tsconfig.spec.json ├── babel.config.json ├── cspell-dictionary.txt ├── cspell.json ├── docs └── reference │ ├── .nojekyll │ ├── assets │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ ├── navigation.js │ ├── search.js │ ├── style.css │ └── typedoc-github-style.css │ ├── classes │ ├── ApiError.html │ ├── FalStream.html │ └── ValidationError.html │ ├── functions │ ├── createFalClient.html │ ├── isCompletedQueueStatus.html │ ├── isQueueStatus.html │ ├── parseEndpointId.html │ ├── withMiddleware.html │ └── withProxy.html │ ├── hierarchy.html │ ├── index.html │ ├── interfaces │ ├── CompletedQueueStatus.html │ ├── FalClient.html │ ├── InProgressQueueStatus.html │ ├── InQueueQueueStatus.html │ ├── QueueClient.html │ ├── RealtimeClient.html │ ├── StorageClient.html │ └── StreamingClient.html │ ├── types │ ├── Metrics.html │ ├── QueueStatus.html │ ├── RequestLog.html │ ├── RequestMiddleware.html │ ├── ResponseHandler.html │ ├── Result.html │ ├── RunOptions.html │ ├── UrlOptions.html │ ├── ValidationErrorInfo.html │ └── WebHookResponse.html │ └── variables │ └── fal.html ├── jest.config.ts ├── jest.preset.js ├── libs ├── .gitkeep ├── client │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ │ ├── auth.ts │ │ ├── client.spec.ts │ │ ├── client.ts │ │ ├── config.spec.ts │ │ ├── config.ts │ │ ├── index.ts │ │ ├── middleware.ts │ │ ├── queue.ts │ │ ├── realtime.ts │ │ ├── request.ts │ │ ├── response.ts │ │ ├── runtime.spec.ts │ │ ├── runtime.ts │ │ ├── storage.ts │ │ ├── streaming.ts │ │ ├── types │ │ │ ├── client.ts │ │ │ ├── common.ts │ │ │ └── endpoints.ts │ │ ├── utils.spec.ts │ │ └── utils.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── create-app │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json └── proxy │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ ├── express.ts │ ├── hono.ts │ ├── index.ts │ ├── nextjs.ts │ ├── remix.ts │ └── svelte.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── migrations.json ├── nx.json ├── package-lock.json ├── package.json ├── tools └── tsconfig.tools.json ├── tsconfig.base.json └── typedoc.json /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": {} 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.json"], 8 | "parser": "jsonc-eslint-parser", 9 | "rules": { 10 | "@nx/dependency-checks": "error" 11 | } 12 | }, 13 | { 14 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 15 | "rules": { 16 | "@nx/enforce-module-boundaries": [ 17 | "error", 18 | { 19 | "enforceBuildableLibDependency": true, 20 | "allow": [], 21 | "depConstraints": [ 22 | { 23 | "sourceTag": "*", 24 | "onlyDependOnLibsWithTags": ["*"] 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | }, 31 | { 32 | "files": ["*.ts", "*.tsx"], 33 | "extends": ["plugin:@nx/typescript"], 34 | "rules": {} 35 | }, 36 | { 37 | "files": ["*.js", "*.jsx"], 38 | "extends": ["plugin:@nx/javascript"], 39 | "rules": {} 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json linguist-generated 2 | docs/reference/** linguist-generated 3 | libs/client/src/types/endpoints.ts linguist-generated 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repo 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: "18.x" 20 | cache: "npm" 21 | - name: Install dependencies 22 | run: npm ci 23 | - name: Format check 24 | run: npx nx affected --target=format --base=remotes/origin/${{ github.base_ref }} 25 | - name: Lint 26 | run: npx nx affected --target=lint --base=remotes/origin/${{ github.base_ref }} 27 | - name: Test 28 | run: npx nx affected --target=test --base=remotes/origin/${{ github.base_ref }} 29 | - name: Build 30 | run: npx nx affected --target=build --prod --base=remotes/origin/${{ github.base_ref }} 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*-v[0-9]+.[0-9]+.[0-9]+*" 7 | workflow_dispatch: 8 | inputs: 9 | package: 10 | description: "Package to release" 11 | required: true 12 | type: choice 13 | options: 14 | - client 15 | - proxy 16 | version: 17 | description: "Version to release (e.g., 1.2.0, 1.3.0-alpha.0)" 18 | required: true 19 | type: string 20 | dry_run: 21 | description: "Dry run (will not publish to npm)" 22 | required: false 23 | type: boolean 24 | default: true 25 | 26 | jobs: 27 | build-and-publish: 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | with: 33 | fetch-depth: 0 34 | 35 | - name: Setup Node.js 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: "20" 39 | registry-url: "https://registry.npmjs.org" 40 | 41 | - name: Install dependencies 42 | run: npm ci 43 | 44 | - name: Extract package info 45 | id: tag-info 46 | run: | 47 | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then 48 | # Manual trigger 49 | PACKAGE_NAME="${{ inputs.package }}" 50 | VERSION="${{ inputs.version }}" 51 | else 52 | # Tag trigger 53 | TAG_NAME=${{ github.ref_name }} 54 | PACKAGE_NAME=$(echo $TAG_NAME | cut -d'-' -f1) 55 | VERSION=$(echo $TAG_NAME | cut -d'-' -f2- | sed 's/^v//') 56 | fi 57 | 58 | # Extract npm tag based on version qualifier 59 | if [[ $VERSION =~ .*-([a-zA-Z]+)\.[0-9]+$ ]]; then 60 | NPM_TAG="${BASH_REMATCH[1]}" 61 | else 62 | NPM_TAG="latest" 63 | fi 64 | 65 | echo "package=$PACKAGE_NAME" >> $GITHUB_OUTPUT 66 | echo "version=$VERSION" >> $GITHUB_OUTPUT 67 | echo "npm_tag=$NPM_TAG" >> $GITHUB_OUTPUT 68 | 69 | - name: Build package 70 | run: npx nx build ${{ steps.tag-info.outputs.package }} --prod 71 | 72 | - name: Verify package version 73 | run: | 74 | PACKAGE_JSON_VERSION=$(node -p "require('./dist/libs/${{ steps.tag-info.outputs.package }}/package.json').version") 75 | if [ "$PACKAGE_JSON_VERSION" != "${{ steps.tag-info.outputs.version }}" ]; then 76 | echo "Package version ($PACKAGE_JSON_VERSION) does not match tag version (${{ steps.tag-info.outputs.version }})" 77 | exit 1 78 | fi 79 | 80 | - name: Publish package 81 | run: | 82 | echo "Publishing ${{ steps.tag-info.outputs.package }}@${{ steps.tag-info.outputs.version }} with tag ${{ steps.tag-info.outputs.npm_tag }}" 83 | cd dist/libs/${{ steps.tag-info.outputs.package }} 84 | npm publish --access public --tag ${{ steps.tag-info.outputs.npm_tag }} ${{ inputs.dry_run && '--dry-run' || '' }} 85 | env: 86 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # Next.js 42 | .next 43 | *.local 44 | .vercel 45 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint:staged 5 | -------------------------------------------------------------------------------- /.nxreleaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "changelog": true, 3 | "npm": true, 4 | "github": true, 5 | "repositoryUrl": "https://github.com/fal-ai/fal-js", 6 | "branches": ["main"] 7 | } 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | /docs 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "plugins": ["prettier-plugin-organize-imports"] 5 | } 6 | -------------------------------------------------------------------------------- /.secretlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "id": "@secretlint/secretlint-rule-preset-recommend" 5 | }, 6 | { 7 | "id": "@secretlint/secretlint-rule-pattern", 8 | "options": { 9 | "patterns": [ 10 | { 11 | "name": "Fal API key", 12 | "pattern": "/\\b[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89abAB][0-9a-f]{3}-[0-9a-f]{12}:[0-9a-fA-F]{32}\\b/" 13 | } 14 | ] 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "ms-python.black-formatter" 4 | }, 5 | "python.formatting.provider": "none" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 https://fal.ai 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The fal.ai JS client 2 | 3 | ![@fal-ai/client npm package](https://img.shields.io/npm/v/@fal-ai/client?color=%237527D7&label=client&style=flat-square) 4 | ![@fal-ai/server-proxy npm package](https://img.shields.io/npm/v/@fal-ai/server-proxy?color=%237527D7&label=proxy&style=flat-square) 5 | ![Build](https://img.shields.io/github/actions/workflow/status/fal-ai/fal-js/build.yml?style=flat-square) 6 | ![License](https://img.shields.io/github/license/fal-ai/fal-js?style=flat-square) 7 | 8 | ## About the Project 9 | 10 | 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. 11 | 12 | ## Getting Started 13 | 14 | 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). 15 | 16 | ### Client Library 17 | 18 | This client library is crafted as a lightweight layer atop platform standards like `fetch`. This ensures a hassle-free integration into your existing codebase. Moreover, it addresses platform disparities, guaranteeing flawless operation across various JavaScript runtimes. 19 | 20 | > **Note:** 21 | > Ensure you've reviewed the [getting started guide](https://fal.ai/docs) to acquire your credentials, browser existing APIs, or create your custom functions. 22 | 23 | 1. Install the client library 24 | ```sh 25 | npm install --save @fal-ai/client 26 | ``` 27 | 2. Start by configuring your credentials: 28 | 29 | ```ts 30 | import { fal } from "@fal-ai/client"; 31 | 32 | fal.config({ 33 | // Can also be auto-configured using environment variables: 34 | credentials: "FAL_KEY", 35 | }); 36 | ``` 37 | 38 | 3. Retrieve your function id and execute it: 39 | ```ts 40 | const result = await fal.run("user/app-alias"); 41 | ``` 42 | 43 | The result's type is contingent upon your Python function's output. Types in Python are mapped to their corresponding types in JavaScript. 44 | 45 | See the available [model APIs](https://fal.ai/models) for more details. 46 | 47 | ### The fal client proxy 48 | 49 | 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. 50 | 51 | For example, if you are using Next.js, you can: 52 | 53 | 1. Instal the proxy library 54 | ```sh 55 | npm install --save @fal-ai/server-proxy 56 | ``` 57 | 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) 58 | ```ts 59 | export { handler as default } from "@fal-ai/server-proxy/nextjs"; 60 | ``` 61 | 3. Configure the client to use the proxy: 62 | ```ts 63 | import { fal } from "@fal-ai/client"; 64 | fal.config({ 65 | proxyUrl: "/api/fal/proxy", 66 | }); 67 | ``` 68 | 4. Make sure your server has `FAL_KEY` as environment variable with a valid API Key. That's it! Now your client calls will route through your server proxy, so your credentials are protected. 69 | 70 | See [libs/proxy](./libs/proxy/) for more details. 71 | 72 | ### The example Next.js app 73 | 74 | You can find a minimal Next.js + fal application examples in [apps/demo-nextjs-page-router/](https://github.com/fal-ai/fal-js/blob/main/apps/demo-nextjs-page-router). 75 | 76 | 1. Run `npm install` on the repository root. 77 | 2. Create a `.env.local` file and add your API Key as `FAL_KEY` environment variable (or export it any other way your prefer). 78 | 3. Run `npx nx serve demo-nextjs-page-router` to start the Next.js app (`demo-nextjs-app-router` is also available if you're interested in the app router version). 79 | 80 | Check our [Next.js integration docs](https://fal.ai/docs/integrations/nextjs) for more details. 81 | 82 | ## Roadmap 83 | 84 | See the [open feature requests](https://github.com/fal-ai/fal-js/labels/enhancement) for a list of proposed features and join the discussion. 85 | 86 | ## Contributing 87 | 88 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 89 | 90 | 1. Make sure you read our [Code of Conduct](https://github.com/fal-ai/fal-js/blob/main/CODE_OF_CONDUCT.md) 91 | 2. Fork the project and clone your fork 92 | 3. Setup the local environment with `npm install` 93 | 4. Create a feature branch (`git checkout -b feature/add-cool-thing`) or a bugfix branch (`git checkout -b fix/smash-that-bug`) 94 | 5. Commit the changes (`git commit -m 'feat(client): added a cool thing'`) - use [conventional commits](https://conventionalcommits.org) 95 | 6. Push to the branch (`git push --set-upstream origin feature/add-cool-thing`) 96 | 7. Open a Pull Request 97 | 98 | Check the [good first issue queue](https://github.com/fal-ai/fal-js/labels/good+first+issue), your contribution will be welcome! 99 | 100 | ## License 101 | 102 | Distributed under the MIT License. See [LICENSE](https://github.com/fal-ai/fal-js/blob/main/LICENSE) for more information. 103 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-ai/fal-js/4c15680afcf5196c917cf749bbb202eacb6a605a/apps/.gitkeep -------------------------------------------------------------------------------- /apps/demo-express-app/.env.example: -------------------------------------------------------------------------------- 1 | # Rename this file to .env.local and add your fal credentials 2 | # Visit https://fal.ai to get started 3 | FAL_KEY="FAL_KEY_ID:FAL_KEY_SECRET" 4 | -------------------------------------------------------------------------------- /apps/demo-express-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/demo-express-app/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: "demo-express-app", 4 | preset: "../../jest.preset.js", 5 | testEnvironment: "node", 6 | transform: { 7 | "^.+\\.[tj]s$": ["ts-jest", { tsconfig: "/tsconfig.spec.json" }], 8 | }, 9 | moduleFileExtensions: ["ts", "js", "html"], 10 | coverageDirectory: "../../coverage/apps/demo-express-app", 11 | }; 12 | -------------------------------------------------------------------------------- /apps/demo-express-app/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-express-app", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/demo-express-app/src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/webpack:webpack", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "target": "node", 13 | "compiler": "tsc", 14 | "outputPath": "dist/apps/demo-express-app", 15 | "main": "apps/demo-express-app/src/main.ts", 16 | "tsConfig": "apps/demo-express-app/tsconfig.app.json", 17 | "assets": ["apps/demo-express-app/src/assets"], 18 | "isolatedConfig": true, 19 | "webpackConfig": "apps/demo-express-app/webpack.config.js" 20 | }, 21 | "configurations": { 22 | "development": {}, 23 | "production": {} 24 | } 25 | }, 26 | "serve": { 27 | "executor": "@nx/js:node", 28 | "defaultConfiguration": "development", 29 | "options": { 30 | "buildTarget": "demo-express-app:build" 31 | }, 32 | "configurations": { 33 | "development": { 34 | "buildTarget": "demo-express-app:build:development" 35 | }, 36 | "production": { 37 | "buildTarget": "demo-express-app:build:production" 38 | } 39 | } 40 | }, 41 | "lint": { 42 | "executor": "@nx/linter:eslint", 43 | "outputs": ["{options.outputFile}"], 44 | "options": { 45 | "lintFilePatterns": ["apps/demo-express-app/**/*.ts"] 46 | } 47 | }, 48 | "test": { 49 | "executor": "@nx/jest:jest", 50 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 51 | "options": { 52 | "jestConfig": "apps/demo-express-app/jest.config.ts", 53 | "passWithNoTests": true 54 | }, 55 | "configurations": { 56 | "ci": { 57 | "ci": true, 58 | "codeCoverage": true 59 | } 60 | } 61 | } 62 | }, 63 | "tags": [] 64 | } 65 | -------------------------------------------------------------------------------- /apps/demo-express-app/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-ai/fal-js/4c15680afcf5196c917cf749bbb202eacb6a605a/apps/demo-express-app/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/demo-express-app/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is not a production server yet! 3 | * This is only a minimal backend to get started. 4 | */ 5 | 6 | import { fal } from "@fal-ai/client"; 7 | import * as falProxy from "@fal-ai/server-proxy/express"; 8 | import cors from "cors"; 9 | import { configDotenv } from "dotenv"; 10 | import express from "express"; 11 | import * as path from "path"; 12 | 13 | configDotenv({ path: "./env.local" }); 14 | 15 | const app = express(); 16 | 17 | // Middlewares 18 | app.use("/assets", express.static(path.join(__dirname, "assets"))); 19 | app.use(express.json()); 20 | 21 | // fal.ai client proxy 22 | app.all(falProxy.route, cors(), falProxy.handler); 23 | 24 | // Your API endpoints 25 | app.get("/api", (req, res) => { 26 | res.send({ message: "Welcome to demo-express-app!" }); 27 | }); 28 | 29 | app.get("/fal-on-server", async (req, res) => { 30 | const result = await fal.run("110602490-lcm", { 31 | input: { 32 | prompt: 33 | "a black cat with glowing eyes, cute, adorable, disney, pixar, highly detailed, 8k", 34 | }, 35 | }); 36 | res.send(result); 37 | }); 38 | 39 | const port = process.env.PORT || 3333; 40 | const server = app.listen(port, () => { 41 | console.log(`Listening at http://localhost:${port}/api`); 42 | }); 43 | server.on("error", console.error); 44 | -------------------------------------------------------------------------------- /apps/demo-express-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node", "express"] 7 | }, 8 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], 9 | "include": ["src/**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo-express-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "esModuleInterop": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/demo-express-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /apps/demo-express-app/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { composePlugins, withNx } = require("@nx/webpack"); 2 | 3 | // Nx plugins for webpack. 4 | module.exports = composePlugins(withNx(), (config) => { 5 | // Update the webpack config as needed here. 6 | // e.g. `config.plugins.push(new MyPlugin())` 7 | return config; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/.env.example: -------------------------------------------------------------------------------- 1 | # Rename this file to .env.local and add your fal credentials 2 | # Visit https://fal.ai to get started 3 | FAL_KEY="FAL_KEY_ID:FAL_KEY_SECRET" 4 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nx/react-typescript", 4 | "next", 5 | "next/core-web-vitals", 6 | "../../.eslintrc.json" 7 | ], 8 | "ignorePatterns": ["!**/*", ".next/**/*"], 9 | "overrides": [ 10 | { 11 | "files": ["*.*"], 12 | "rules": { 13 | "@next/next/no-html-link-for-pages": "off" 14 | } 15 | }, 16 | { 17 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 18 | "rules": { 19 | "@next/next/no-html-link-for-pages": [ 20 | "error", 21 | "apps/demo-nextjs-app-router/pages" 22 | ] 23 | } 24 | }, 25 | { 26 | "files": ["*.ts", "*.tsx"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "rules": {} 32 | }, 33 | { 34 | "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], 35 | "env": { 36 | "jest": true 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts: -------------------------------------------------------------------------------- 1 | import { route } from "@fal-ai/server-proxy/nextjs"; 2 | 3 | export const { GET, POST, PUT } = route; 4 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/app/comfy/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | 5 | export default function Index() { 6 | const router = useRouter(); // Use correct router 7 | return ( 8 |
9 |
10 |

11 | Serverless Comfy Workflow Examples powered by{" "} 12 | fal 13 |

14 |

15 | Learn how to use our fal-js to execute Comfy workflows. 16 |

17 |
18 | 24 | 30 | 36 |
37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./global.css"; 2 | 3 | export const metadata = { 4 | title: "Welcome to demo-nextjs-app-router", 5 | description: "Generated by create-nx-workspace", 6 | }; 7 | 8 | export default function RootLayout({ 9 | children, 10 | }: { 11 | children: React.ReactNode; 12 | }) { 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/app/queue/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { fal } from "@fal-ai/client"; 4 | import { useState } from "react"; 5 | 6 | fal.config({ 7 | proxyUrl: "/api/fal/proxy", 8 | }); 9 | 10 | type ErrorProps = { 11 | error: any; 12 | }; 13 | 14 | function Error(props: ErrorProps) { 15 | if (!props.error) { 16 | return null; 17 | } 18 | return ( 19 |
23 | Error {props.error.message} 24 |
25 | ); 26 | } 27 | 28 | const DEFAULT_ENDPOINT_ID = "fal-ai/fast-sdxl"; 29 | const DEFAULT_INPUT = `{ 30 | "prompt": "A beautiful sunset over the ocean" 31 | }`; 32 | 33 | export default function Home() { 34 | // Input state 35 | const [endpointId, setEndpointId] = useState(DEFAULT_ENDPOINT_ID); 36 | const [input, setInput] = useState(DEFAULT_INPUT); 37 | // Result state 38 | const [loading, setLoading] = useState(false); 39 | const [error, setError] = useState(null); 40 | const [result, setResult] = useState(null); 41 | const [logs, setLogs] = useState([]); 42 | const [elapsedTime, setElapsedTime] = useState(0); 43 | 44 | const reset = () => { 45 | setLoading(false); 46 | setError(null); 47 | setResult(null); 48 | setLogs([]); 49 | setElapsedTime(0); 50 | }; 51 | 52 | const run = async () => { 53 | reset(); 54 | setLoading(true); 55 | const start = Date.now(); 56 | try { 57 | const result = await fal.subscribe(endpointId, { 58 | input: JSON.parse(input), 59 | logs: true, 60 | // mode: "streaming", 61 | mode: "polling", 62 | pollInterval: 1000, 63 | onQueueUpdate(update) { 64 | console.log("queue update"); 65 | console.log(update); 66 | setElapsedTime(Date.now() - start); 67 | if ( 68 | update.status === "IN_PROGRESS" || 69 | update.status === "COMPLETED" 70 | ) { 71 | if (update.logs && update.logs.length > logs.length) { 72 | setLogs((update.logs || []).map((log) => log.message)); 73 | } 74 | } 75 | }, 76 | }); 77 | setResult(result); 78 | } catch (error: any) { 79 | setError(error); 80 | } finally { 81 | setLoading(false); 82 | setElapsedTime(Date.now() - start); 83 | } 84 | }; 85 | return ( 86 |
87 |
88 |

89 | fal 90 | queue 91 |

92 |
93 | 96 | setEndpointId(e.target.value)} 105 | /> 106 |
107 |
108 | 111 | 122 |
123 | 124 | 134 | 135 | 136 | 137 |
138 |
139 |

JSON Result

140 |

141 | {`Elapsed Time (seconds): ${(elapsedTime / 1000).toFixed(2)}`} 142 |

143 |
144 |               {result
145 |                 ? JSON.stringify(result, null, 2)
146 |                 : "// result pending..."}
147 |             
148 |
149 | 150 |
151 |

Logs

152 |
153 |               {logs.join("\n")}
154 |             
155 |
156 |
157 |
158 |
159 | ); 160 | } 161 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/app/realtime/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | /* eslint-disable @next/next/no-img-element */ 4 | import { createFalClient } from "@fal-ai/client"; 5 | import { ChangeEvent, useRef, useState } from "react"; 6 | import { DrawingCanvas } from "../../components/drawing"; 7 | 8 | const fal = createFalClient({ 9 | proxyUrl: "/api/fal/proxy", 10 | }); 11 | 12 | const PROMPT_EXPANDED = 13 | ", beautiful, colorful, highly detailed, best quality, uhd"; 14 | 15 | const PROMPT = "a moon in the night sky"; 16 | 17 | const defaults = { 18 | model_name: "runwayml/stable-diffusion-v1-5", 19 | image_size: "square", 20 | num_inference_steps: 4, 21 | seed: 6252023, 22 | }; 23 | 24 | export default function RealtimePage() { 25 | const [prompt, setPrompt] = useState(PROMPT); 26 | 27 | const currentDrawing = useRef(null); 28 | const outputCanvasRef = useRef(null); 29 | 30 | const { send } = fal.realtime.connect( 31 | "fal-ai/fast-lcm-diffusion/image-to-image", 32 | { 33 | connectionKey: "realtime-demo", 34 | throttleInterval: 128, 35 | onResult(result) { 36 | if (result.images && result.images[0] && result.images[0].content) { 37 | const canvas = outputCanvasRef.current; 38 | const context = canvas?.getContext("2d"); 39 | if (canvas && context) { 40 | const imageBytes: Uint8Array = result.images[0].content; 41 | const blob = new Blob([imageBytes], { type: "image/png" }); 42 | createImageBitmap(blob) 43 | .then((bitmap) => { 44 | context.drawImage(bitmap, 0, 0); 45 | }) 46 | .catch(console.error); 47 | } 48 | } 49 | }, 50 | }, 51 | ); 52 | 53 | const handlePromptChange = (e: ChangeEvent) => { 54 | setPrompt(e.target.value); 55 | if (currentDrawing.current) { 56 | send({ 57 | prompt: e.target.value.trim() + PROMPT_EXPANDED, 58 | image_bytes: currentDrawing.current, 59 | ...defaults, 60 | }); 61 | } 62 | }; 63 | 64 | return ( 65 |
66 |
67 |

68 | falrealtime 69 |

70 |
71 | 76 |
77 |
78 |
79 | { 81 | currentDrawing.current = imageData; 82 | send({ 83 | prompt: prompt + PROMPT_EXPANDED, 84 | image_bytes: imageData, 85 | ...defaults, 86 | }); 87 | }} 88 | /> 89 |
90 |
91 |
92 | 98 |
99 |
100 |
101 |
102 |
103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/app/streaming/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { fal } from "@fal-ai/client"; 4 | import { useState } from "react"; 5 | 6 | fal.config({ 7 | proxyUrl: "/api/fal/proxy", 8 | }); 9 | 10 | type ErrorProps = { 11 | error: any; 12 | }; 13 | 14 | function Error(props: ErrorProps) { 15 | if (!props.error) { 16 | return null; 17 | } 18 | return ( 19 |
23 | Error {props.error.message} 24 |
25 | ); 26 | } 27 | 28 | const DEFAULT_ENDPOINT_ID = "fal-ai/llavav15-13b"; 29 | const DEFAULT_INPUT = { 30 | prompt: "Do you know who drew this picture and what is the name of it?", 31 | image_url: "https://llava-vl.github.io/static/images/monalisa.jpg", 32 | max_new_tokens: 100, 33 | temperature: 0.2, 34 | top_p: 1, 35 | }; 36 | 37 | export default function StreamingTest() { 38 | // Input state 39 | const [endpointId, setEndpointId] = useState(DEFAULT_ENDPOINT_ID); 40 | const [input, setInput] = useState( 41 | JSON.stringify(DEFAULT_INPUT, null, 2), 42 | ); 43 | // Result state 44 | const [loading, setLoading] = useState(false); 45 | const [error, setError] = useState(null); 46 | const [events, setEvents] = useState([]); 47 | const [elapsedTime, setElapsedTime] = useState(0); 48 | 49 | const reset = () => { 50 | setLoading(false); 51 | setError(null); 52 | setEvents([]); 53 | setElapsedTime(0); 54 | }; 55 | 56 | const run = async () => { 57 | reset(); 58 | setLoading(true); 59 | const start = Date.now(); 60 | try { 61 | const stream = await fal.stream(endpointId, { 62 | input: JSON.parse(input), 63 | }); 64 | 65 | for await (const partial of stream) { 66 | setEvents((events) => [partial, ...events]); 67 | } 68 | 69 | const result = await stream.done(); 70 | setEvents((events) => [result, ...events]); 71 | } catch (error: any) { 72 | setError(error); 73 | } finally { 74 | setLoading(false); 75 | setElapsedTime(Date.now() - start); 76 | } 77 | }; 78 | return ( 79 |
80 |
81 |

82 | fal 83 | queue 84 |

85 |
86 | 89 | setEndpointId(e.target.value)} 98 | /> 99 |
100 |
101 | 104 | 115 |
116 | 117 | 127 | 128 | 129 | 130 |
131 |
132 |

JSON Result

133 |

134 | {`Elapsed Time (seconds): ${(elapsedTime / 1000).toFixed(2)}`} 135 |

136 |
137 | {events.map((event, index) => ( 138 |
142 |                   {JSON.stringify(event, null, 2)}
143 |                 
144 | ))} 145 |
146 |
147 |
148 |
149 |
150 | ); 151 | } 152 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/components/drawing.tsx: -------------------------------------------------------------------------------- 1 | import { type Excalidraw } from "@excalidraw/excalidraw"; 2 | import { ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types"; 3 | import { 4 | AppState, 5 | ExcalidrawImperativeAPI, 6 | } from "@excalidraw/excalidraw/types/types"; 7 | import { useCallback, useEffect, useState } from "react"; 8 | import initialDrawing from "./drawingState.json"; 9 | 10 | export type CanvasChangeEvent = { 11 | elements: readonly ExcalidrawElement[]; 12 | appState: AppState; 13 | imageData: Uint8Array; 14 | }; 15 | 16 | export type DrawingCanvasProps = { 17 | onCanvasChange: (event: CanvasChangeEvent) => void; 18 | }; 19 | 20 | export async function blobToBase64(blob: Blob): Promise { 21 | const reader = new FileReader(); 22 | reader.readAsDataURL(blob); 23 | return new Promise((resolve) => { 24 | reader.onloadend = () => { 25 | resolve(reader.result?.toString() || ""); 26 | }; 27 | }); 28 | } 29 | 30 | export async function blobToUint8Array(blob: Blob): Promise { 31 | const buffer = await blob.arrayBuffer(); 32 | return new Uint8Array(buffer); 33 | } 34 | 35 | export function DrawingCanvas({ onCanvasChange }: DrawingCanvasProps) { 36 | const [ExcalidrawComponent, setExcalidrawComponent] = useState< 37 | typeof Excalidraw | null 38 | >(null); 39 | const [excalidrawAPI, setExcalidrawAPI] = 40 | useState(null); 41 | const [sceneData, setSceneData] = useState(null); 42 | 43 | useEffect(() => { 44 | import("@excalidraw/excalidraw").then((comp) => 45 | setExcalidrawComponent(comp.Excalidraw), 46 | ); 47 | const onResize = () => { 48 | if (excalidrawAPI) { 49 | excalidrawAPI.refresh(); 50 | } 51 | }; 52 | window.addEventListener("resize", onResize); 53 | return () => { 54 | window.removeEventListener("resize", onResize); 55 | }; 56 | }, []); 57 | 58 | const handleCanvasChanges = useCallback( 59 | async (elements: readonly ExcalidrawElement[], appState: AppState) => { 60 | if (!excalidrawAPI || !elements || !elements.length) { 61 | return; 62 | } 63 | const { exportToBlob, convertToExcalidrawElements, serializeAsJSON } = 64 | await import("@excalidraw/excalidraw"); 65 | 66 | const [boundingBoxElement] = convertToExcalidrawElements([ 67 | { 68 | type: "rectangle", 69 | x: 0, 70 | y: 0, 71 | width: 512, 72 | height: 512, 73 | fillStyle: "solid", 74 | backgroundColor: "cyan", 75 | }, 76 | ]); 77 | 78 | const newSceneData = serializeAsJSON( 79 | elements, 80 | appState, 81 | excalidrawAPI.getFiles(), 82 | "local", 83 | ); 84 | if (newSceneData !== sceneData) { 85 | setSceneData(newSceneData); 86 | const blob = await exportToBlob({ 87 | elements: [boundingBoxElement, ...elements], 88 | appState: { 89 | ...appState, 90 | frameRendering: { 91 | ...(appState.frameRendering || {}), 92 | clip: false, 93 | }, 94 | }, 95 | files: excalidrawAPI.getFiles(), 96 | mimeType: "image/webp", 97 | quality: 0.5, 98 | exportPadding: 0, 99 | getDimensions: () => { 100 | return { width: 512, height: 512 }; 101 | }, 102 | }); 103 | const imageData = await blobToUint8Array(blob); 104 | onCanvasChange({ elements, appState, imageData }); 105 | } 106 | }, 107 | [excalidrawAPI, onCanvasChange, sceneData], 108 | ); 109 | 110 | return ( 111 |
112 | {ExcalidrawComponent && ( 113 | setExcalidrawAPI(api)} 115 | initialData={{ elements: initialDrawing as ExcalidrawElement[] }} 116 | onChange={handleCanvasChanges} 117 | /> 118 | )} 119 |
120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/components/drawingState.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "rectangle", 4 | "version": 240, 5 | "versionNonce": 21728473, 6 | "isDeleted": false, 7 | "id": "EnLu91BTRnzWtj7m-l4Id", 8 | "fillStyle": "solid", 9 | "strokeWidth": 2, 10 | "strokeStyle": "solid", 11 | "roughness": 1, 12 | "opacity": 100, 13 | "angle": 0, 14 | "x": -3.3853912353515625, 15 | "y": -2.3741912841796875, 16 | "strokeColor": "#1971c2", 17 | "backgroundColor": "#343a40", 18 | "width": 568.016487121582, 19 | "height": 582.1398010253906, 20 | "seed": 295965933, 21 | "groupIds": [], 22 | "frameId": null, 23 | "roundness": null, 24 | "boundElements": [], 25 | "updated": 1700904828477, 26 | "link": null, 27 | "locked": false 28 | }, 29 | { 30 | "type": "ellipse", 31 | "version": 3545, 32 | "versionNonce": 647409943, 33 | "isDeleted": false, 34 | "id": "F6oN3k42RqfCqlzJLGXXS", 35 | "fillStyle": "solid", 36 | "strokeWidth": 1, 37 | "strokeStyle": "solid", 38 | "roughness": 1, 39 | "opacity": 100, 40 | "angle": 0, 41 | "x": 345.65307998657227, 42 | "y": 81.02682495117188, 43 | "strokeColor": "#f08c00", 44 | "backgroundColor": "#ffec99", 45 | "width": 124.31249999999997, 46 | "height": 113.591796875, 47 | "seed": 23374002, 48 | "groupIds": [], 49 | "frameId": null, 50 | "roundness": { 51 | "type": 2 52 | }, 53 | "boundElements": [], 54 | "updated": 1700904844024, 55 | "link": null, 56 | "locked": false 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module "*.svg" { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: "demo-nextjs-app-router", 4 | preset: "../../jest.preset.js", 5 | transform: { 6 | "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "@nx/react/plugins/jest", 7 | "^.+\\.[tj]sx?$": ["babel-jest", { presets: ["@nx/next/babel"] }], 8 | }, 9 | moduleFileExtensions: ["ts", "tsx", "js", "jsx"], 10 | coverageDirectory: "../../coverage/apps/demo-nextjs-app-router", 11 | }; 12 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/next.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const { composePlugins, withNx } = require("@nx/next"); 5 | 6 | /** 7 | * @type {import('@nx/next/plugins/with-nx').WithNxOptions} 8 | **/ 9 | const nextConfig = { 10 | nx: { 11 | // Set this to true if you would like to use SVGR 12 | // See: https://github.com/gregberge/svgr 13 | svgr: false, 14 | }, 15 | }; 16 | 17 | const plugins = [ 18 | // Add more Next.js plugins to this list if needed. 19 | withNx, 20 | ]; 21 | 22 | module.exports = composePlugins(...plugins)(nextConfig); 23 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/postcss.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path"); 2 | 3 | // Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build 4 | // option from your application's configuration (i.e. project.json). 5 | // 6 | // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries 7 | 8 | module.exports = { 9 | plugins: { 10 | tailwindcss: { 11 | config: join(__dirname, "tailwind.config.js"), 12 | }, 13 | autoprefixer: {}, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-nextjs-app-router", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/demo-nextjs-app-router", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/next:build", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "outputPath": "dist/apps/demo-nextjs-app-router" 13 | }, 14 | "configurations": { 15 | "development": { 16 | "outputPath": "apps/demo-nextjs-app-router" 17 | }, 18 | "production": {} 19 | } 20 | }, 21 | "serve": { 22 | "executor": "@nx/next:server", 23 | "defaultConfiguration": "development", 24 | "options": { 25 | "buildTarget": "demo-nextjs-app-router:build", 26 | "dev": true 27 | }, 28 | "configurations": { 29 | "development": { 30 | "buildTarget": "demo-nextjs-app-router:build:development", 31 | "dev": true 32 | }, 33 | "production": { 34 | "buildTarget": "demo-nextjs-app-router:build:production", 35 | "dev": false 36 | } 37 | } 38 | }, 39 | "export": { 40 | "executor": "@nx/next:export", 41 | "options": { 42 | "buildTarget": "demo-nextjs-app-router:build:production" 43 | } 44 | }, 45 | "test": { 46 | "executor": "@nx/jest:jest", 47 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 48 | "options": { 49 | "jestConfig": "apps/demo-nextjs-app-router/jest.config.ts", 50 | "passWithNoTests": true 51 | }, 52 | "configurations": { 53 | "ci": { 54 | "ci": true, 55 | "codeCoverage": true 56 | } 57 | } 58 | }, 59 | "lint": { 60 | "executor": "@nx/linter:eslint", 61 | "outputs": ["{options.outputFile}"], 62 | "options": { 63 | "lintFilePatterns": ["apps/demo-nextjs-app-router/**/*.{ts,tsx,js,jsx}"] 64 | } 65 | } 66 | }, 67 | "tags": [] 68 | } 69 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-ai/fal-js/4c15680afcf5196c917cf749bbb202eacb6a605a/apps/demo-nextjs-app-router/public/.gitkeep -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-ai/fal-js/4c15680afcf5196c917cf749bbb202eacb6a605a/apps/demo-nextjs-app-router/public/favicon.ico -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { createGlobPatternsForDependencies } = require("@nx/react/tailwind"); 2 | const { join } = require("path"); 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | module.exports = { 6 | content: [ 7 | join( 8 | __dirname, 9 | "{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}", 10 | ), 11 | ...createGlobPatternsForDependencies(__dirname), 12 | ], 13 | darkMode: "class", 14 | theme: { 15 | extend: {}, 16 | }, 17 | plugins: [], 18 | }; 19 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "incremental": true, 14 | "plugins": [ 15 | { 16 | "name": "next" 17 | } 18 | ], 19 | "types": ["jest", "node"] 20 | }, 21 | "include": [ 22 | "**/*.ts", 23 | "**/*.tsx", 24 | "**/*.js", 25 | "**/*.jsx", 26 | "../../apps/demo-nextjs-app-router/.next/types/**/*.ts", 27 | "../../dist/apps/demo-nextjs-app-router/.next/types/**/*.ts", 28 | "next-env.d.ts" 29 | ], 30 | "exclude": [ 31 | "node_modules", 32 | "jest.config.ts", 33 | "src/**/*.spec.ts", 34 | "src/**/*.test.ts" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /apps/demo-nextjs-app-router/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"], 7 | "jsx": "react" 8 | }, 9 | "include": [ 10 | "jest.config.ts", 11 | "src/**/*.test.ts", 12 | "src/**/*.spec.ts", 13 | "src/**/*.test.tsx", 14 | "src/**/*.spec.tsx", 15 | "src/**/*.test.js", 16 | "src/**/*.spec.js", 17 | "src/**/*.test.jsx", 18 | "src/**/*.spec.jsx", 19 | "src/**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/.env.example: -------------------------------------------------------------------------------- 1 | # Rename this file to .env.local and add your fal credentials 2 | # Visit https://fal.ai to get started 3 | FAL_KEY="FAL_KEY_ID:FAL_KEY_SECRET" 4 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nx/react-typescript", 4 | "next", 5 | "next/core-web-vitals", 6 | "../../.eslintrc.json" 7 | ], 8 | "ignorePatterns": ["!**/*", ".next/**/*"], 9 | "overrides": [ 10 | { 11 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 12 | "rules": { 13 | "@next/next/no-html-link-for-pages": ["error", "apps/demo-app/pages"] 14 | } 15 | }, 16 | { 17 | "files": ["*.ts", "*.tsx"], 18 | "rules": {} 19 | }, 20 | { 21 | "files": ["*.js", "*.jsx"], 22 | "rules": {} 23 | } 24 | ], 25 | "rules": { 26 | "@next/next/no-html-link-for-pages": "off" 27 | }, 28 | "env": { 29 | "jest": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module "*.svg" { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: "demo-nextjs-page-router", 4 | preset: "../../jest.preset.js", 5 | transform: { 6 | "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "@nx/react/plugins/jest", 7 | "^.+\\.[tj]sx?$": ["babel-jest", { presets: ["@nx/next/babel"] }], 8 | }, 9 | moduleFileExtensions: ["ts", "tsx", "js", "jsx"], 10 | coverageDirectory: "../../coverage/apps/demo-nextjs-page-router", 11 | }; 12 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/next.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const { withNx } = require("@nx/next/plugins/with-nx"); 5 | 6 | /** 7 | * @type {import('@nx/next/plugins/with-nx').WithNxOptions} 8 | **/ 9 | const nextConfig = { 10 | nx: { 11 | // Set this to true if you would like to to use SVGR 12 | // See: https://github.com/gregberge/svgr 13 | svgr: false, 14 | }, 15 | }; 16 | 17 | module.exports = withNx(nextConfig); 18 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from "next/app"; 2 | import Head from "next/head"; 3 | import "./styles.css"; 4 | 5 | function CustomApp({ Component, pageProps }: AppProps) { 6 | return ( 7 | <> 8 | 9 | Welcome to demo-app! 10 | 11 |
12 | 13 |
14 | 15 | ); 16 | } 17 | 18 | export default CustomApp; 19 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/pages/api/fal/proxy.ts: -------------------------------------------------------------------------------- 1 | // @snippet:start("client.proxy.nextjs") 2 | export { handler as default } from "@fal-ai/server-proxy/nextjs"; 3 | // @snippet:end 4 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/pages/index.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | } 3 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/postcss.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path"); 2 | 3 | // Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build 4 | // option from your application's configuration (i.e. project.json). 5 | // 6 | // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries 7 | 8 | module.exports = { 9 | plugins: { 10 | tailwindcss: { 11 | config: join(__dirname, "tailwind.config.js"), 12 | }, 13 | autoprefixer: {}, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-nextjs-page-router", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/demo-nextjs-page-router", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/next:build", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "outputPath": "dist/apps/demo-nextjs-page-router" 13 | }, 14 | "configurations": { 15 | "development": { 16 | "outputPath": "apps/demo-nextjs-page-router" 17 | }, 18 | "production": {} 19 | } 20 | }, 21 | "serve": { 22 | "executor": "@nx/next:server", 23 | "defaultConfiguration": "development", 24 | "options": { 25 | "buildTarget": "demo-nextjs-page-router:build", 26 | "dev": true 27 | }, 28 | "configurations": { 29 | "development": { 30 | "buildTarget": "demo-nextjs-page-router:build:development", 31 | "dev": true 32 | }, 33 | "production": { 34 | "buildTarget": "demo-nextjs-page-router:build:production", 35 | "dev": false 36 | } 37 | } 38 | }, 39 | "export": { 40 | "executor": "@nx/next:export", 41 | "options": { 42 | "buildTarget": "demo-nextjs-page-router:build:production" 43 | } 44 | }, 45 | "test": { 46 | "executor": "@nx/jest:jest", 47 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 48 | "options": { 49 | "jestConfig": "apps/demo-nextjs-page-router/jest.config.ts", 50 | "passWithNoTests": true 51 | } 52 | }, 53 | "lint": { 54 | "executor": "@nx/linter:eslint", 55 | "outputs": ["{options.outputFile}"], 56 | "options": { 57 | "lintFilePatterns": [ 58 | "apps/demo-nextjs-page-router/**/*.{ts,tsx,js,jsx}" 59 | ] 60 | } 61 | } 62 | }, 63 | "tags": [] 64 | } 65 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-ai/fal-js/4c15680afcf5196c917cf749bbb202eacb6a605a/apps/demo-nextjs-page-router/public/.gitkeep -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/public/placeholder@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-ai/fal-js/4c15680afcf5196c917cf749bbb202eacb6a605a/apps/demo-nextjs-page-router/public/placeholder@2x.jpg -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/specs/index.spec.tsx: -------------------------------------------------------------------------------- 1 | import "@inrupt/jest-jsdom-polyfills"; 2 | 3 | import { render } from "@testing-library/react"; 4 | 5 | import Index from "../pages/index"; 6 | 7 | describe("Index", () => { 8 | xit("should render successfully", () => { 9 | const { baseElement } = render(); 10 | expect(baseElement).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { createGlobPatternsForDependencies } = require("@nx/react/tailwind"); 2 | const { join } = require("path"); 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | module.exports = { 6 | content: [ 7 | join( 8 | __dirname, 9 | "{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html}", 10 | ), 11 | ...createGlobPatternsForDependencies(__dirname), 12 | ], 13 | darkMode: "class", 14 | theme: { 15 | extend: {}, 16 | }, 17 | plugins: [], 18 | }; 19 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "incremental": true, 14 | "types": ["jest", "node"] 15 | }, 16 | "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"], 17 | "exclude": ["node_modules", "jest.config.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /apps/demo-nextjs-page-router/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"], 7 | "jsx": "react" 8 | }, 9 | "include": [ 10 | "jest.config.ts", 11 | "**/*.test.ts", 12 | "**/*.spec.ts", 13 | "**/*.test.tsx", 14 | "**/*.spec.tsx", 15 | "**/*.test.js", 16 | "**/*.spec.js", 17 | "**/*.test.jsx", 18 | "**/*.spec.jsx", 19 | "**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "babelrcRoots": ["*"] 3 | } 4 | -------------------------------------------------------------------------------- /cspell-dictionary.txt: -------------------------------------------------------------------------------- 1 | quickstart 2 | runtimes 3 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "version": "0.2", 4 | "language": "en", 5 | "languageId": "markdown", 6 | "dictionaryDefinitions": [ 7 | { 8 | "name": "project-words", 9 | "path": "./cspell-dictionary.txt", 10 | "addWords": true 11 | } 12 | ], 13 | "dictionaries": ["en_US", "project-words", "typescript", "python"] 14 | } 15 | -------------------------------------------------------------------------------- /docs/reference/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/reference/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-code-background: #FFFFFF; 3 | --dark-code-background: #1E1E1E; 4 | } 5 | 6 | @media (prefers-color-scheme: light) { :root { 7 | --code-background: var(--light-code-background); 8 | } } 9 | 10 | @media (prefers-color-scheme: dark) { :root { 11 | --code-background: var(--dark-code-background); 12 | } } 13 | 14 | :root[data-theme='light'] { 15 | --code-background: var(--light-code-background); 16 | } 17 | 18 | :root[data-theme='dark'] { 19 | --code-background: var(--dark-code-background); 20 | } 21 | 22 | pre, code { background: var(--code-background); } 23 | -------------------------------------------------------------------------------- /docs/reference/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE43UXU/CMBQG4P/Sa+IUBZU7QzCQSESJemG8OG5n0NC1sz3zI8b/bpzKaNe13PK+58l2Ovr4yQjfiY3YRcknWivNeqwEWrMRSwUYgyb5Tw7WVAjWYxsuMzY66p999bbTlyCWpBGK9vg2Cs3fg+AZEFey4yGcQsgaq6IUSJjdVFjhkoAq04BcEuocUjSJr2jD/cHQfsmx4CjJq23TEDGTC61WGo2JPZy3GabrYtx1ayG0rgVeeicPMbcIgngRkuxKCFuS0rAKWVYjTP18mFyugpjVCXFzJM3Tnb3TR4km+fvZmTw8Pz0a9N1duyf3K3SfVku5xZcKDV2plYs0yZ7GnGeZwDfQ2EE1hbhoSiUNTkFmAnXbs+I9tEqQB6mEez7t2Upelz/3SGvJTRIz7rToMJokZjgX2kzmysU8lZj6gM9TpTb/+3RFJ45pOYhGeAXN4VmgSXIQ9uTx7lCqEQg9d2VeybReTeJUbGx4soNxE77NG9PfDNJRcT+oBG1wIrNScUmzzEc5lQD2xmnt+981lt2IUAut3j+6lDpsAU/fB9L83BQIAAA=" -------------------------------------------------------------------------------- /docs/reference/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAE72cWY/bRhKA/wv9Kstin+S8LYIEMbDBZuPd7INgDDgSZ0xEQyqkZK9h+L8vmodYxaoWm5rJPo0tdh2s/qr6IJvforr60kR322/RH0W5j+6kWEVl9pxHd9FjdohW0bk+RHfR56wusodD3rx7zA7rT6dnd2l3yJomb6K7KPq+GhQYdVGwq/PslP+UHX44FHl5uih7PJe7U1GVzbtJi6uKhTYXzVRnUZ7y+jHb5c27MHXxRoye/nnOz/m8rjdDO6BxFR2z2nkCvfKYqfPscCqeQyyBpjcaa05VnT2F2Bpb3myqzrPnonwKMja2DTcnNioZw3guQyLYtrrRRHN+aHZ18RAUPtD2RQEMjt4iQ0ZrOWbO/f3p6zEMCmdpfWk/Y3Dde8YWgi/F6dMvxX5/yL9kdc7UAdwgtL44qV/r6r9fPRrba9dryia1sR5L3m/5n+e8OTG+uig078j1665ei/w1hW9mok7d5MvkP1258hdKcHkmSjgznosgdW8uTfm7gN75jJ2y07kJMzY0fYExR/CHJSaRwO2GhwLyr2qJdUbqZhfqvDkfwnr10vRmY7us3OWHIGOXpouMgRT4rR9H/VmAW4Qnwq4qy3wXqvLN2NyX08hRMFCI0ebfjsWPdV3VF6O9rnfDhavu61hA75tTfd6d5pS9wS155y9+ecc3hDVvaCaD52w8VPuvMxZck7dikQUQ/N+zQ7HP3PDC98Hk+qt0BaczqEemzgKIjYjVGLbHIj/s2za0f1jrWGCxdZhCT/npp6XmicxSD0LAZC0zfJ6a/duieVuUn/K6OOX7GzxgsWXt9y1fap3OeZpjVTb5z1m5P+Q1maCgqy+f71B187Md7CBf6D90ixh/nUcNwpeG5+OhyvZhCt9cGvN3gn1cPkX3Wuyn6VdqGxbt/fTc8qnOyuaxqp/fl8dzYCzfEKH/Wwiw5bkVC1Yw8Zov/D9lhw94gTbk6eXKqxR7rC2ozI+uefpyV5WPxVOAqa7VjVbycn+sivL0fj9nCbW80ZozMGOma3Kj/urYriHnbIzNwu2g4a8qZ01c3cK4rn1flfmc/r7NjRayh6o+zZkYGi2wgScoTfFUZrMdfml1s526W1TPIwwb3hi5bdZ8LXfvT3mdnar645zFe9T8fpldNEb2e2/XRknUZMG+gHcLi1M5t5E19dQ/fWmXgJNlczfXAFcWbQJNpk7vy8dqoplp8dKJkU/l3OSIc9ZT1p6bp2VG151EsOFh6PXYP1S7hfY7ideyvzzq66Whpx5M4fpP/vBzVf0xzGkn/kyuhm6EHrO6yX+kY/C4HTppEaq4aLj0GtWi6+FKf6iej4f8lO/ntHMNF27oon2tyyLkfLhe2sKWMoOWgBWM88LD5T47ZbO6132rawZm+KcDnNfY/BDns0g64Fz+YzKd6q1eLry4I7Cm2c4YPfIEqkDLnmtG1m3Tt3GAsZnOec5PnyrSM7zRS9sX2mynZh/w9OqqYSyw3PoUjn/XBx6O8cJL4ZhomoMDeOTbMTo/7KvnrCiDDK1h8zmTM9315zmvv4ZZHZq+0OIxO30KM9i3XG7P8wTu79V0rjJeeKVnboOmwIdtziNv7jYNfNJ+zcx6bDxnbm46lX/OSd7yNoemL7TYVOd6F3ibl7YvtOkeiTSn7PkYZhY2X255CuMv+akudtPi1P/6UgyhmjkGB0e8A9ZjXuflLr9Hb5d47ayJwFWzTKDAovJ92U7OuKkcWATSVuFbsP6nwB6tc4+SGJevvRh0f6yawtWvJS4QyZe60s/J7gt2M9rnBpIKe3SwxKVunXIPd+SCnEJyr+1W1/tLnUJSr+1S9xx7qUtI6hVcwmn7a1091XnDLvGQc0zD10len+L5/OV89247PC0134u8gvG5pPW6cFPeLnJsLnWvuHZT9i5wbi6BZ7j56xybS2OvYzdl8nXHYDJf3VABHi7eTwlMZa/euUxmHV+YyH7jV/N4iennyUQwyPoo9HIHrtcRvw83lJFlbl2vItccu6GILHHteg2ZA/Yvc+t6BfG7dUMB4d36uIqKcp//N7r7Fn3O68ZNTu8isZbrNFp1bwO58wCdv6toVz0/d4+K9tXu3P7zY9/s99w9mnaNu9bvNtFqu1kpud6o+OPH1XYQbi+0Pww6xl9awThabWNOMCaCMRIU0WorOEFBBAUSlNFqKzlBSQQlElTRaqs4QUUEFRLU0WqrOUFNBDUSNNFqazhBQwQNErTRams5QUsELRJMotU24QQTIpggwTRabdOVtOtUCiSYEsEUA+B4iDcrIdbGYtGYwhNP6Gnx4flhAMIExY6LmGUophDFmKLYsRGzHMUUpBiTFCv/PVOYYkxT7BiJWRBjClSMiYodJzELY0yhijFVsWMlNiuRrKW1WJiCFWOyYsdLzEIZU7hiTFfsmIlZMGMKWIwJEy1hLJyCEiYwYcIxI9jSJihhYlKj2iLF4imYMoUJE44ZwZc4SpjAhAnHjGDxFJQwgQkTjhmhVlKttUmwMCVMYMKE8UIiKGECEyYcM0KzXUUJE5gw4ZgRbL0UlDCBCROp/54pYQITJh0zgmVbUsIkJky2hLFsS0qYxIRJ4Y22pITJyUgovdGWzGCICZMtYSnrNiVMYsKk9hZASQmTmDDpmJFsSkpKmMSESceMZFNSUsIkJkwmfrcpYRITJh0zks1nSQmTmDDlHyUVJUxhwpRjRrLFQFHCFCZMCW9iKEqYwoSpdrLFT5ooYWoy33LMSHasUsyUCxOmHDOSLQaKEqYwYaoljM1nRQlTmDDVEsbms6KEKUyYcsxINqsUJUxhwpRjRrGJoShhChOmHTOKTQxNCdOYMO2YUYKrJJoSpjFh2jGjWDw1JUxjwrRjRrGEaUqYxoRp5Zu1agqYnkzqHTKKn9Yz83oMmHbIKJZOTQHTGDBtvbVAU8A0Bkw7ZBSLtqaAaQyYbgFj0dYUMI0BMy1gLNqGAmYwYMYho1m0DQXMYMCMQ0azaBsKmMGAGYeMZsu2oYAZDJhxzGgWbUMJM5gwo73zVkMJM5Olo/FCYpjVIybMOGY0m1SGEmYwYSbx1gJDCTOYMOOY0WxWGUqYwYRZ/yBpKWEWE2ZbwtiUtJQwiwmzLWH8ipsSZjFhtiWMzSpLCbOYMNsSxmaVpYRZTJj1T8MsJcxiwmy7O8GmpKWE2ckGhWPGsClpmT0KTJh1zBg2JS0lzGLCrGPGsClpKWEWE5b4CUsoYQkmLHHMGDarEkpYgglLHDOGTYyEEpZgwhLHjGHZTihhCSYsccwYlu2EEpZgwhLHjEm4YpBQwhJMWOKvYQklLMGEJS1hbGIklLBksg3mmLEs2wmzE4YJS1LvmiyhhCWYsNQxY9nESClhKSYsdcxYwc3VU0pYiglLHTNWssKUsBQTljpmLLtKSClhKSYsdcxYzQpTwlJMWOqYsSzbKSUsxYSl/s2KlBKWYsLSdpfVspuelLAUE5Ym/q6ihKWTzdbU31XMfut0w3Xj7avuGhYHv/Xysbe7umtT+cm266YFjd/M2zAbr5vJzutG+rchN8ze62ay+bpR3n7rrk3lJ/uvG39N665N5SdbsBvj7fru2lR+sgu7sd7e765N5ScbsZvkSv8zW7GbyV7sJr3S/8xubP9b+/Tpc16f8v377inUdnt5Pe5bdN8/moo3wwOzb1G8ie6+fV9Fser+St3/Tbq/qr+ubffXmO6v7X+3ffuk/3/SXv8+Ptpqf7083XLXnOv9ubXRJwV8Umm4juFk2qhJJ6MmK8M0HYu8++jAqEbIUY1QYWrcObKiP0eGbg7o0iJIV3ccHbijgTt9X8iwuxu+8gGUgWCLeIGS7gnpqMgCr9KeIFeM+n+E9eNueJLavtQ3vNIAjCQQ17DwDUduRy1SAcACAzd8VAREToDIBWsZDxoDTcAf0SeRSPukC7zJ9vtuj9lh1x84BCkOQhakqzsAA7IIuGdsmIr2uCuIeAoingSpGM4vuxcqgCJAmQpLxUdcEwDvm1B5GlTY96FahpOY4G5AMVBhndO+EJD3n+QA/thRkwy7r6f85FMGskyGuTV9z3lUZkDXJ2H0FOWxfyHLVwYMqDU6VOcZ9aAGOmzoTbb++JwCZCVhjBfNXMHTgBATxnvhi5oG0JqwgtWfZwC9CfIvCSO/PVsKBkAAlw4bFbq3y0CkAeypHUaYsOhcToWAewL1LQkjYTgYBoILfLJhgbm8tgZcAcFJ9HBnYYS3Z4hBnIFDOgxH9/Y8KE7AFxWWuJcPNQAtwA0VNiFsj9Dy5V+DwcyERbk7MAVCDLLAhsWlP+QFdAA3bFho+g+agsCAGhuuAZxzAPkA3EnDin+rjJkrgFSIw7DzFBsFCoUO0zR+iRWoAfQsUsIM2SBKIowd+FInCDcgKO0nuGkyJGsY471mrFgBxbpXbMIQ7fUdKlQDDIDMhtXaXtEz+DAnwAPoi8Ooxa+fgiACVanog5gOQQzLqUH3p+GDWqCzwaxDhpLTnWAHtwuUiH5RawJZPuMMBf0QKs8UUw2SyoT1J10UK5AHOqxaDGcMAVlg9pSEkUBrRAyUxP04LvpNBdkzkfRMpGZgY4k1Qh2oJ6kcNA77HpuwXLt8xRl0DAjpEh20Skkwi5RhtYSuKwC3ffh0WA6Ab0aDvgbQLdNCb0+BDtChPem0MfSApIoDe248Iw5uD0TcBt5g/71d4AwgIA5WMnzNetQD53/LtJwqJkRwuySsXoBzvSBEINRJGEmX772RhZcEo5wMdGqya6kA4CashA0fDAR+gDoow6rypJpIkBkqLO/P9YGr6+B+bFh8P1++iUO3K4FbIoyiibai/RITiDfQqMPo/pI/fKqqP+rL53fA3YKEM2HrLfeRcc+UBG5vhSs7dl8zB3oAlXHA7PDjKjoWx/xQlHl0t/34/fv/ANWXeYfhYQAA"; -------------------------------------------------------------------------------- /docs/reference/functions/isCompletedQueueStatus.html: -------------------------------------------------------------------------------- 1 | isCompletedQueueStatus | @fal-ai/client

Function isCompletedQueueStatus

2 | -------------------------------------------------------------------------------- /docs/reference/functions/isQueueStatus.html: -------------------------------------------------------------------------------- 1 | isQueueStatus | @fal-ai/client

Function isQueueStatus

2 | -------------------------------------------------------------------------------- /docs/reference/functions/parseEndpointId.html: -------------------------------------------------------------------------------- 1 | parseEndpointId | @fal-ai/client

Function parseEndpointId

  • Parameters

    • id: string

    Returns EndpointId

2 | -------------------------------------------------------------------------------- /docs/reference/functions/withProxy.html: -------------------------------------------------------------------------------- 1 | withProxy | @fal-ai/client

Function withProxy

2 | -------------------------------------------------------------------------------- /docs/reference/hierarchy.html: -------------------------------------------------------------------------------- 1 | @fal-ai/client

@fal-ai/client

Class Hierarchy

2 | -------------------------------------------------------------------------------- /docs/reference/types/Metrics.html: -------------------------------------------------------------------------------- 1 | Metrics | @fal-ai/client

Type Alias Metrics

Metrics: {
    inference_time: number | null;
}
2 | -------------------------------------------------------------------------------- /docs/reference/types/QueueStatus.html: -------------------------------------------------------------------------------- 1 | QueueStatus | @fal-ai/client
2 | -------------------------------------------------------------------------------- /docs/reference/types/RequestMiddleware.html: -------------------------------------------------------------------------------- 1 | RequestMiddleware | @fal-ai/client

Type Alias RequestMiddleware

RequestMiddleware: ((request: RequestConfig) => Promise<RequestConfig>)
2 | -------------------------------------------------------------------------------- /docs/reference/types/ResponseHandler.html: -------------------------------------------------------------------------------- 1 | ResponseHandler | @fal-ai/client

Type Alias ResponseHandler<Output>

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

Type Parameters

  • Output
2 | -------------------------------------------------------------------------------- /docs/reference/types/Result.html: -------------------------------------------------------------------------------- 1 | Result | @fal-ai/client

Type Alias Result<T>

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

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

3 |

Type Parameters

  • T
4 | -------------------------------------------------------------------------------- /docs/reference/types/ValidationErrorInfo.html: -------------------------------------------------------------------------------- 1 | ValidationErrorInfo | @fal-ai/client

Type Alias ValidationErrorInfo

ValidationErrorInfo: {
    loc: (string | number)[];
    msg: string;
    type: string;
}
2 | -------------------------------------------------------------------------------- /docs/reference/variables/fal.html: -------------------------------------------------------------------------------- 1 | fal | @fal-ai/client

Variable falConst

fal: SingletonFalClient = ...

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

3 |
4 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from "@nx/jest"; 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require("@nx/jest/preset").default; 2 | 3 | module.exports = { 4 | ...nxPreset, 5 | /* TODO: Update to latest Jest snapshotFormat 6 | * By default Nx has kept the older style of Jest Snapshot formats 7 | * to prevent breaking of any existing tests with snapshots. 8 | * It's recommend you update to the latest format. 9 | * You can do this by removing snapshotFormat property 10 | * and running tests with --update-snapshot flag. 11 | * Example: "nx affected --targets=test --update-snapshot" 12 | * More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format 13 | */ 14 | snapshotFormat: { escapeString: true, printBasicPrototype: true }, 15 | }; 16 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-ai/fal-js/4c15680afcf5196c917cf749bbb202eacb6a605a/libs/.gitkeep -------------------------------------------------------------------------------- /libs/client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /libs/client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/client/README.md: -------------------------------------------------------------------------------- 1 | # fal.ai JavaScript/TypeScript client library 2 | 3 | ![@fal-ai/client npm package](https://img.shields.io/npm/v/@fal-ai/client?color=%237527D7&label=%40fal-ai%2Fclient&style=flat-square) 4 | 5 | ## Introduction 6 | 7 | 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. 8 | 9 | ## Getting started 10 | 11 | Before diving into the client-specific features, ensure you've set up your credentials: 12 | 13 | ```ts 14 | import { fal } from "@fal-ai/client"; 15 | 16 | fal.config({ 17 | // Can also be auto-configured using environment variables: 18 | // Either a single FAL_KEY or a combination of FAL_KEY_ID and FAL_KEY_SECRET 19 | credentials: "FAL_KEY_ID:FAL_KEY_SECRET", 20 | }); 21 | ``` 22 | 23 | **Note:** Ensure you've reviewed the [fal.ai getting started guide](https://fal.ai/docs) to acquire your credentials and register your functions. Also, make sure your credentials are always protected. See the [../proxy](../proxy) package for a secure way to use the client in client-side applications. 24 | 25 | ## Running functions with `fal.run` 26 | 27 | The `fal.run` method is the simplest way to execute a function. It returns a promise that resolves to the function's result: 28 | 29 | ```ts 30 | const result = await fal.run("my-function-id", { 31 | input: { foo: "bar" }, 32 | }); 33 | ``` 34 | 35 | ## Long-running functions with `fal.subscribe` 36 | 37 | The `fal.subscribe` method offers a powerful way to rely on the [queue system](https://www.fal.ai/docs/function-endpoints/queue) to execute long-running functions. It returns the result once it's done like any other async function, so your don't have to deal with queue status updates yourself. However, it does support queue events, in case you want to listen and react to them: 38 | 39 | ```ts 40 | const result = await fal.subscribe("my-function-id", { 41 | input: { foo: "bar" }, 42 | onQueueUpdate(update) { 43 | if (update.status === "IN_QUEUE") { 44 | console.log(`Your position in the queue is ${update.position}`); 45 | } 46 | }, 47 | }); 48 | ``` 49 | 50 | ## More features 51 | 52 | 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. 53 | -------------------------------------------------------------------------------- /libs/client/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: "client", 4 | preset: "../../jest.preset.js", 5 | globals: {}, 6 | testEnvironment: "node", 7 | transform: { 8 | "^.+\\.[tj]sx?$": [ 9 | "ts-jest", 10 | { 11 | tsconfig: "/tsconfig.spec.json", 12 | }, 13 | ], 14 | }, 15 | moduleFileExtensions: ["ts", "tsx", "js", "jsx"], 16 | coverageDirectory: "../../coverage/libs/client", 17 | }; 18 | -------------------------------------------------------------------------------- /libs/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fal-ai/client", 3 | "description": "The fal.ai client for JavaScript and TypeScript", 4 | "version": "1.5.0", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/fal-ai/fal-js.git", 9 | "directory": "libs/client" 10 | }, 11 | "keywords": [ 12 | "fal", 13 | "client", 14 | "ai", 15 | "ml", 16 | "typescript" 17 | ], 18 | "exports": { 19 | ".": "./src/index.js", 20 | "./endpoints": "./src/types/endpoints.js" 21 | }, 22 | "typesVersions": { 23 | "*": { 24 | "endpoints": [ 25 | "src/types/endpoints.d.ts" 26 | ] 27 | } 28 | }, 29 | "main": "./src/index.js", 30 | "types": "./src/index.d.ts", 31 | "dependencies": { 32 | "@msgpack/msgpack": "^3.0.0-beta2", 33 | "eventsource-parser": "^1.1.2", 34 | "robot3": "^0.4.1" 35 | }, 36 | "engines": { 37 | "node": ">=18.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /libs/client/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libs/client/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/libs/client", 12 | "tsConfig": "libs/client/tsconfig.lib.json", 13 | "packageJson": "libs/client/package.json", 14 | "main": "libs/client/src/index.ts", 15 | "assets": ["LICENSE", "CODE_OF_CONDUCT.md", "libs/client/README.md"] 16 | } 17 | }, 18 | "lint": { 19 | "executor": "@nx/linter:eslint", 20 | "outputs": ["{options.outputFile}"], 21 | "options": { 22 | "lintFilePatterns": ["libs/client/**/*.ts"] 23 | } 24 | }, 25 | "test": { 26 | "executor": "@nx/jest:jest", 27 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 28 | "options": { 29 | "jestConfig": "libs/client/jest.config.ts", 30 | "passWithNoTests": true 31 | }, 32 | "configurations": { 33 | "ci": { 34 | "ci": true, 35 | "codeCoverage": true 36 | } 37 | } 38 | }, 39 | "release": { 40 | "executor": "@theunderscorer/nx-semantic-release:semantic-release" 41 | } 42 | }, 43 | "tags": [] 44 | } 45 | -------------------------------------------------------------------------------- /libs/client/src/auth.ts: -------------------------------------------------------------------------------- 1 | import { getRestApiUrl, RequiredConfig } from "./config"; 2 | import { dispatchRequest } from "./request"; 3 | import { parseEndpointId } from "./utils"; 4 | 5 | export const TOKEN_EXPIRATION_SECONDS = 120; 6 | 7 | /** 8 | * Get a token to connect to the realtime endpoint. 9 | */ 10 | export async function getTemporaryAuthToken( 11 | app: string, 12 | config: RequiredConfig, 13 | ): Promise { 14 | const appId = parseEndpointId(app); 15 | const token: string | object = await dispatchRequest({ 16 | method: "POST", 17 | targetUrl: `${getRestApiUrl()}/tokens/`, 18 | config, 19 | input: { 20 | allowed_apps: [appId.alias], 21 | token_expiration: TOKEN_EXPIRATION_SECONDS, 22 | }, 23 | }); 24 | // keep this in case the response was wrapped (old versions of the proxy do that) 25 | // should be safe to remove in the future 26 | if (typeof token !== "string" && token["detail"]) { 27 | return token["detail"]; 28 | } 29 | return token; 30 | } 31 | -------------------------------------------------------------------------------- /libs/client/src/client.spec.ts: -------------------------------------------------------------------------------- 1 | import { buildUrl } from "./request"; 2 | 3 | describe("The function test suite", () => { 4 | it("should build the URL with a function username/app-alias", () => { 5 | const alias = "fal-ai/text-to-image"; 6 | const url = buildUrl(alias); 7 | expect(url).toMatch(`fal.run/${alias}`); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /libs/client/src/client.ts: -------------------------------------------------------------------------------- 1 | import { Config, createConfig } from "./config"; 2 | import { createQueueClient, QueueClient, QueueSubscribeOptions } from "./queue"; 3 | import { createRealtimeClient, RealtimeClient } from "./realtime"; 4 | import { buildUrl, dispatchRequest } from "./request"; 5 | import { resultResponseHandler } from "./response"; 6 | import { createStorageClient, StorageClient } from "./storage"; 7 | import { createStreamingClient, StreamingClient } from "./streaming"; 8 | import { EndpointType, InputType, OutputType } from "./types/client"; 9 | import { Result, RunOptions } from "./types/common"; 10 | 11 | /** 12 | * The main client type, it provides access to simple API model usage, 13 | * as well as access to the `queue` and `storage` APIs. 14 | * 15 | * @see createFalClient 16 | */ 17 | export interface FalClient { 18 | /** 19 | * The queue client to interact with the queue API. 20 | */ 21 | readonly queue: QueueClient; 22 | 23 | /** 24 | * The realtime client to interact with the realtime API 25 | * and receive updates in real-time. 26 | * @see #RealtimeClient 27 | * @see #RealtimeClient.connect 28 | */ 29 | readonly realtime: RealtimeClient; 30 | 31 | /** 32 | * The storage client to interact with the storage API. 33 | */ 34 | readonly storage: StorageClient; 35 | 36 | /** 37 | * The streaming client to interact with the streaming API. 38 | * @see #stream 39 | */ 40 | readonly streaming: StreamingClient; 41 | 42 | /** 43 | * Runs a fal endpoint identified by its `endpointId`. 44 | * 45 | * @param endpointId The endpoint id, e.g. `fal-ai/fast-sdxl`. 46 | * @param options The request options, including the input payload. 47 | * @returns A promise that resolves to the result of the request once it's completed. 48 | * 49 | * @note 50 | * We **do not recommend** this use for most use cases as it will block the client 51 | * until the response is received. Moreover, if the connection is closed before 52 | * the response is received, the request will be lost. Instead, we recommend 53 | * using the `subscribe` method for most use cases. 54 | */ 55 | run( 56 | endpointId: Id, 57 | options: RunOptions>, 58 | ): Promise>>; 59 | 60 | /** 61 | * Subscribes to updates for a specific request in the queue. 62 | * 63 | * @param endpointId - The ID of the API endpoint. 64 | * @param options - Options to configure how the request is run and how updates are received. 65 | * @returns A promise that resolves to the result of the request once it's completed. 66 | */ 67 | subscribe( 68 | endpointId: Id, 69 | options: RunOptions> & QueueSubscribeOptions, 70 | ): Promise>>; 71 | 72 | /** 73 | * Calls a fal app that supports streaming and provides a streaming-capable 74 | * object as a result, that can be used to get partial results through either 75 | * `AsyncIterator` or through an event listener. 76 | * 77 | * @param endpointId the endpoint id, e.g. `fal-ai/llavav15-13b`. 78 | * @param options the request options, including the input payload. 79 | * @returns the `FalStream` instance. 80 | */ 81 | stream: StreamingClient["stream"]; 82 | } 83 | 84 | /** 85 | * Creates a new reference of the `FalClient`. 86 | * @param userConfig Optional configuration to override the default settings. 87 | * @returns a new instance of the `FalClient`. 88 | */ 89 | export function createFalClient(userConfig: Config = {}): FalClient { 90 | const config = createConfig(userConfig); 91 | const storage = createStorageClient({ config }); 92 | const queue = createQueueClient({ config, storage }); 93 | const streaming = createStreamingClient({ config, storage }); 94 | const realtime = createRealtimeClient({ config }); 95 | return { 96 | queue, 97 | realtime, 98 | storage, 99 | streaming, 100 | stream: streaming.stream, 101 | async run( 102 | endpointId: Id, 103 | options: RunOptions> = {}, 104 | ): Promise>> { 105 | const input = options.input 106 | ? await storage.transformInput(options.input) 107 | : undefined; 108 | return dispatchRequest, Result>>({ 109 | method: options.method, 110 | targetUrl: buildUrl(endpointId, options), 111 | input: input as InputType, 112 | config: { 113 | ...config, 114 | responseHandler: resultResponseHandler, 115 | }, 116 | options: { 117 | signal: options.abortSignal, 118 | }, 119 | }); 120 | }, 121 | subscribe: async (endpointId, options) => { 122 | const { request_id: requestId } = await queue.submit(endpointId, options); 123 | if (options.onEnqueue) { 124 | options.onEnqueue(requestId); 125 | } 126 | await queue.subscribeToStatus(endpointId, { requestId, ...options }); 127 | return queue.result(endpointId, { requestId }); 128 | }, 129 | }; 130 | } 131 | -------------------------------------------------------------------------------- /libs/client/src/config.spec.ts: -------------------------------------------------------------------------------- 1 | import { createConfig } from "./config"; 2 | 3 | describe("The config test suite", () => { 4 | it("should set the config variables accordingly", () => { 5 | const newConfig = { 6 | credentials: "key-id:key-secret", 7 | }; 8 | const currentConfig = createConfig(newConfig); 9 | expect(currentConfig.credentials).toEqual(newConfig.credentials); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /libs/client/src/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | withMiddleware, 3 | withProxy, 4 | type RequestMiddleware, 5 | } from "./middleware"; 6 | import type { ResponseHandler } from "./response"; 7 | import { defaultResponseHandler } from "./response"; 8 | import { isBrowser } from "./runtime"; 9 | 10 | export type CredentialsResolver = () => string | undefined; 11 | 12 | type FetchType = typeof fetch; 13 | 14 | export function resolveDefaultFetch(): FetchType { 15 | if (typeof fetch === "undefined") { 16 | throw new Error( 17 | "Your environment does not support fetch. Please provide your own fetch implementation.", 18 | ); 19 | } 20 | return fetch; 21 | } 22 | 23 | export type Config = { 24 | /** 25 | * The credentials to use for the fal client. When using the 26 | * client in the browser, it's recommended to use a proxy server to avoid 27 | * exposing the credentials in the client's environment. 28 | * 29 | * By default it tries to use the `FAL_KEY` environment variable, when 30 | * `process.env` is defined. 31 | * 32 | * @see https://fal.ai/docs/model-endpoints/server-side 33 | * @see #suppressLocalCredentialsWarning 34 | */ 35 | credentials?: undefined | string | CredentialsResolver; 36 | /** 37 | * Suppresses the warning when the fal credentials are exposed in the 38 | * browser's environment. Make sure you understand the security implications 39 | * before enabling this option. 40 | */ 41 | suppressLocalCredentialsWarning?: boolean; 42 | /** 43 | * The URL of the proxy server to use for the client requests. The proxy 44 | * server should forward the requests to the fal api. 45 | */ 46 | proxyUrl?: string; 47 | /** 48 | * The request middleware to use for the client requests. By default it 49 | * doesn't apply any middleware. 50 | */ 51 | requestMiddleware?: RequestMiddleware; 52 | /** 53 | * The response handler to use for the client requests. By default it uses 54 | * a built-in response handler that returns the JSON response. 55 | */ 56 | responseHandler?: ResponseHandler; 57 | /** 58 | * The fetch implementation to use for the client requests. By default it uses 59 | * the global `fetch` function. 60 | */ 61 | fetch?: FetchType; 62 | }; 63 | 64 | export type RequiredConfig = Required; 65 | 66 | /** 67 | * Checks if the required FAL environment variables are set. 68 | * 69 | * @returns `true` if the required environment variables are set, 70 | * `false` otherwise. 71 | */ 72 | function hasEnvVariables(): boolean { 73 | return ( 74 | typeof process !== "undefined" && 75 | process.env && 76 | (typeof process.env.FAL_KEY !== "undefined" || 77 | (typeof process.env.FAL_KEY_ID !== "undefined" && 78 | typeof process.env.FAL_KEY_SECRET !== "undefined")) 79 | ); 80 | } 81 | 82 | export const credentialsFromEnv: CredentialsResolver = () => { 83 | if (!hasEnvVariables()) { 84 | return undefined; 85 | } 86 | 87 | if (typeof process.env.FAL_KEY !== "undefined") { 88 | return process.env.FAL_KEY; 89 | } 90 | 91 | return process.env.FAL_KEY_ID 92 | ? `${process.env.FAL_KEY_ID}:${process.env.FAL_KEY_SECRET}` 93 | : undefined; 94 | }; 95 | 96 | const DEFAULT_CONFIG: Partial = { 97 | credentials: credentialsFromEnv, 98 | suppressLocalCredentialsWarning: false, 99 | requestMiddleware: (request) => Promise.resolve(request), 100 | responseHandler: defaultResponseHandler, 101 | }; 102 | 103 | /** 104 | * Configures the fal client. 105 | * 106 | * @param config the new configuration. 107 | */ 108 | export function createConfig(config: Config): RequiredConfig { 109 | let configuration = { 110 | ...DEFAULT_CONFIG, 111 | ...config, 112 | fetch: config.fetch ?? resolveDefaultFetch(), 113 | } as RequiredConfig; 114 | if (config.proxyUrl) { 115 | configuration = { 116 | ...configuration, 117 | requestMiddleware: withMiddleware( 118 | configuration.requestMiddleware, 119 | withProxy({ targetUrl: config.proxyUrl }), 120 | ), 121 | }; 122 | } 123 | const { credentials: resolveCredentials, suppressLocalCredentialsWarning } = 124 | configuration; 125 | const credentials = 126 | typeof resolveCredentials === "function" 127 | ? resolveCredentials() 128 | : resolveCredentials; 129 | if (isBrowser() && credentials && !suppressLocalCredentialsWarning) { 130 | console.warn( 131 | "The fal credentials are exposed in the browser's environment. " + 132 | "That's not recommended for production use cases.", 133 | ); 134 | } 135 | return configuration; 136 | } 137 | 138 | /** 139 | * @returns the URL of the fal REST api endpoint. 140 | */ 141 | export function getRestApiUrl(): string { 142 | return "https://rest.alpha.fal.ai"; 143 | } 144 | -------------------------------------------------------------------------------- /libs/client/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createFalClient, type FalClient } from "./client"; 2 | import { Config } from "./config"; 3 | import { StreamOptions } from "./streaming"; 4 | import { EndpointType, InputType } from "./types/client"; 5 | import { RunOptions } from "./types/common"; 6 | 7 | export { createFalClient, type FalClient } from "./client"; 8 | export { withMiddleware, withProxy } from "./middleware"; 9 | export type { RequestMiddleware } from "./middleware"; 10 | export type { QueueClient } from "./queue"; 11 | export type { RealtimeClient } from "./realtime"; 12 | export { ApiError, ValidationError } from "./response"; 13 | export type { ResponseHandler } from "./response"; 14 | export type { StorageClient } from "./storage"; 15 | export type { FalStream, StreamingClient } from "./streaming"; 16 | export * from "./types/common"; 17 | export type { 18 | QueueStatus, 19 | ValidationErrorInfo, 20 | WebHookResponse, 21 | } from "./types/common"; 22 | export { parseEndpointId } from "./utils"; 23 | 24 | type SingletonFalClient = { 25 | config(config: Config): void; 26 | } & FalClient; 27 | 28 | /** 29 | * Creates a singleton instance of the client. This is useful as a compatibility 30 | * layer for existing code that uses the clients version prior to 1.0.0. 31 | */ 32 | export const fal: SingletonFalClient = (function createSingletonFalClient() { 33 | let currentInstance: FalClient = createFalClient(); 34 | return { 35 | config(config: Config) { 36 | currentInstance = createFalClient(config); 37 | }, 38 | get queue() { 39 | return currentInstance.queue; 40 | }, 41 | get realtime() { 42 | return currentInstance.realtime; 43 | }, 44 | get storage() { 45 | return currentInstance.storage; 46 | }, 47 | get streaming() { 48 | return currentInstance.streaming; 49 | }, 50 | run(id: Id, options: RunOptions>) { 51 | return currentInstance.run(id, options); 52 | }, 53 | subscribe( 54 | endpointId: Id, 55 | options: RunOptions>, 56 | ) { 57 | return currentInstance.subscribe(endpointId, options); 58 | }, 59 | stream( 60 | endpointId: Id, 61 | options: StreamOptions>, 62 | ) { 63 | return currentInstance.stream(endpointId, options); 64 | }, 65 | } satisfies SingletonFalClient; 66 | })(); 67 | -------------------------------------------------------------------------------- /libs/client/src/middleware.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A request configuration object. 3 | * 4 | * **Note:** This is a simplified version of the `RequestConfig` type from the 5 | * `fetch` API. It contains only the properties that are relevant for the 6 | * fal client. It also works around the fact that the `fetch` API `Request` 7 | * does not support mutability, its clone method has critical limitations 8 | * to our use case. 9 | */ 10 | export type RequestConfig = { 11 | url: string; 12 | method: string; 13 | headers?: Record; 14 | }; 15 | 16 | export type RequestMiddleware = ( 17 | request: RequestConfig, 18 | ) => Promise; 19 | 20 | /** 21 | * Setup a execution chain of middleware functions. 22 | * 23 | * @param middlewares one or more middleware functions. 24 | * @returns a middleware function that executes the given middlewares in order. 25 | */ 26 | export function withMiddleware( 27 | ...middlewares: RequestMiddleware[] 28 | ): RequestMiddleware { 29 | const isDefined = (middleware: RequestMiddleware): boolean => 30 | typeof middleware === "function"; 31 | 32 | return async (config: RequestConfig) => { 33 | let currentConfig = { ...config }; 34 | for (const middleware of middlewares.filter(isDefined)) { 35 | currentConfig = await middleware(currentConfig); 36 | } 37 | return currentConfig; 38 | }; 39 | } 40 | 41 | export type RequestProxyConfig = { 42 | targetUrl: string; 43 | }; 44 | 45 | export const TARGET_URL_HEADER = "x-fal-target-url"; 46 | 47 | export function withProxy(config: RequestProxyConfig): RequestMiddleware { 48 | const passthrough = (requestConfig: RequestConfig) => 49 | Promise.resolve(requestConfig); 50 | // when running on the server, we don't need to proxy the request 51 | if (typeof window === "undefined") { 52 | return passthrough; 53 | } 54 | // if x-fal-target-url is already set, we skip it 55 | return (requestConfig) => 56 | requestConfig.headers && TARGET_URL_HEADER in requestConfig 57 | ? passthrough(requestConfig) 58 | : Promise.resolve({ 59 | ...requestConfig, 60 | url: config.targetUrl, 61 | headers: { 62 | ...(requestConfig.headers || {}), 63 | [TARGET_URL_HEADER]: requestConfig.url, 64 | }, 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /libs/client/src/request.ts: -------------------------------------------------------------------------------- 1 | import { RequiredConfig } from "./config"; 2 | import { ResponseHandler } from "./response"; 3 | import { getUserAgent, isBrowser } from "./runtime"; 4 | import { RunOptions, UrlOptions } from "./types/common"; 5 | import { ensureEndpointIdFormat, isValidUrl } from "./utils"; 6 | 7 | const isCloudflareWorkers = 8 | typeof navigator !== "undefined" && 9 | navigator?.userAgent === "Cloudflare-Workers"; 10 | 11 | type RequestOptions = { 12 | responseHandler?: ResponseHandler; 13 | }; 14 | 15 | type RequestParams = { 16 | method?: string; 17 | targetUrl: string; 18 | input?: Input; 19 | config: RequiredConfig; 20 | options?: RequestOptions & RequestInit; 21 | headers?: Record; 22 | }; 23 | 24 | export async function dispatchRequest( 25 | params: RequestParams, 26 | ): Promise { 27 | const { targetUrl, input, config, options = {} } = params; 28 | const { 29 | credentials: credentialsValue, 30 | requestMiddleware, 31 | responseHandler, 32 | fetch, 33 | } = config; 34 | const userAgent = isBrowser() ? {} : { "User-Agent": getUserAgent() }; 35 | const credentials = 36 | typeof credentialsValue === "function" 37 | ? credentialsValue() 38 | : credentialsValue; 39 | 40 | const { method, url, headers } = await requestMiddleware({ 41 | method: (params.method ?? options.method ?? "post").toUpperCase(), 42 | url: targetUrl, 43 | headers: params.headers, 44 | }); 45 | const authHeader = credentials ? { Authorization: `Key ${credentials}` } : {}; 46 | const requestHeaders = { 47 | ...authHeader, 48 | Accept: "application/json", 49 | "Content-Type": "application/json", 50 | ...userAgent, 51 | ...(headers ?? {}), 52 | } as HeadersInit; 53 | 54 | const { responseHandler: customResponseHandler, ...requestInit } = options; 55 | const response = await fetch(url, { 56 | ...requestInit, 57 | method, 58 | headers: { 59 | ...requestHeaders, 60 | ...(requestInit.headers ?? {}), 61 | }, 62 | ...(!isCloudflareWorkers && { mode: "cors" }), 63 | signal: options.signal, 64 | body: 65 | method.toLowerCase() !== "get" && input 66 | ? JSON.stringify(input) 67 | : undefined, 68 | }); 69 | const handleResponse = customResponseHandler ?? responseHandler; 70 | return await handleResponse(response); 71 | } 72 | 73 | /** 74 | * Builds the final url to run the function based on its `id` or alias and 75 | * a the options from `RunOptions`. 76 | * 77 | * @private 78 | * @param id the function id or alias 79 | * @param options the run options 80 | * @returns the final url to run the function 81 | */ 82 | export function buildUrl( 83 | id: string, 84 | options: RunOptions & UrlOptions = {}, 85 | ): string { 86 | const method = (options.method ?? "post").toLowerCase(); 87 | const path = (options.path ?? "").replace(/^\//, "").replace(/\/{2,}/, "/"); 88 | const input = options.input; 89 | const params = { 90 | ...(options.query || {}), 91 | ...(method === "get" ? input : {}), 92 | }; 93 | 94 | const queryParams = 95 | Object.keys(params).length > 0 96 | ? `?${new URLSearchParams(params).toString()}` 97 | : ""; 98 | 99 | // if a fal url is passed, just use it 100 | if (isValidUrl(id)) { 101 | const url = id.endsWith("/") ? id : `${id}/`; 102 | return `${url}${path}${queryParams}`; 103 | } 104 | 105 | const appId = ensureEndpointIdFormat(id); 106 | const subdomain = options.subdomain ? `${options.subdomain}.` : ""; 107 | const url = `https://${subdomain}fal.run/${appId}/${path}`; 108 | return `${url.replace(/\/$/, "")}${queryParams}`; 109 | } 110 | -------------------------------------------------------------------------------- /libs/client/src/response.ts: -------------------------------------------------------------------------------- 1 | import { RequiredConfig } from "./config"; 2 | import { Result, ValidationErrorInfo } from "./types/common"; 3 | 4 | export type ResponseHandler = (response: Response) => Promise; 5 | 6 | const REQUEST_ID_HEADER = "x-fal-request-id"; 7 | 8 | export type ResponseHandlerCreator = ( 9 | config: RequiredConfig, 10 | ) => ResponseHandler; 11 | 12 | type ApiErrorArgs = { 13 | message: string; 14 | status: number; 15 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 16 | body?: any; 17 | }; 18 | 19 | export class ApiError extends Error { 20 | public readonly status: number; 21 | public readonly body: Body; 22 | constructor({ message, status, body }: ApiErrorArgs) { 23 | super(message); 24 | this.name = "ApiError"; 25 | this.status = status; 26 | this.body = body; 27 | } 28 | } 29 | 30 | type ValidationErrorBody = { 31 | detail: ValidationErrorInfo[]; 32 | }; 33 | 34 | export class ValidationError extends ApiError { 35 | constructor(args: ApiErrorArgs) { 36 | super(args); 37 | this.name = "ValidationError"; 38 | } 39 | 40 | get fieldErrors(): ValidationErrorInfo[] { 41 | // NOTE: this is a hack to support both FastAPI/Pydantic errors 42 | // and some custom 422 errors that might not be in the Pydantic format. 43 | if (typeof this.body.detail === "string") { 44 | return [ 45 | { 46 | loc: ["body"], 47 | msg: this.body.detail, 48 | type: "value_error", 49 | }, 50 | ]; 51 | } 52 | return this.body.detail || []; 53 | } 54 | 55 | getFieldErrors(field: string): ValidationErrorInfo[] { 56 | return this.fieldErrors.filter( 57 | (error) => error.loc[error.loc.length - 1] === field, 58 | ); 59 | } 60 | } 61 | 62 | export async function defaultResponseHandler( 63 | response: Response, 64 | ): Promise { 65 | const { status, statusText } = response; 66 | const contentType = response.headers.get("Content-Type") ?? ""; 67 | if (!response.ok) { 68 | if (contentType.includes("application/json")) { 69 | const body = await response.json(); 70 | const ErrorType = status === 422 ? ValidationError : ApiError; 71 | throw new ErrorType({ 72 | message: body.message || statusText, 73 | status, 74 | body, 75 | }); 76 | } 77 | throw new ApiError({ message: `HTTP ${status}: ${statusText}`, status }); 78 | } 79 | if (contentType.includes("application/json")) { 80 | return response.json() as Promise; 81 | } 82 | if (contentType.includes("text/html")) { 83 | return response.text() as Promise; 84 | } 85 | if (contentType.includes("application/octet-stream")) { 86 | return response.arrayBuffer() as Promise; 87 | } 88 | // TODO convert to either number or bool automatically 89 | return response.text() as Promise; 90 | } 91 | 92 | export async function resultResponseHandler( 93 | response: Response, 94 | ): Promise> { 95 | const data = await defaultResponseHandler(response); 96 | return { 97 | data, 98 | requestId: response.headers.get(REQUEST_ID_HEADER) || "", 99 | } satisfies Result; 100 | } 101 | -------------------------------------------------------------------------------- /libs/client/src/runtime.spec.ts: -------------------------------------------------------------------------------- 1 | import { getUserAgent, isBrowser } from "./runtime"; 2 | 3 | describe("the runtime test suite", () => { 4 | it("should return false when calling isBrowser() on a test", () => { 5 | expect(isBrowser()).toBe(false); 6 | }); 7 | 8 | it("should return true when calling isBrowser() and window is present", () => { 9 | global.window = { 10 | document: {}, 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 12 | } as any; 13 | expect(isBrowser()).toBe(true); 14 | }); 15 | 16 | it("should create the correct user agent identifier", () => { 17 | expect(getUserAgent()).toMatch(/@fal-ai\/client/); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /libs/client/src/runtime.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | export function isBrowser(): boolean { 4 | return ( 5 | typeof window !== "undefined" && typeof window.document !== "undefined" 6 | ); 7 | } 8 | 9 | let memoizedUserAgent: string | null = null; 10 | 11 | export function getUserAgent(): string { 12 | if (memoizedUserAgent !== null) { 13 | return memoizedUserAgent; 14 | } 15 | const packageInfo = require("../package.json"); 16 | memoizedUserAgent = `${packageInfo.name}/${packageInfo.version}`; 17 | return memoizedUserAgent; 18 | } 19 | -------------------------------------------------------------------------------- /libs/client/src/types/client.ts: -------------------------------------------------------------------------------- 1 | import { EndpointTypeMap } from "./endpoints"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/ban-types 4 | export type EndpointType = keyof EndpointTypeMap | (string & {}); 5 | 6 | // Get input type based on endpoint ID 7 | export type InputType = T extends keyof EndpointTypeMap 8 | ? EndpointTypeMap[T]["input"] 9 | : Record; 10 | 11 | // Get output type based on endpoint ID 12 | export type OutputType = T extends keyof EndpointTypeMap 13 | ? EndpointTypeMap[T]["output"] 14 | : any; 15 | -------------------------------------------------------------------------------- /libs/client/src/types/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents an API result, containing the data, 3 | * the request ID and any other relevant information. 4 | */ 5 | export type Result = { 6 | data: T; 7 | requestId: string; 8 | }; 9 | 10 | /** 11 | * The function input and other configuration when running 12 | * the function, such as the HTTP method to use. 13 | */ 14 | export type RunOptions = { 15 | /** 16 | * The function input. It will be submitted either as query params 17 | * or the body payload, depending on the `method`. 18 | */ 19 | readonly input?: Input; 20 | 21 | /** 22 | * The HTTP method, defaults to `post`; 23 | */ 24 | readonly method?: "get" | "post" | "put" | "delete" | string; 25 | 26 | /** 27 | * The abort signal to cancel the request. 28 | */ 29 | readonly abortSignal?: AbortSignal; 30 | }; 31 | 32 | export type UrlOptions = { 33 | /** 34 | * If `true`, the function will use the queue to run the function 35 | * asynchronously and return the result in a separate call. This 36 | * influences how the URL is built. 37 | */ 38 | readonly subdomain?: string; 39 | 40 | /** 41 | * The query parameters to include in the URL. 42 | */ 43 | readonly query?: Record; 44 | 45 | /** 46 | * The path to append to the function URL. 47 | */ 48 | path?: string; 49 | }; 50 | 51 | export type RequestLog = { 52 | message: string; 53 | level: "STDERR" | "STDOUT" | "ERROR" | "INFO" | "WARN" | "DEBUG"; 54 | source: "USER"; 55 | timestamp: string; // Using string to represent date-time format, but you could also use 'Date' type if you're going to construct Date objects. 56 | }; 57 | 58 | export type Metrics = { 59 | inference_time: number | null; 60 | }; 61 | 62 | interface BaseQueueStatus { 63 | status: "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED"; 64 | request_id: string; 65 | response_url: string; 66 | status_url: string; 67 | cancel_url: string; 68 | } 69 | 70 | export interface InQueueQueueStatus extends BaseQueueStatus { 71 | status: "IN_QUEUE"; 72 | queue_position: number; 73 | } 74 | 75 | export interface InProgressQueueStatus extends BaseQueueStatus { 76 | status: "IN_PROGRESS"; 77 | logs: RequestLog[]; 78 | } 79 | 80 | export interface CompletedQueueStatus extends BaseQueueStatus { 81 | status: "COMPLETED"; 82 | logs: RequestLog[]; 83 | metrics?: Metrics; 84 | } 85 | 86 | export type QueueStatus = 87 | | InProgressQueueStatus 88 | | CompletedQueueStatus 89 | | InQueueQueueStatus; 90 | 91 | export function isQueueStatus(obj: any): obj is QueueStatus { 92 | return obj && obj.status && obj.response_url; 93 | } 94 | 95 | export function isCompletedQueueStatus(obj: any): obj is CompletedQueueStatus { 96 | return isQueueStatus(obj) && obj.status === "COMPLETED"; 97 | } 98 | 99 | export type ValidationErrorInfo = { 100 | msg: string; 101 | loc: Array; 102 | type: string; 103 | }; 104 | 105 | /** 106 | * Represents the response from a WebHook request. 107 | * This is a union type that varies based on the `status` property. 108 | * 109 | * @template Payload - The type of the payload in the response. It defaults to `any`, 110 | * allowing for flexibility in specifying the structure of the payload. 111 | */ 112 | export type WebHookResponse = 113 | | { 114 | /** Indicates a successful response. */ 115 | status: "OK"; 116 | /** The payload of the response, structure determined by the Payload type. */ 117 | payload: Payload; 118 | /** Error is never present in a successful response. */ 119 | error: never; 120 | /** The unique identifier for the request. */ 121 | request_id: string; 122 | } 123 | | { 124 | /** Indicates an unsuccessful response. */ 125 | status: "ERROR"; 126 | /** The payload of the response, structure determined by the Payload type. */ 127 | payload: Payload; 128 | /** Description of the error that occurred. */ 129 | error: string; 130 | /** The unique identifier for the request. */ 131 | request_id: string; 132 | }; 133 | -------------------------------------------------------------------------------- /libs/client/src/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { ensureEndpointIdFormat, parseEndpointId } from "./utils"; 2 | 3 | describe("The utils test suite", () => { 4 | it("shoud match a current appOwner/appId format", () => { 5 | const id = "fal-ai/fast-sdxl"; 6 | expect(ensureEndpointIdFormat(id)).toBe(id); 7 | }); 8 | 9 | it("shoud match a current appOwner/appId/path format", () => { 10 | const id = "fal-ai/fast-sdxl/image-to-image"; 11 | expect(ensureEndpointIdFormat(id)).toBe(id); 12 | }); 13 | 14 | it("should throw on an invalid app id format", () => { 15 | const id = "just-an-id"; 16 | expect(() => ensureEndpointIdFormat(id)).toThrowError(); 17 | }); 18 | 19 | it("should parse a current app id", () => { 20 | const id = "fal-ai/fast-sdxl"; 21 | const parsed = parseEndpointId(id); 22 | expect(parsed).toEqual({ 23 | owner: "fal-ai", 24 | alias: "fast-sdxl", 25 | }); 26 | }); 27 | 28 | it("should parse a current app id with path", () => { 29 | const id = "fal-ai/fast-sdxl/image-to-image"; 30 | const parsed = parseEndpointId(id); 31 | expect(parsed).toEqual({ 32 | owner: "fal-ai", 33 | alias: "fast-sdxl", 34 | path: "image-to-image", 35 | }); 36 | }); 37 | 38 | it("should parse a current app id with namespace", () => { 39 | const id = "workflows/fal-ai/fast-sdxl"; 40 | const parsed = parseEndpointId(id); 41 | expect(parsed).toEqual({ 42 | owner: "fal-ai", 43 | alias: "fast-sdxl", 44 | namespace: "workflows", 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /libs/client/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function ensureEndpointIdFormat(id: string): string { 2 | const parts = id.split("/"); 3 | if (parts.length > 1) { 4 | return id; 5 | } 6 | const [, appOwner, appId] = /^([0-9]+)-([a-zA-Z0-9-]+)$/.exec(id) || []; 7 | if (appOwner && appId) { 8 | return `${appOwner}/${appId}`; 9 | } 10 | throw new Error( 11 | `Invalid app id: ${id}. Must be in the format /`, 12 | ); 13 | } 14 | 15 | const ENDPOINT_NAMESPACES = ["workflows", "comfy"] as const; 16 | 17 | type EndpointNamespace = (typeof ENDPOINT_NAMESPACES)[number]; 18 | 19 | export type EndpointId = { 20 | readonly owner: string; 21 | readonly alias: string; 22 | readonly path?: string; 23 | readonly namespace?: EndpointNamespace; 24 | }; 25 | 26 | export function parseEndpointId(id: string): EndpointId { 27 | const normalizedId = ensureEndpointIdFormat(id); 28 | const parts = normalizedId.split("/"); 29 | if (ENDPOINT_NAMESPACES.includes(parts[0] as any)) { 30 | return { 31 | owner: parts[1], 32 | alias: parts[2], 33 | path: parts.slice(3).join("/") || undefined, 34 | namespace: parts[0] as EndpointNamespace, 35 | }; 36 | } 37 | return { 38 | owner: parts[0], 39 | alias: parts[1], 40 | path: parts.slice(2).join("/") || undefined, 41 | }; 42 | } 43 | 44 | export function isValidUrl(url: string) { 45 | try { 46 | const { host } = new URL(url); 47 | return /(fal\.(ai|run))$/.test(host); 48 | } catch (_) { 49 | return false; 50 | } 51 | } 52 | 53 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 54 | export function throttle any>( 55 | func: T, 56 | limit: number, 57 | leading = false, 58 | ): (...funcArgs: Parameters) => ReturnType | void { 59 | let lastFunc: NodeJS.Timeout | null; 60 | let lastRan: number; 61 | 62 | return (...args: Parameters): ReturnType | void => { 63 | if (!lastRan && leading) { 64 | func(...args); 65 | lastRan = Date.now(); 66 | } else { 67 | if (lastFunc) { 68 | clearTimeout(lastFunc); 69 | } 70 | 71 | lastFunc = setTimeout( 72 | () => { 73 | if (Date.now() - lastRan >= limit) { 74 | func(...args); 75 | lastRan = Date.now(); 76 | } 77 | }, 78 | limit - (Date.now() - lastRan), 79 | ); 80 | } 81 | }; 82 | } 83 | 84 | let isRunningInReact: boolean | undefined; 85 | 86 | /** 87 | * Not really the most optimal way to detect if we're running in React, 88 | * but the idea here is that we can support multiple rendering engines 89 | * (starting with React), with all their peculiarities, without having 90 | * to add a dependency or creating custom integrations (e.g. custom hooks). 91 | * 92 | * Yes, a bit of magic to make things works out-of-the-box. 93 | * @returns `true` if running in React, `false` otherwise. 94 | */ 95 | export function isReact() { 96 | if (isRunningInReact === undefined) { 97 | const stack = new Error().stack; 98 | isRunningInReact = 99 | !!stack && 100 | (stack.includes("node_modules/react-dom/") || 101 | stack.includes("node_modules/next/")); 102 | } 103 | return isRunningInReact; 104 | } 105 | 106 | /** 107 | * Check if a value is a plain object. 108 | * @param value - The value to check. 109 | * @returns `true` if the value is a plain object, `false` otherwise. 110 | */ 111 | export function isPlainObject(value: any): boolean { 112 | return !!value && Object.getPrototypeOf(value) === Object.prototype; 113 | } 114 | -------------------------------------------------------------------------------- /libs/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/client/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "inlineSources": true, 7 | "declaration": true, 8 | "esModuleInterop": true, 9 | "importHelpers": false, 10 | "allowJs": true, 11 | "checkJs": false, 12 | "types": ["node"] 13 | }, 14 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], 15 | "include": ["src/**/*.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /libs/client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": ["jest", "node"] 8 | }, 9 | "include": [ 10 | "jest.config.ts", 11 | "src/**/*.test.ts", 12 | "src/**/*.spec.ts", 13 | "src/**/*.test.tsx", 14 | "src/**/*.spec.tsx", 15 | "src/**/*.test.js", 16 | "src/**/*.spec.js", 17 | "src/**/*.test.jsx", 18 | "src/**/*.spec.jsx", 19 | "src/**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /libs/create-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/create-app/README.md: -------------------------------------------------------------------------------- 1 | ## The fal.ai App Generator 2 | 3 | Generate a new full stack app configured with [fal.ai](https://fal.ai) proxy and models. 4 | 5 | ```sh 6 | npx @fal-ai/create-app 7 | ``` 8 | -------------------------------------------------------------------------------- /libs/create-app/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: "create-app", 4 | preset: "../../jest.preset.js", 5 | testEnvironment: "node", 6 | transform: { 7 | "^.+\\.[tj]s$": ["ts-jest", { tsconfig: "/tsconfig.spec.json" }], 8 | }, 9 | moduleFileExtensions: ["ts", "js", "html"], 10 | coverageDirectory: "../../coverage/libs/create-app", 11 | }; 12 | -------------------------------------------------------------------------------- /libs/create-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fal-ai/create-app", 3 | "version": "0.1.4", 4 | "description": "The fal app generator.", 5 | "type": "module", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "bin": "src/index.js", 9 | "keywords": [ 10 | "fal", 11 | "next", 12 | "nextjs", 13 | "express" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/fal-ai/fal-js.git", 18 | "directory": "libs/create-app" 19 | }, 20 | "dependencies": { 21 | "@inquirer/prompts": "^3.3.0", 22 | "@inquirer/select": "^1.3.1", 23 | "chalk": "^5.3.0", 24 | "commander": "^11.1.0", 25 | "execa": "^8.0.1", 26 | "open": "^10.0.3", 27 | "ora": "^8.0.1", 28 | "tslib": "^2.3.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /libs/create-app/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-app", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libs/create-app/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/libs/create-app", 12 | "tsConfig": "libs/create-app/tsconfig.lib.json", 13 | "packageJson": "libs/create-app/package.json", 14 | "main": "libs/create-app/src/index.ts", 15 | "assets": ["LICENSE", "CODE_OF_CONDUCT.md", "libs/create-app/README.md"] 16 | } 17 | }, 18 | "lint": { 19 | "executor": "@nx/linter:eslint", 20 | "outputs": ["{options.outputFile}"], 21 | "options": { 22 | "lintFilePatterns": ["libs/create-app/**/*.ts"] 23 | } 24 | }, 25 | "test": { 26 | "executor": "@nx/jest:jest", 27 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 28 | "options": { 29 | "jestConfig": "libs/create-app/jest.config.ts", 30 | "passWithNoTests": true 31 | }, 32 | "configurations": { 33 | "ci": { 34 | "ci": true, 35 | "codeCoverage": true 36 | } 37 | } 38 | } 39 | }, 40 | "tags": [] 41 | } 42 | -------------------------------------------------------------------------------- /libs/create-app/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { input } from "@inquirer/prompts"; 4 | import select from "@inquirer/select"; 5 | import chalk from "chalk"; 6 | import childProcess from "child_process"; 7 | import { Command } from "commander"; 8 | import { execa, execaCommand } from "execa"; 9 | import fs from "fs"; 10 | import open from "open"; 11 | import ora from "ora"; 12 | import path from "path"; 13 | 14 | const program = new Command(); 15 | const log = console.log; 16 | const repoUrl = "https://github.com/fal-ai/fal-nextjs-template.git"; 17 | const green = chalk.green; 18 | const purple = chalk.hex("#6e40c9"); 19 | 20 | async function main() { 21 | const spinner = ora({ 22 | text: "Creating codebase", 23 | }); 24 | try { 25 | const kebabRegez = /^([a-z]+)(-[a-z0-9]+)*$/; 26 | 27 | program 28 | .name("The fal.ai App Generator") 29 | .description("Generate full stack AI apps integrated with fal.ai"); 30 | 31 | program.parse(process.argv); 32 | 33 | const args = program.args; 34 | let appName = args[0]; 35 | 36 | if (!appName || !kebabRegez.test(args[0])) { 37 | appName = await input({ 38 | message: "Enter your app name", 39 | default: "model-playground", 40 | validate: (d) => { 41 | if (!kebabRegez.test(d)) { 42 | return "please enter your app name in the format of my-app-name"; 43 | } 44 | return true; 45 | }, 46 | }); 47 | } 48 | 49 | const hasFalEnv = await select({ 50 | message: "Do you have a fal.ai API key?", 51 | choices: [ 52 | { 53 | name: "Yes", 54 | value: true, 55 | }, 56 | { 57 | name: "No", 58 | value: false, 59 | }, 60 | ], 61 | }); 62 | 63 | if (!hasFalEnv) { 64 | await open("https://www.fal.ai/dashboard"); 65 | } 66 | 67 | const fal_api_key = await input({ message: "Fal AI API Key" }); 68 | 69 | const envs = ` 70 | # environment, either PRODUCTION or DEVELOPMENT 71 | ENVIRONMENT="PRODUCTION" 72 | 73 | # FAL AI API Key 74 | FAL_KEY="${fal_api_key}" 75 | `; 76 | 77 | log(`\nInitializing project. \n`); 78 | 79 | spinner.start(); 80 | await execa("git", ["clone", repoUrl, appName]); 81 | 82 | let packageJson = fs.readFileSync(`${appName}/package.json`, "utf8"); 83 | const packageObj = JSON.parse(packageJson); 84 | packageObj.name = appName; 85 | packageJson = JSON.stringify(packageObj, null, 2); 86 | fs.writeFileSync(`${appName}/package.json`, packageJson); 87 | fs.writeFileSync(`${appName}/.env.local`, envs); 88 | 89 | process.chdir(path.join(process.cwd(), appName)); 90 | await execa("rm", ["-rf", ".git"]); 91 | await execa("git", ["init"]); 92 | 93 | spinner.text = ""; 94 | let startCommand = ""; 95 | 96 | if (isBunInstalled()) { 97 | spinner.text = "Installing dependencies"; 98 | await execaCommand("bun install").pipeStdout(process.stdout); 99 | spinner.text = ""; 100 | startCommand = "bun dev"; 101 | console.log("\n"); 102 | } else if (isYarnInstalled()) { 103 | await execaCommand("yarn").pipeStdout(process.stdout); 104 | startCommand = "yarn dev"; 105 | } else { 106 | spinner.text = "Installing dependencies"; 107 | await execa("npm", ["install", "--verbose"]).pipeStdout(process.stdout); 108 | spinner.text = ""; 109 | startCommand = "npm run dev"; 110 | } 111 | 112 | spinner.stop(); 113 | await execa("git", ["add", "."]); 114 | await execa("git", ["commit", "-m", "Initial commit"]); 115 | 116 | process.chdir("../"); 117 | log( 118 | `${green.bold("Success!")} Created ${purple.bold( 119 | appName, 120 | )} at ${process.cwd()} \n`, 121 | ); 122 | log( 123 | `To get started, change into the new directory and run ${chalk.cyan( 124 | startCommand, 125 | )}\n`, 126 | ); 127 | } catch (err) { 128 | log("\n"); 129 | if (err.exitCode == 128) { 130 | log("Error: directory already exists."); 131 | } 132 | spinner.stop(); 133 | } 134 | } 135 | 136 | main(); 137 | 138 | function isYarnInstalled() { 139 | try { 140 | childProcess.execSync("yarn --version"); 141 | return true; 142 | } catch { 143 | return false; 144 | } 145 | } 146 | 147 | function isBunInstalled() { 148 | try { 149 | childProcess.execSync("bun --version"); 150 | return true; 151 | } catch (err) { 152 | return false; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /libs/create-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/create-app/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "target": "es6", 8 | "noImplicitAny": true, 9 | "moduleResolution": "node", 10 | "sourceMap": true, 11 | "outDir": "dist", 12 | "baseUrl": ".", 13 | "paths": { 14 | "*": ["node_modules/*", "src/types/*"] 15 | } 16 | }, 17 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], 18 | "include": ["src/**/*.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /libs/create-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /libs/proxy/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nx/js/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /libs/proxy/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/proxy/README.md: -------------------------------------------------------------------------------- 1 | # fal.ai proxy library 2 | 3 | ![@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) 4 | 5 | ## Introduction 6 | 7 | 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. 8 | 9 | ### Install the proxy library: 10 | 11 | ``` 12 | npm install --save @fal-ai/server-proxy 13 | ``` 14 | 15 | ## Next.js page router integration 16 | 17 | For Next.js applications using the page router: 18 | 19 | 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): 20 | 2. Re-export the proxy handler from the library as the default export: 21 | ```ts 22 | export { handler as default } from "@fal-ai/server-proxy/nextjs"; 23 | ``` 24 | 3. Ensure you've set the `FAL_KEY` as an environment variable in your server, containing a valid API Key. 25 | 26 | ## Next.js app router integration 27 | 28 | For Next.js applications using the app router: 29 | 30 | 1. Create an API route in your Next.js app, as a convention we suggest using `app/api/fal/proxy/route.js` (or `.ts` if you're using TypeScript): 31 | 2. Re-export the proxy handler from the library as the default export: 32 | 33 | ```ts 34 | import { route } from "@fal-ai/server-proxy/nextjs"; 35 | 36 | export const { GET, POST, PUT } = route; 37 | ``` 38 | 39 | 3. Ensure you've set the `FAL_KEY` as an environment variable in your server, containing a valid API Key. 40 | 41 | ## Express integration 42 | 43 | For Express applications: 44 | 45 | 1. Make sure your app supports JSON payloads, either by using `express.json()` (recommended) or `body-parser`: 46 | ```ts 47 | app.use(express.json()); 48 | ``` 49 | 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: 50 | 51 | ```ts 52 | import * as falProxy from "@fal-ai/server-proxy/express"; 53 | 54 | app.all( 55 | falProxy.route, // '/api/fal/proxy' or you can use your own 56 | cors(), // if external clients will use the proxy 57 | falProxy.handler, 58 | ); 59 | ``` 60 | 61 | 3. Ensure you've set the `FAL_KEY` as an environment variable in your server, containing a valid API Key. 62 | 63 | ## Client configuration 64 | 65 | Once you've set up the proxy, you can configure the client to use it: 66 | 67 | ```ts 68 | import { fal } from "@fal-ai/client"; 69 | 70 | fal.config({ 71 | proxyUrl: "/api/fal/proxy", // or https://my.app.com/api/fal/proxy 72 | }); 73 | ``` 74 | 75 | Now all your client calls will route through your server proxy, so your credentials are protected. 76 | 77 | ## More information 78 | 79 | For a deeper dive into the proxy library and its capabilities, explore the [official documentation](https://fal.ai/docs). 80 | -------------------------------------------------------------------------------- /libs/proxy/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: "proxy", 4 | preset: "../../jest.preset.js", 5 | globals: {}, 6 | testEnvironment: "node", 7 | transform: { 8 | "^.+\\.[tj]sx?$": [ 9 | "ts-jest", 10 | { 11 | tsconfig: "/tsconfig.spec.json", 12 | }, 13 | ], 14 | }, 15 | moduleFileExtensions: ["ts", "tsx", "js", "jsx"], 16 | coverageDirectory: "../../coverage/libs/proxy", 17 | }; 18 | -------------------------------------------------------------------------------- /libs/proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fal-ai/server-proxy", 3 | "description": "The fal.ai server proxy adapter for JavaScript and TypeScript Web frameworks", 4 | "version": "1.1.1", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/fal-ai/fal-js.git", 9 | "directory": "libs/proxy" 10 | }, 11 | "keywords": [ 12 | "fal", 13 | "client", 14 | "next", 15 | "nextjs", 16 | "express", 17 | "hono", 18 | "proxy" 19 | ], 20 | "exports": { 21 | ".": "./src/index.js", 22 | "./express": "./src/express.js", 23 | "./hono": "./src/hono.js", 24 | "./nextjs": "./src/nextjs.js", 25 | "./remix": "./src/remix.js", 26 | "./svelte": "./src/svelte.js" 27 | }, 28 | "typesVersions": { 29 | "*": { 30 | "express": [ 31 | "src/express.d.ts" 32 | ], 33 | "hono": [ 34 | "src/hono.d.ts" 35 | ], 36 | "nextjs": [ 37 | "src/nextjs.d.ts" 38 | ], 39 | "remix": [ 40 | "src/remix.d.ts" 41 | ], 42 | "svelte": [ 43 | "src/svelte.d.ts" 44 | ] 45 | } 46 | }, 47 | "main": "./src/index.js", 48 | "types": "./src/index.d.ts", 49 | "peerDependencies": { 50 | "@remix-run/dev": "^2.0.0", 51 | "@sveltejs/kit": "^2.0.0", 52 | "express": "^4.0.0", 53 | "hono": "^4.0.0", 54 | "next": "13.4 - 14 || >=15.0.0-0", 55 | "react": "^18.0.0 || >=19.0.0-0", 56 | "react-dom": "^18.0.0 || >=19.0.0-0" 57 | }, 58 | "peerDependenciesMeta": { 59 | "@remix-run/dev": { 60 | "optional": true 61 | }, 62 | "@sveltejs/kit": { 63 | "optional": true 64 | }, 65 | "express": { 66 | "optional": true 67 | }, 68 | "hono": { 69 | "optional": true 70 | }, 71 | "next": { 72 | "optional": true 73 | }, 74 | "react": { 75 | "optional": true 76 | }, 77 | "react-dom": { 78 | "optional": true 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /libs/proxy/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proxy", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libs/proxy/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/libs/proxy", 12 | "tsConfig": "libs/proxy/tsconfig.lib.json", 13 | "packageJson": "libs/proxy/package.json", 14 | "main": "libs/proxy/src/index.ts", 15 | "assets": ["LICENSE", "CODE_OF_CONDUCT.md", "libs/proxy/README.md"] 16 | } 17 | }, 18 | "lint": { 19 | "executor": "@nx/linter:eslint", 20 | "outputs": ["{options.outputFile}"], 21 | "options": { 22 | "lintFilePatterns": ["libs/proxy/**/*.ts"] 23 | } 24 | }, 25 | "test": { 26 | "executor": "@nx/jest:jest", 27 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 28 | "options": { 29 | "jestConfig": "libs/proxy/jest.config.ts", 30 | "passWithNoTests": true 31 | }, 32 | "configurations": { 33 | "ci": { 34 | "ci": true, 35 | "codeCoverage": true 36 | } 37 | } 38 | } 39 | }, 40 | "tags": [] 41 | } 42 | -------------------------------------------------------------------------------- /libs/proxy/src/express.ts: -------------------------------------------------------------------------------- 1 | import type { RequestHandler } from "express"; 2 | import { DEFAULT_PROXY_ROUTE, handleRequest } from "./index"; 3 | 4 | /** 5 | * The default Express route for the fal.ai client proxy. 6 | */ 7 | export const route = DEFAULT_PROXY_ROUTE; 8 | 9 | /** 10 | * The Express route handler for the fal.ai client proxy. 11 | * 12 | * @param request The Express request object. 13 | * @param response The Express response object. 14 | * @param next The Express next function. 15 | */ 16 | export const handler: RequestHandler = async (request, response, next) => { 17 | await handleRequest({ 18 | id: "express", 19 | method: request.method, 20 | getRequestBody: async () => JSON.stringify(request.body), 21 | getHeaders: () => request.headers, 22 | getHeader: (name) => request.headers[name], 23 | sendHeader: (name, value) => response.setHeader(name, value), 24 | respondWith: (status, data) => response.status(status).json(data), 25 | sendResponse: async (res) => { 26 | if (res.body instanceof ReadableStream) { 27 | const reader = res.body.getReader(); 28 | const stream = async () => { 29 | const { done, value } = await reader.read(); 30 | if (done) { 31 | response.end(); 32 | return response; 33 | } 34 | response.write(value); 35 | return await stream(); 36 | }; 37 | 38 | return await stream().catch((error) => { 39 | if (!response.headersSent) { 40 | response.status(500).send(error.message); 41 | } else { 42 | response.end(); 43 | } 44 | }); 45 | } 46 | if (res.headers.get("content-type")?.includes("application/json")) { 47 | return response.status(res.status).json(await res.json()); 48 | } 49 | return response.status(res.status).send(await res.text()); 50 | }, 51 | }); 52 | next(); 53 | }; 54 | -------------------------------------------------------------------------------- /libs/proxy/src/hono.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | import { type StatusCode } from "hono/utils/http-status"; 3 | import { 4 | handleRequest, 5 | HeaderValue, 6 | resolveApiKeyFromEnv, 7 | responsePassthrough, 8 | } from "./index"; 9 | 10 | export type FalHonoProxyOptions = { 11 | /** 12 | * A function to resolve the API key used by the proxy. 13 | * By default, it uses the `FAL_KEY` environment variable. 14 | */ 15 | resolveApiKey?: () => Promise; 16 | }; 17 | 18 | type RouteHandler = (context: Context) => Promise; 19 | 20 | /** 21 | * Creates a route handler that proxies requests to the fal API. 22 | * 23 | * This is a drop-in handler for Hono applications so that the client can be called 24 | * directly from the client-side code while keeping API keys safe. 25 | * 26 | * @param param the proxy options. 27 | * @returns a Hono route handler function. 28 | */ 29 | export function createRouteHandler({ 30 | resolveApiKey = resolveApiKeyFromEnv, 31 | }: FalHonoProxyOptions): RouteHandler { 32 | const routeHandler: RouteHandler = async (context) => { 33 | const responseHeaders: Record = {}; 34 | const response = await handleRequest({ 35 | id: "hono", 36 | method: context.req.method, 37 | respondWith: (status, data) => { 38 | return context.json(data, status as StatusCode, responseHeaders); 39 | }, 40 | getHeaders: () => responseHeaders, 41 | getHeader: (name) => context.req.header(name), 42 | sendHeader: (name, value) => (responseHeaders[name] = value), 43 | getRequestBody: async () => JSON.stringify(await context.req.json()), 44 | sendResponse: responsePassthrough, 45 | resolveApiKey, 46 | }); 47 | return response; 48 | }; 49 | 50 | return routeHandler; 51 | } 52 | -------------------------------------------------------------------------------- /libs/proxy/src/index.ts: -------------------------------------------------------------------------------- 1 | export const TARGET_URL_HEADER = "x-fal-target-url"; 2 | 3 | export const DEFAULT_PROXY_ROUTE = "/api/fal/proxy"; 4 | 5 | const FAL_KEY = process.env.FAL_KEY; 6 | const FAL_KEY_ID = process.env.FAL_KEY_ID; 7 | const FAL_KEY_SECRET = process.env.FAL_KEY_SECRET; 8 | 9 | export type HeaderValue = string | string[] | undefined | null; 10 | 11 | const FAL_URL_REG_EXP = /(\.|^)fal\.(run|ai)$/; 12 | 13 | /** 14 | * The proxy behavior that is passed to the proxy handler. This is a subset of 15 | * request objects that are used by different frameworks, like Express and NextJS. 16 | */ 17 | export interface ProxyBehavior { 18 | id: string; 19 | method: string; 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | respondWith(status: number, data: string | any): ResponseType; 22 | sendResponse(response: Response): Promise; 23 | getHeaders(): Record; 24 | getHeader(name: string): HeaderValue; 25 | sendHeader(name: string, value: string): void; 26 | getRequestBody(): Promise; 27 | resolveApiKey?: () => Promise; 28 | } 29 | 30 | /** 31 | * Utility to get a header value as `string` from a Headers object. 32 | * 33 | * @private 34 | * @param request the header value. 35 | * @returns the header value as `string` or `undefined` if the header is not set. 36 | */ 37 | function singleHeaderValue(value: HeaderValue): string | undefined { 38 | if (!value) { 39 | return undefined; 40 | } 41 | if (Array.isArray(value)) { 42 | return value[0]; 43 | } 44 | return value; 45 | } 46 | 47 | function getFalKey(): string | undefined { 48 | if (FAL_KEY) { 49 | return FAL_KEY; 50 | } 51 | if (FAL_KEY_ID && FAL_KEY_SECRET) { 52 | return `${FAL_KEY_ID}:${FAL_KEY_SECRET}`; 53 | } 54 | return undefined; 55 | } 56 | 57 | const EXCLUDED_HEADERS = ["content-length", "content-encoding"]; 58 | 59 | /** 60 | * A request handler that proxies the request to the fal API 61 | * endpoint. This is useful so client-side calls to the fal endpoint 62 | * can be made without CORS issues and the correct credentials can be added 63 | * effortlessly. 64 | * 65 | * @param behavior the request proxy behavior. 66 | * @returns Promise the promise that will be resolved once the request is done. 67 | */ 68 | export async function handleRequest( 69 | behavior: ProxyBehavior, 70 | ) { 71 | const targetUrl = singleHeaderValue(behavior.getHeader(TARGET_URL_HEADER)); 72 | if (!targetUrl) { 73 | return behavior.respondWith(400, `Missing the ${TARGET_URL_HEADER} header`); 74 | } 75 | 76 | const urlHost = new URL(targetUrl).host; 77 | if (!FAL_URL_REG_EXP.test(urlHost)) { 78 | return behavior.respondWith(412, `Invalid ${TARGET_URL_HEADER} header`); 79 | } 80 | 81 | const falKey = behavior.resolveApiKey 82 | ? await behavior.resolveApiKey() 83 | : getFalKey(); 84 | if (!falKey) { 85 | return behavior.respondWith(401, "Missing fal.ai credentials"); 86 | } 87 | 88 | // pass over headers prefixed with x-fal-* 89 | const headers: Record = {}; 90 | Object.keys(behavior.getHeaders()).forEach((key) => { 91 | if (key.toLowerCase().startsWith("x-fal-")) { 92 | headers[key.toLowerCase()] = behavior.getHeader(key); 93 | } 94 | }); 95 | 96 | const proxyUserAgent = `@fal-ai/server-proxy/${behavior.id}`; 97 | const userAgent = singleHeaderValue(behavior.getHeader("user-agent")); 98 | const res = await fetch(targetUrl, { 99 | method: behavior.method, 100 | headers: { 101 | ...headers, 102 | authorization: 103 | singleHeaderValue(behavior.getHeader("authorization")) ?? 104 | `Key ${falKey}`, 105 | accept: "application/json", 106 | "content-type": "application/json", 107 | "user-agent": userAgent, 108 | "x-fal-client-proxy": proxyUserAgent, 109 | } as HeadersInit, 110 | body: 111 | behavior.method?.toUpperCase() === "GET" 112 | ? undefined 113 | : await behavior.getRequestBody(), 114 | }); 115 | 116 | // copy headers from fal to the proxied response 117 | res.headers.forEach((value, key) => { 118 | if (!EXCLUDED_HEADERS.includes(key.toLowerCase())) { 119 | behavior.sendHeader(key, value); 120 | } 121 | }); 122 | 123 | return behavior.sendResponse(res); 124 | } 125 | 126 | export function fromHeaders( 127 | headers: Headers, 128 | ): Record { 129 | // TODO once Header.entries() is available, use that instead 130 | // Object.fromEntries(headers.entries()); 131 | const result: Record = {}; 132 | headers.forEach((value, key) => { 133 | result[key] = value; 134 | }); 135 | return result; 136 | } 137 | 138 | export const responsePassthrough = (res: Response) => Promise.resolve(res); 139 | 140 | export const resolveApiKeyFromEnv = () => Promise.resolve(getFalKey()); 141 | -------------------------------------------------------------------------------- /libs/proxy/src/nextjs.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse, type NextRequest } from "next/server"; 2 | import type { NextApiHandler } from "next/types"; 3 | import { 4 | DEFAULT_PROXY_ROUTE, 5 | fromHeaders, 6 | handleRequest, 7 | responsePassthrough, 8 | } from "./index"; 9 | 10 | /** 11 | * The default Next API route for the fal.ai client proxy. 12 | */ 13 | export const PROXY_ROUTE = DEFAULT_PROXY_ROUTE; 14 | 15 | /** 16 | * The Next API route handler for the fal.ai client proxy. 17 | * Use it with the /pages router in Next.js. 18 | * 19 | * Note: the page routers proxy doesn't support streaming responses. 20 | * 21 | * @param request the Next API request object. 22 | * @param response the Next API response object. 23 | * @returns a promise that resolves when the request is handled. 24 | */ 25 | export const handler: NextApiHandler = async (request, response) => { 26 | return handleRequest({ 27 | id: "nextjs-page-router", 28 | method: request.method || "POST", 29 | getRequestBody: async () => JSON.stringify(request.body), 30 | getHeaders: () => request.headers, 31 | getHeader: (name) => request.headers[name], 32 | sendHeader: (name, value) => response.setHeader(name, value), 33 | respondWith: (status, data) => response.status(status).json(data), 34 | sendResponse: async (res) => { 35 | if (res.headers.get("content-type")?.includes("application/json")) { 36 | return response.status(res.status).json(await res.json()); 37 | } 38 | return response.status(res.status).send(await res.text()); 39 | }, 40 | }); 41 | }; 42 | 43 | /** 44 | * The Next API route handler for the fal.ai client proxy on App Router apps. 45 | * 46 | * @param request the Next API request object. 47 | * @returns a promise that resolves when the request is handled. 48 | */ 49 | async function routeHandler(request: NextRequest) { 50 | const responseHeaders = new Headers(); 51 | return await handleRequest({ 52 | id: "nextjs-app-router", 53 | method: request.method, 54 | getRequestBody: async () => request.text(), 55 | getHeaders: () => fromHeaders(request.headers), 56 | getHeader: (name) => request.headers.get(name), 57 | sendHeader: (name, value) => responseHeaders.set(name, value), 58 | respondWith: (status, data) => 59 | NextResponse.json(data, { 60 | status, 61 | headers: responseHeaders, 62 | }), 63 | sendResponse: responsePassthrough, 64 | }); 65 | } 66 | 67 | export const route = { 68 | handler: routeHandler, 69 | GET: routeHandler, 70 | POST: routeHandler, 71 | PUT: routeHandler, 72 | }; 73 | -------------------------------------------------------------------------------- /libs/proxy/src/remix.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ActionFunction, 3 | ActionFunctionArgs, 4 | LoaderFunction, 5 | LoaderFunctionArgs, 6 | json as jsonFunction, 7 | } from "@remix-run/node"; 8 | import { 9 | fromHeaders, 10 | handleRequest, 11 | resolveApiKeyFromEnv, 12 | responsePassthrough, 13 | } from "./index"; 14 | 15 | export type FalRemixProxy = { 16 | action: ActionFunction; 17 | loader: LoaderFunction; 18 | }; 19 | 20 | export type FalRemixProxyOptions = { 21 | /** 22 | * The reference to the `json` function from the Remix runtime. 23 | * e.g. `import { json } from "@remix-run/node";` 24 | */ 25 | json: typeof jsonFunction; 26 | /** 27 | * A function to resolve the API key used by the proxy. 28 | * By default, it uses the `FAL_KEY` environment variable. 29 | */ 30 | resolveApiKey?: () => Promise; 31 | }; 32 | 33 | export function createProxy({ 34 | json, 35 | resolveApiKey = resolveApiKeyFromEnv, 36 | }: FalRemixProxyOptions): FalRemixProxy { 37 | const proxy = async ({ 38 | request, 39 | }: ActionFunctionArgs | LoaderFunctionArgs) => { 40 | const responseHeaders = new Headers(); 41 | return handleRequest({ 42 | id: "remix", 43 | method: request.method, 44 | respondWith: (status, data) => 45 | json(data, { status, headers: responseHeaders }), 46 | getHeaders: () => fromHeaders(request.headers), 47 | getHeader: (name) => request.headers.get(name), 48 | sendHeader: (name, value) => responseHeaders.set(name, value), 49 | getRequestBody: async () => JSON.stringify(await request.json()), 50 | sendResponse: responsePassthrough, 51 | resolveApiKey, 52 | }); 53 | }; 54 | return { 55 | action: proxy, 56 | loader: proxy, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /libs/proxy/src/svelte.ts: -------------------------------------------------------------------------------- 1 | import { type RequestHandler } from "@sveltejs/kit"; 2 | import { fromHeaders, handleRequest } from "./index"; 3 | 4 | type RequestHandlerParams = { 5 | /** 6 | * The credentials to use for the request. Usually comes from `$env/static/private` 7 | */ 8 | credentials?: string | undefined; 9 | }; 10 | 11 | /** 12 | * Creates the SvelteKit request handler for the fal.ai client proxy on App Router apps. 13 | * The passed credentials will be used to authenticate the request, if not provided the 14 | * environment variable `FAL_KEY` will be used. 15 | * 16 | * @param params the request handler parameters. 17 | * @returns the SvelteKit request handler. 18 | */ 19 | export const createRequestHandler = ({ 20 | credentials, 21 | }: RequestHandlerParams = {}) => { 22 | const handler: RequestHandler = async ({ request }) => { 23 | const FAL_KEY = credentials || process.env.FAL_KEY || ""; 24 | const responseHeaders = new Headers({ 25 | "Content-Type": "application/json", 26 | }); 27 | return await handleRequest({ 28 | id: "svelte-app-router", 29 | method: request.method, 30 | getRequestBody: async () => request.text(), 31 | getHeaders: () => fromHeaders(request.headers), 32 | getHeader: (name) => request.headers.get(name), 33 | sendHeader: (name, value) => (responseHeaders[name] = value), 34 | resolveApiKey: () => Promise.resolve(FAL_KEY), 35 | respondWith: (status, data) => 36 | new Response(JSON.stringify(data), { 37 | status, 38 | headers: responseHeaders, 39 | }), 40 | sendResponse: async (res) => { 41 | return new Response(res.body, res); 42 | }, 43 | }); 44 | }; 45 | return { 46 | requestHandler: handler, 47 | GET: handler, 48 | POST: handler, 49 | PUT: handler, 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /libs/proxy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "compilerOptions": { 6 | "target": "ES2020", 7 | "importHelpers": false 8 | }, 9 | "references": [ 10 | { 11 | "path": "./tsconfig.lib.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /libs/proxy/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "types": ["node"] 9 | }, 10 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], 11 | "include": ["src/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /libs/proxy/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.test.tsx", 13 | "src/**/*.spec.tsx", 14 | "src/**/*.test.js", 15 | "src/**/*.spec.js", 16 | "src/**/*.test.jsx", 17 | "src/**/*.spec.jsx", 18 | "src/**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "extends": "nx/presets/npm.json", 4 | "tasksRunnerOptions": { 5 | "default": { 6 | "runner": "nx-cloud", 7 | "options": { 8 | "cacheableOperations": ["build", "lint", "test", "e2e"], 9 | "accessToken": "" 10 | } 11 | } 12 | }, 13 | "neverConnectToCloud": true, 14 | "targetDefaults": { 15 | "build": { 16 | "dependsOn": ["^build"], 17 | "inputs": ["production", "^production"] 18 | }, 19 | "test": { 20 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"] 21 | }, 22 | "e2e": { 23 | "inputs": ["default", "^production"] 24 | }, 25 | "lint": { 26 | "inputs": ["default", "{workspaceRoot}/.eslintrc.json"] 27 | } 28 | }, 29 | "namedInputs": { 30 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 31 | "production": [ 32 | "default", 33 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 34 | "!{projectRoot}/tsconfig.spec.json", 35 | "!{projectRoot}/jest.config.[jt]s", 36 | "!{projectRoot}/.eslintrc.json", 37 | "!{projectRoot}/src/test-setup.[jt]s" 38 | ], 39 | "sharedGlobals": ["{workspaceRoot}/babel.config.json"] 40 | }, 41 | "generators": { 42 | "@nx/next": { 43 | "application": { 44 | "style": "css", 45 | "linter": "eslint" 46 | } 47 | }, 48 | "@nx/react": { 49 | "application": { 50 | "babel": true 51 | } 52 | } 53 | }, 54 | "defaultProject": "demo-nextjs-app-router" 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fal-ai/fal-js", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "nx serve", 7 | "build": "nx build", 8 | "test": "nx test", 9 | "lint:staged": "lint-staged", 10 | "docs:typedoc": "typedoc --tsconfig libs/client/tsconfig.lib.json", 11 | "prepare": "husky install" 12 | }, 13 | "private": true, 14 | "lint-staged": { 15 | ".{env,env.example}": [ 16 | "secretlint" 17 | ], 18 | "*.{js,jsx,ts,tsx}": [ 19 | "secretlint", 20 | "prettier --write" 21 | ], 22 | "*.{md,mdx}": [ 23 | "secretlint", 24 | "cspell", 25 | "prettier --write" 26 | ], 27 | "libs/client/src/**/*.ts": [ 28 | "npm run docs:typedoc" 29 | ] 30 | }, 31 | "dependencies": { 32 | "@inquirer/prompts": "^3.3.0", 33 | "@inquirer/select": "^1.3.1", 34 | "@msgpack/msgpack": "^3.0.0-beta2", 35 | "@oclif/core": "^2.3.0", 36 | "@oclif/plugin-help": "^5.2.5", 37 | "@remix-run/dev": "^2.11.1", 38 | "@remix-run/node": "^2.11.1", 39 | "axios": "^1.0.0", 40 | "chalk": "^5.3.0", 41 | "change-case": "^4.1.2", 42 | "chokidar": "^3.5.3", 43 | "commander": "^11.1.0", 44 | "core-js": "^3.6.5", 45 | "cors": "^2.8.5", 46 | "cross-fetch": "^3.1.5", 47 | "dotenv": "^16.3.1", 48 | "encoding": "^0.1.13", 49 | "eventsource-parser": "^1.1.2", 50 | "execa": "^8.0.1", 51 | "express": "^4.18.2", 52 | "fast-glob": "^3.2.12", 53 | "http-proxy": "^1.18.1", 54 | "http-proxy-middleware": "^2.0.6", 55 | "js-base64": "^3.7.5", 56 | "next": "^14.2.5", 57 | "open": "^10.0.3", 58 | "ora": "^8.0.1", 59 | "react": "^18.2.0", 60 | "react-dom": "^18.2.0", 61 | "regenerator-runtime": "0.13.7", 62 | "robot3": "^0.4.1", 63 | "ts-morph": "^17.0.1", 64 | "tslib": "^2.3.0" 65 | }, 66 | "devDependencies": { 67 | "@commitlint/cli": "^17.0.0", 68 | "@commitlint/config-conventional": "^17.0.0", 69 | "@excalidraw/excalidraw": "^0.17.0", 70 | "@inrupt/jest-jsdom-polyfills": "^3.0.1", 71 | "@nrwl/express": "16.10.0", 72 | "@nx/cypress": "16.10.0", 73 | "@nx/eslint-plugin": "16.10.0", 74 | "@nx/express": "16.10.0", 75 | "@nx/jest": "16.10.0", 76 | "@nx/js": "16.10.0", 77 | "@nx/linter": "16.10.0", 78 | "@nx/next": "^16.10.0", 79 | "@nx/node": "16.10.0", 80 | "@nx/react": "16.10.0", 81 | "@nx/web": "16.10.0", 82 | "@nx/webpack": "16.10.0", 83 | "@nx/workspace": "16.10.0", 84 | "@secretlint/secretlint-rule-pattern": "^7.0.7", 85 | "@secretlint/secretlint-rule-preset-recommend": "^7.0.7", 86 | "@sveltejs/kit": "^2.5.0", 87 | "@swc-node/core": "^1.10.6", 88 | "@swc-node/register": "^1.6.8", 89 | "@testing-library/react": "14.0.0", 90 | "@theunderscorer/nx-semantic-release": "^2.2.1", 91 | "@types/cors": "^2.8.14", 92 | "@types/express": "4.17.13", 93 | "@types/jest": "29.4.4", 94 | "@types/node": "18.14.2", 95 | "@types/react": "18.2.24", 96 | "@types/react-dom": "18.2.9", 97 | "@typescript-eslint/eslint-plugin": "5.62.0", 98 | "@typescript-eslint/parser": "5.62.0", 99 | "autoprefixer": "10.4.13", 100 | "babel-jest": "29.4.3", 101 | "cspell": "^8.0.0", 102 | "cypress": "^11.0.0", 103 | "eslint": "8.46.0", 104 | "eslint-config-next": "^14.0.3", 105 | "eslint-config-prettier": "^8.1.0", 106 | "eslint-plugin-cypress": "2.15.1", 107 | "eslint-plugin-import": "2.27.5", 108 | "eslint-plugin-jsx-a11y": "6.7.1", 109 | "eslint-plugin-react": "7.32.2", 110 | "eslint-plugin-react-hooks": "^4.6.0", 111 | "fs-extra": "^11.1.0", 112 | "hono": "^4.6.3", 113 | "husky": "^8.0.0", 114 | "jest": "29.4.3", 115 | "jest-environment-jsdom": "29.4.3", 116 | "jest-environment-node": "^29.4.1", 117 | "lint-staged": "^15.0.2", 118 | "nx": "16.10.0", 119 | "nx-cloud": "16.4.0", 120 | "organize-imports-cli": "^0.10.0", 121 | "postcss": "8.4.21", 122 | "prettier": "^3.3.3", 123 | "prettier-plugin-organize-imports": "^3.2.4", 124 | "prettier-plugin-tailwindcss": "^0.6.5", 125 | "secretlint": "^7.0.7", 126 | "tailwindcss": "3.2.7", 127 | "ts-jest": "29.1.1", 128 | "ts-node": "^10.9.1", 129 | "ts-protoc-gen": "^0.15.0", 130 | "tsconfig-paths": "^4.2.0", 131 | "typedoc": "^0.26.7", 132 | "typedoc-github-theme": "^0.1.2", 133 | "typedoc-plugin-extras": "^3.1.0", 134 | "typedoc-plugin-mdn-links": "^3.2.12", 135 | "typescript": "^5.5.4" 136 | }, 137 | "prettier": { 138 | "plugins": [ 139 | "prettier-plugin-organize-imports", 140 | "prettier-plugin-tailwindcss" 141 | ] 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@fal-ai/client": ["libs/client/src/index.ts"], 19 | "@fal-ai/client/endpoints": ["libs/client/src/types/endpoints.ts"], 20 | "@fal-ai/create-app": ["libs/create-app/src/index.ts"], 21 | "@fal-ai/server-proxy": ["libs/proxy/src/index.ts"], 22 | "@fal-ai/server-proxy/express": ["libs/proxy/src/express.ts"], 23 | "@fal-ai/server-proxy/nextjs": ["libs/proxy/src/nextjs.ts"] 24 | } 25 | }, 26 | "exclude": ["node_modules/**", "tmp/**", "dist/**"] 27 | } 28 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "out": "docs/reference", 4 | "entryPoints": ["./libs/client/src/index.ts"], 5 | "exclude": [ 6 | "./src/__tests__/**", 7 | "*.spec.ts", 8 | "./libs/client/src/types/endpoints.ts" 9 | ], 10 | "excludeExternals": true, 11 | "excludeInternal": false, 12 | "includeVersion": false, 13 | "githubPages": true, 14 | "plugin": [ 15 | "typedoc-plugin-mdn-links", 16 | "typedoc-plugin-extras", 17 | "typedoc-github-theme" 18 | ], 19 | "readme": "none", 20 | "hideGenerator": true 21 | } 22 | --------------------------------------------------------------------------------