├── .changeset
├── README.md
└── config.json
├── .github
├── ISSUE_TEMPLATE
│ ├── Bug.md
│ └── RFC.md
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .husky
└── pre-commit
├── .prettierignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── examples
├── ecommerce
│ ├── .gitignore
│ ├── .vscode
│ │ └── settings.json
│ ├── README.md
│ ├── app
│ │ ├── api
│ │ │ └── fuse
│ │ │ │ └── route.ts
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── page.module.css
│ │ └── page.tsx
│ ├── components
│ │ ├── Cart.module.css
│ │ ├── Cart.tsx
│ │ ├── Category.module.css
│ │ ├── Category.tsx
│ │ ├── DatalayerProvider.tsx
│ │ ├── Product.module.css
│ │ └── Product.tsx
│ ├── fuse
│ │ ├── client.ts
│ │ ├── fragment-masking.ts
│ │ ├── gql.ts
│ │ ├── graphql.ts
│ │ ├── index.ts
│ │ ├── pages.ts
│ │ └── server.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── next.svg
│ │ └── vercel.svg
│ ├── schema.graphql
│ ├── tsconfig.json
│ └── types
│ │ ├── Cart.ts
│ │ ├── Category.ts
│ │ ├── Product.ts
│ │ └── context.ts
├── spacex
│ ├── .gitignore
│ ├── .vscode
│ │ └── settings.json
│ ├── README.md
│ ├── app
│ │ ├── api
│ │ │ └── fuse
│ │ │ │ └── route.ts
│ │ ├── client
│ │ │ ├── page.module.css
│ │ │ └── page.tsx
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── page.module.css
│ │ ├── page.tsx
│ │ └── rsc
│ │ │ ├── page.module.css
│ │ │ └── page.tsx
│ ├── components
│ │ ├── DatalayerProvider.tsx
│ │ ├── LaunchDetails.tsx
│ │ ├── LaunchItem.module.css
│ │ ├── LaunchItem.tsx
│ │ ├── LaunchSite.tsx
│ │ ├── Location.tsx
│ │ ├── PageNumbers.module.css
│ │ ├── PageNumbers.tsx
│ │ ├── Rocket.tsx
│ │ └── actions
│ │ │ └── sayHello.ts
│ ├── fuse
│ │ ├── client.ts
│ │ ├── fragment-masking.ts
│ │ ├── gql.ts
│ │ ├── graphql.ts
│ │ ├── index.ts
│ │ ├── pages.ts
│ │ └── server.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ └── test.tsx
│ ├── public
│ │ ├── next.svg
│ │ └── vercel.svg
│ ├── schema.graphql
│ ├── tsconfig.json
│ └── types
│ │ ├── Launch.ts
│ │ ├── launch
│ │ ├── Rocket.ts
│ │ └── Site.ts
│ │ └── scopes.ts
└── standalone
│ ├── .gitignore
│ ├── README.md
│ ├── _context.ts
│ ├── index.html
│ ├── package.json
│ ├── public
│ └── vite.svg
│ ├── schema.graphql
│ ├── src
│ ├── App.tsx
│ ├── components
│ │ ├── LaunchDetails.tsx
│ │ ├── LaunchItem.module.css
│ │ ├── LaunchItem.tsx
│ │ ├── LaunchSite.tsx
│ │ ├── Location.tsx
│ │ ├── PageNumbers.module.css
│ │ └── PageNumbers.tsx
│ ├── fuse
│ │ ├── index.ts
│ │ ├── introspection.ts
│ │ └── tada.ts
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ ├── types
│ ├── Launch.ts
│ └── launch
│ │ ├── Rocket.ts
│ │ └── Site.ts
│ └── vite.config.ts
├── package.json
├── packages
├── core
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── client.d.ts
│ ├── loader.js
│ ├── package.json
│ ├── rsc.d.ts
│ ├── src
│ │ ├── adapters
│ │ │ ├── bun.ts
│ │ │ ├── cloudflare.ts
│ │ │ ├── lambda.ts
│ │ │ └── node.ts
│ │ ├── builder.ts
│ │ ├── cli.ts
│ │ ├── client.ts
│ │ ├── dev.ts
│ │ ├── errors.ts
│ │ ├── exchanges
│ │ │ └── cache.ts
│ │ ├── next
│ │ │ ├── client.ts
│ │ │ ├── index.ts
│ │ │ ├── pages.ts
│ │ │ ├── plugin.ts
│ │ │ └── rsc.ts
│ │ ├── pothos-list
│ │ │ ├── global-types.ts
│ │ │ ├── index.ts
│ │ │ ├── schema-builder.ts
│ │ │ └── types.ts
│ │ └── utils
│ │ │ ├── codegen.ts
│ │ │ ├── gql-tada.ts
│ │ │ └── yoga-helpers.ts
│ ├── test
│ │ ├── authorization.test.ts
│ │ ├── cli.test.ts
│ │ ├── errors.test.ts
│ │ ├── fixtures
│ │ │ ├── simple
│ │ │ │ ├── package.json
│ │ │ │ ├── tsconfig.json
│ │ │ │ └── types
│ │ │ │ │ └── Test.ts
│ │ │ └── tada
│ │ │ │ ├── package.json
│ │ │ │ ├── tsconfig.json
│ │ │ │ └── types
│ │ │ │ └── Test.ts
│ │ ├── list.test.ts
│ │ ├── node.test.ts
│ │ └── schema.test.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ └── vitest.config.mts
└── create-fuse-app
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── get-package-manager.ts
│ ├── index.ts
│ ├── install-package.ts
│ └── rewrite-next.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── website
├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .svgrrc.js
├── README.md
├── next-env.d.ts
├── next-sitemap.config.js
├── next.config.js
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
├── favicon.ico
├── favicon.png
├── images
│ ├── data-layers-vs-api-gateways.png
│ ├── data-layers-vs-bffs.png
│ ├── data-layers-vs-graphql-federation.png
│ ├── fuse-circle.svg
│ ├── fuse-circles-with-logos.svg
│ ├── fuse-circles-with-logos.webp
│ ├── fuse-diagram-desktop.svg
│ ├── fuse-diagram-mobile.svg
│ ├── fuse-grid-logo.webp
│ ├── fuse-logo-inverted.svg
│ ├── fuse-logo-white-border.svg
│ ├── fuse-logo.svg
│ ├── fuse-outline.svg
│ ├── fuse-workflow.svg
│ ├── gql-with-circles.svg
│ ├── homepage-code-sample-desktop.svg
│ ├── homepage-code-sample-mobile.svg
│ ├── nextjs-logo.svg
│ ├── og-image.png
│ └── the-grid.svg
├── next.svg
├── robots.txt
├── sitemap-0.xml
├── sitemap.xml
├── vercel.svg
└── videos
│ ├── video-poster.png
│ ├── video-sample-vertical.mp4
│ ├── video-sample.mp4
│ └── video-vertical-poster.png
├── src
├── components
│ ├── Button.tsx
│ ├── Card.tsx
│ ├── Explain.tsx
│ ├── ExternalLink.tsx
│ ├── HeadMeta.tsx
│ ├── Heading.tsx
│ ├── MobileMenuLines.tsx
│ ├── PageVerticaLines.tsx
│ ├── PoweredByCards.tsx
│ ├── Section.tsx
│ ├── StarOnGithub.tsx
│ ├── Testimonials.tsx
│ ├── Text.tsx
│ ├── TheGrid.tsx
│ ├── icons
│ │ ├── AngularjsLogo.tsx
│ │ ├── ArrowConnectingNodes.tsx
│ │ ├── ArrowOpeningPath.tsx
│ │ ├── Automatic.tsx
│ │ ├── AwsLogo.tsx
│ │ ├── BuildingBlock.tsx
│ │ ├── BunLogo.tsx
│ │ ├── CloudflareWorkersLogo.tsx
│ │ ├── External.tsx
│ │ ├── FuseLogo.tsx
│ │ ├── FuseLogoInverted.tsx
│ │ ├── FuseLogoWithName.tsx
│ │ ├── FuseLogoWithNameDark.tsx
│ │ ├── FuseLogoWithNameLight.tsx
│ │ ├── GatsbyLogo.tsx
│ │ ├── GithubLogo.tsx
│ │ ├── GlobalUnique.tsx
│ │ ├── GraphQlCodeGenLogo.tsx
│ │ ├── GraphQlYogaLogo.tsx
│ │ ├── GraphiqlLogo.tsx
│ │ ├── GraphqlLogoOutline.tsx
│ │ ├── HttpLogo.tsx
│ │ ├── JavaLogo.tsx
│ │ ├── KotlinLogo.tsx
│ │ ├── NextJsLogo.tsx
│ │ ├── NodeJsLogo.tsx
│ │ ├── NodeStack.tsx
│ │ ├── NpmLogo.tsx
│ │ ├── Observability.tsx
│ │ ├── PothosLogo.tsx
│ │ ├── PrismaLogo.tsx
│ │ ├── PuzzlePieces.tsx
│ │ ├── ReactLogo.tsx
│ │ ├── ReactNativeLogo.tsx
│ │ ├── Relay.tsx
│ │ ├── Scalable.tsx
│ │ ├── Security.tsx
│ │ ├── Star.tsx
│ │ ├── StarSparkle.tsx
│ │ ├── StellateLogo.tsx
│ │ ├── StellateLogoWithName.tsx
│ │ ├── SwiftLogo.tsx
│ │ ├── Terminal.tsx
│ │ ├── UrqlLogo.tsx
│ │ ├── VueLogo.tsx
│ │ ├── XLogo.tsx
│ │ ├── index.ts
│ │ └── svg
│ │ │ ├── angularjsLogo.svg
│ │ │ ├── arrowConnectingNodes.svg
│ │ │ ├── arrowOpeningPath.svg
│ │ │ ├── automatic.svg
│ │ │ ├── awsLogo.svg
│ │ │ ├── buildingBlock.svg
│ │ │ ├── bunLogo.svg
│ │ │ ├── cloudflareWorkersLogo.svg
│ │ │ ├── external.svg
│ │ │ ├── fuseLogo.svg
│ │ │ ├── fuseLogoInverted.svg
│ │ │ ├── fuseLogoWithName.svg
│ │ │ ├── fuseLogoWithNameDark.svg
│ │ │ ├── fuseLogoWithNameLight.svg
│ │ │ ├── gatsbyLogo.svg
│ │ │ ├── githubLogo.svg
│ │ │ ├── globalUnique.svg
│ │ │ ├── graphQLCodeGenLogo.svg
│ │ │ ├── graphQLYogaLogo.svg
│ │ │ ├── graphiqlLogo.svg
│ │ │ ├── graphqlLogoOutline.svg
│ │ │ ├── httpLogo.svg
│ │ │ ├── javaLogo.svg
│ │ │ ├── kotlinLogo.svg
│ │ │ ├── nextJsLogo.svg
│ │ │ ├── nodeJsLogo.svg
│ │ │ ├── nodeStack.svg
│ │ │ ├── npmLogo.svg
│ │ │ ├── observability.svg
│ │ │ ├── pothosLogo.svg
│ │ │ ├── prismaLogo.svg
│ │ │ ├── puzzlePieces.svg
│ │ │ ├── reactLogo.svg
│ │ │ ├── reactNativeLogo.svg
│ │ │ ├── relay.svg
│ │ │ ├── scalable.svg
│ │ │ ├── security.svg
│ │ │ ├── star.svg
│ │ │ ├── starSparkle.svg
│ │ │ ├── stellateLogo.svg
│ │ │ ├── stellateLogoWithName.svg
│ │ │ ├── swiftLogo.svg
│ │ │ ├── terminal.svg
│ │ │ ├── urqlLogo.svg
│ │ │ ├── vueLogo.svg
│ │ │ └── xLogo.svg
│ └── snippets
│ │ ├── client.mdx
│ │ └── node.mdx
├── mdx.d.ts
├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── _meta.json
│ ├── docs
│ │ ├── _meta.json
│ │ ├── client
│ │ │ ├── _meta.json
│ │ │ ├── best-practices.mdx
│ │ │ ├── index.mdx
│ │ │ ├── javascript
│ │ │ │ ├── _meta.json
│ │ │ │ ├── angular.mdx
│ │ │ │ ├── nextjs.mdx
│ │ │ │ ├── react.mdx
│ │ │ │ └── vue.mdx
│ │ │ ├── other
│ │ │ │ ├── _meta.json
│ │ │ │ ├── android.mdx
│ │ │ │ ├── http.mdx
│ │ │ │ └── ios.mdx
│ │ │ └── types.mdx
│ │ ├── deployment
│ │ │ ├── _meta.json
│ │ │ ├── bun.mdx
│ │ │ ├── cloudflare-workers.mdx
│ │ │ ├── index.mdx
│ │ │ ├── lambda.mdx
│ │ │ ├── nextjs.mdx
│ │ │ └── node.mdx
│ │ ├── fuse-method
│ │ │ ├── _meta.json
│ │ │ ├── index.mdx
│ │ │ ├── vs-backend-for-frontends.mdx
│ │ │ └── vs-graphql-federation.mdx
│ │ ├── index.mdx
│ │ ├── server
│ │ │ ├── _meta.json
│ │ │ ├── authorization.mdx
│ │ │ ├── context.mdx
│ │ │ ├── error-handling.mdx
│ │ │ ├── integrations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── drizzle.mdx
│ │ │ │ ├── grpc.mdx
│ │ │ │ ├── index.mdx
│ │ │ │ ├── kysely.mdx
│ │ │ │ ├── prisma.mdx
│ │ │ │ └── rest.mdx
│ │ │ ├── lists.mdx
│ │ │ ├── mocking.mdx
│ │ │ ├── nodes.mdx
│ │ │ ├── objects-enums-unions-interfaces.mdx
│ │ │ └── queries-and-mutations.mdx
│ │ ├── setting-fuse-up-manually
│ │ │ ├── _meta.json
│ │ │ ├── index.mdx
│ │ │ └── nextjs.mdx
│ │ └── troubleshooting.mdx
│ └── index.tsx
├── styles
│ └── globals.scss
├── svg.d.ts
└── utils
│ └── tailwind.ts
├── tailwind.config.ts
├── theme.config.tsx
└── tsconfig.json
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "access": "public",
6 | "baseBranch": "main",
7 | "updateInternalDependencies": "minor",
8 | "ignore": ["@fuse-examples/*", "@fuse/website", "@fuse-fixtures/*"],
9 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
10 | "onlyUpdatePeerDependentsWhenOutOfRange": true,
11 | "updateInternalDependents": "out-of-range"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 'bug report'
3 | about: Report a bug with Fuse
4 | title: 'Bug: short description'
5 | labels: 'Bug'
6 | ---
7 |
8 |
9 |
10 | ## Description
11 |
12 |
13 |
14 |
15 |
16 | [Reproduction]()
17 |
18 | ## Versions
19 |
20 | - TypeScript Version:
21 | - Fuse version:
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/RFC.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 'RFC'
3 | about: Propose an enhancement / feature and start a discussion
4 | title: 'RFC: Your Proposal'
5 | labels: 'RFC'
6 | ---
7 |
8 | ## Summary
9 |
10 |
16 |
17 | ## Proposed Solution
18 |
19 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - main
7 | jobs:
8 | release:
9 | name: CI
10 | runs-on: ubuntu-20.04
11 | timeout-minutes: 20
12 | steps:
13 | - name: Checkout Repo
14 | uses: actions/checkout@v3
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Setup Node
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: 20
22 |
23 | - name: Setup pnpm
24 | uses: pnpm/action-setup@v2.2.4
25 | with:
26 | version: 8
27 | run_install: false
28 |
29 | - name: Get pnpm store directory
30 | id: pnpm-store
31 | run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
32 |
33 | - name: Use pnpm store
34 | uses: actions/cache@v3
35 | id: pnpm-cache
36 | with:
37 | path: |
38 | ~/.cache/Cypress
39 | ${{ steps.pnpm-store.outputs.pnpm_cache_dir }}
40 | key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
41 | restore-keys: |
42 | ${{ runner.os }}-pnpm-
43 |
44 | - name: Install Dependencies
45 | run: pnpm install
46 |
47 | - name: TypeCheck fuse
48 | id: typecheck-core
49 | run: pnpm --filter fuse typecheck
50 |
51 | - name: Build fuse
52 | id: build-core
53 | run: pnpm --filter fuse build
54 |
55 | - name: Test fuse
56 | id: test-core
57 | run: pnpm --filter fuse test
58 |
59 | - name: TypeCheck example
60 | id: typecheck-example
61 | run: pnpm --filter @fuse-examples/spacex typecheck
62 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - main
6 | jobs:
7 | release:
8 | name: Release
9 | runs-on: ubuntu-20.04
10 | timeout-minutes: 20
11 | permissions:
12 | contents: write
13 | id-token: write
14 | issues: write
15 | repository-projects: write
16 | deployments: write
17 | packages: write
18 | pull-requests: write
19 | steps:
20 | - name: Checkout Repo
21 | uses: actions/checkout@v3
22 | with:
23 | fetch-depth: 0
24 |
25 | - name: Setup Node
26 | uses: actions/setup-node@v3
27 | with:
28 | node-version: 20
29 |
30 | - name: Setup pnpm
31 | uses: pnpm/action-setup@v2.2.4
32 | with:
33 | version: 8
34 | run_install: false
35 |
36 | - name: Get pnpm store directory
37 | id: pnpm-store
38 | run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
39 |
40 | - name: Use pnpm store
41 | uses: actions/cache@v3
42 | id: pnpm-cache
43 | with:
44 | path: |
45 | ~/.cache/Cypress
46 | ${{ steps.pnpm-store.outputs.pnpm_cache_dir }}
47 | key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
48 | restore-keys: |
49 | ${{ runner.os }}-pnpm-
50 |
51 | - name: Install Dependencies
52 | run: pnpm install
53 |
54 | - name: PR or Publish
55 | id: changesets
56 | uses: changesets/action@v1.4.5
57 | with:
58 | version: pnpm changeset version
59 | publish: pnpm changeset publish
60 | env:
61 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | packages/core/test/fixtures/**/build
4 | packages/core/test/fixtures/**/schema.graphql
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | examples/spacex/gql
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib",
3 | "typescript.enablePromptUseWorkspaceTsdk": true
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Stellate
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fuse
2 |
3 | 
4 |
5 | # Getting Started
6 |
7 | When you are in the root of your app run the following command. This will
8 | install all the packages and generate the files you need.
9 |
10 | ```sh
11 | npx create-fuse-app
12 | ```
13 |
14 | Then, run `npx fuse dev` and your API will be running at `localhost:4000/graphql`!
15 |
16 | > If you are **using Next.js, you don't need to manually run `fuse dev`**. `create-fuse-app` will add a Next.js plugin to your `next.config.js/ts/mjs`` and an API route at `/api/fuse` for you to access your API. ([learn more](https://fusedata.dev/docs/setting-fuse-up-manually/nextjs))
17 |
18 | ## Querying your data layer
19 |
20 | ```tsx
21 | import { graphql } from '@/fuse'
22 | import { execute } from '@/fuse/server'
23 |
24 | const UserQuery = graphql(`
25 | query User($id: ID!) {
26 | user(id: $id) {
27 | id
28 | name
29 | }
30 | }
31 | `)
32 |
33 | export default async function Page() {
34 | const result = await execute({
35 | query: UserQuery,
36 | variables: { id: '1' },
37 | })
38 |
39 | return
Welcome {result.data?.user?.name}
40 | }
41 | ```
42 |
43 | # [Docs](https://fusedata.dev/docs)
44 |
45 | **Read [the documentation](https://fusedata.dev/docs) for more information about using Fuse**.
46 |
47 | Quicklinks to some of the most-visited pages:
48 |
49 | - [Getting started](https://fusedata.dev/docs)
50 | - [Querying your API (client)](https://fusedata.dev/docs/client)
51 | - [Building your API (server)](https://fusedata.dev/docs/server/queries-and-mutations)
52 | - [Deploying your API (server)](https://fusedata.dev/docs/deployment)
53 | - [The Fuse Method](https://fusedata.dev/docs/fuse-method)
54 |
55 | # License
56 |
57 | Licensed under the MIT License, Copyright © 2023-present Stellate, Inc.
58 |
59 | See LICENSE for more information.
60 |
--------------------------------------------------------------------------------
/examples/ecommerce/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/examples/ecommerce/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/ecommerce/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/examples/ecommerce/app/api/fuse/route.ts:
--------------------------------------------------------------------------------
1 | import { createAPIRouteHandler } from 'fuse/next'
2 |
3 | const layer = createAPIRouteHandler<{ userId: string }>({
4 | context: (ctx) => ({
5 | // For demo purposes everyone is the same user
6 | userId: '1',
7 | }),
8 | })
9 |
10 | export const GET = layer
11 | export const POST = layer
12 |
--------------------------------------------------------------------------------
/examples/ecommerce/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/examples/ecommerce/app/favicon.ico
--------------------------------------------------------------------------------
/examples/ecommerce/app/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | max-width: 100vw;
4 | overflow-x: hidden;
5 | }
6 |
7 | body {
8 | color: black;
9 | }
10 |
11 | a {
12 | color: inherit;
13 | text-decoration: none;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/ecommerce/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Inter } from 'next/font/google'
2 | import './globals.css'
3 | import { DatalayerProvider } from '@/components/DatalayerProvider'
4 |
5 | const inter = Inter({ subsets: ['latin'] })
6 |
7 | export default function RootLayout({
8 | children,
9 | }: {
10 | children: React.ReactNode
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/examples/ecommerce/app/page.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | min-height: 100vh;
6 | }
7 |
8 | .list {
9 | padding: 0;
10 | margin: 0;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/ecommerce/app/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import styles from './page.module.css'
4 | import { graphql } from '@/fuse'
5 | import { execute } from '@/fuse/server'
6 | import { Category } from '@/components/Category'
7 | import { Cart } from '@/components/Cart'
8 |
9 | const HomePageQuery = graphql(`
10 | query HomePage {
11 | myCart {
12 | ...Cart_CartFields
13 | }
14 | categories {
15 | ...Category_CategoryFields
16 | }
17 | }
18 | `)
19 |
20 | export default async function Page() {
21 | const result = await execute({
22 | query: HomePageQuery,
23 | variables: {},
24 | context: () => ({ userId: '1' }),
25 | })
26 | return (
27 |
28 | Fuse Store
29 | {result.data?.myCart && }
30 | {result.data?.categories.map((category, i) => (
31 |
32 | ))}
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/examples/ecommerce/components/Cart.module.css:
--------------------------------------------------------------------------------
1 | .cartTitle {
2 | margin-bottom: 12px;
3 | text-align: center;
4 | }
5 |
6 | .cartSection {
7 | margin-bottom: 16px;
8 | }
9 |
10 | .grid {
11 | list-style: none;
12 | display: grid;
13 | grid-template-columns: 1fr 1fr 1fr;
14 | gap: 16px;
15 | padding: 0;
16 | margin: 0;
17 | }
--------------------------------------------------------------------------------
/examples/ecommerce/components/Cart.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentType, graphql, useFragment } from '@/fuse'
2 | import { Product } from './Product'
3 | import styles from './Cart.module.css'
4 |
5 | const CartFields = graphql(`
6 | fragment Cart_CartFields on Cart {
7 | items {
8 | quantity
9 | product {
10 | id
11 | price
12 | ...Product_ProductFields
13 | }
14 | }
15 | }
16 | `)
17 |
18 | export const Cart = (props: { cart: FragmentType }) => {
19 | const cart = useFragment(CartFields, props.cart)
20 | return (
21 |
22 |
23 | Cart - Total Price: $
24 | {cart.items?.reduce(
25 | (acc, item) => acc + item.quantity * item.product.price,
26 | 0,
27 | )}
28 |
29 |
30 | {cart.items?.map(
31 | (cartItem, i) =>
32 | cartItem &&
33 | cartItem.product && (
34 |
39 | ),
40 | )}
41 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/examples/ecommerce/components/Category.module.css:
--------------------------------------------------------------------------------
1 | .categoryTitle {
2 | margin-bottom: 12px;
3 | text-align: center;
4 | }
5 |
6 | .categorySection {
7 | margin-bottom: 16px;
8 | }
9 |
10 | .grid {
11 | list-style: none;
12 | display: grid;
13 | grid-template-columns: 1fr 1fr 1fr;
14 | gap: 16px;
15 | padding: 0;
16 | margin: 0;
17 | }
--------------------------------------------------------------------------------
/examples/ecommerce/components/Category.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentType, graphql, useFragment } from '@/fuse'
2 | import { Product } from './Product'
3 | import styles from './Category.module.css'
4 |
5 | const CategoryFields = graphql(`
6 | fragment Category_CategoryFields on Category {
7 | name
8 | products {
9 | id
10 | ...Product_ProductFields
11 | }
12 | }
13 | `)
14 |
15 | export const Category = (props: {
16 | category: FragmentType
17 | }) => {
18 | const category = useFragment(CategoryFields, props.category)
19 | return (
20 |
21 | {category.name}
22 |
23 | {category.products.map((product, i) => (
24 |
25 | ))}
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/examples/ecommerce/components/DatalayerProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Provider, createClient } from '@/fuse/client'
4 | import React, { Suspense } from 'react'
5 |
6 | export const DatalayerProvider = (props: any) => {
7 | const [client, ssr] = React.useMemo(() => {
8 | const { client, ssr } = createClient({
9 | url:
10 | process.env.NODE_ENV === 'production'
11 | ? 'https://spacex-fuse.vercel.app/api/fuse'
12 | : 'http://localhost:3000/api/fuse',
13 | })
14 |
15 | return [client, ssr]
16 | }, [])
17 |
18 | return (
19 |
20 | {props.children}
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/examples/ecommerce/components/Product.module.css:
--------------------------------------------------------------------------------
1 | .productItem {
2 | border: 1px solid black;
3 | border-radius: 6px;
4 | padding: 16px;
5 | width: 256px;
6 | display: flex;
7 | flex-direction: column;
8 | }
9 |
10 | .addToCart {
11 | margin-bottom: 16px;
12 | }
13 |
14 | .productImage {
15 | height: 128px;
16 | width: 128px;
17 | margin-left: auto;
18 | margin-right: auto;
19 | margin-bottom: 12px;
20 | }
21 |
22 | .productName {
23 | text-align: center;
24 | margin: 0;
25 | margin-bottom: 16px;
26 | }
27 |
28 | .productDescription {
29 | margin: 0;
30 | font-size: 14px;
31 | }
32 |
--------------------------------------------------------------------------------
/examples/ecommerce/components/Product.tsx:
--------------------------------------------------------------------------------
1 | 'use client;'
2 |
3 | import { FragmentType, graphql, useFragment } from '@/fuse'
4 | import styles from './Product.module.css'
5 |
6 | const ProductFields = graphql(`
7 | fragment Product_ProductFields on Product {
8 | id
9 | name
10 | image
11 | description
12 | price
13 | }
14 | `)
15 |
16 | export const Product = (props: {
17 | product: FragmentType
18 | noAddToCart?: boolean
19 | }) => {
20 | const product = useFragment(ProductFields, props.product)
21 | return (
22 |
23 |
30 |
31 | {product.name} - ${product.price}
32 |
33 | {!props.noAddToCart && (
34 |
35 | )}
36 | {product.description && (
37 | {product.description}
38 | )}
39 |
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/examples/ecommerce/fuse/client.ts:
--------------------------------------------------------------------------------
1 | // This is a generated file!
2 |
3 | export * from 'fuse/next/client'
4 |
--------------------------------------------------------------------------------
/examples/ecommerce/fuse/index.ts:
--------------------------------------------------------------------------------
1 | export * from './fragment-masking'
2 | export * from './gql'
3 |
--------------------------------------------------------------------------------
/examples/ecommerce/fuse/pages.ts:
--------------------------------------------------------------------------------
1 | // This is a generated file!
2 |
3 | export * from 'fuse/next/pages'
4 |
--------------------------------------------------------------------------------
/examples/ecommerce/fuse/server.ts:
--------------------------------------------------------------------------------
1 | // This is a generated file!
2 |
3 | export * from 'fuse/next/server'
4 | export { __internal_execute as execute } from 'fuse/next/server'
5 |
--------------------------------------------------------------------------------
/examples/ecommerce/next.config.js:
--------------------------------------------------------------------------------
1 | const { nextFusePlugin } = require('fuse/next/plugin')
2 |
3 | /** @type {import('next').NextConfig} */
4 | const nextConfig = nextFusePlugin({})({})
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/examples/ecommerce/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fuse-examples/ecommerce",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "typecheck": "tsc"
11 | },
12 | "dependencies": {
13 | "@graphql-typed-document-node/core": "^3.2.0",
14 | "graphql": "^16.8.1",
15 | "next": "14.0.3",
16 | "react": "^18",
17 | "react-dom": "^18"
18 | },
19 | "devDependencies": {
20 | "@0no-co/graphqlsp": "^1.0.2",
21 | "@types/node": "^20",
22 | "@types/react": "^18",
23 | "@types/react-dom": "^18",
24 | "@types/webpack-env": "^1.18.4",
25 | "fuse": "workspace:*",
26 | "typescript": "^5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/ecommerce/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/ecommerce/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/ecommerce/schema.graphql:
--------------------------------------------------------------------------------
1 | type Cart {
2 | id: ID
3 | items: [CartItem!]
4 | }
5 |
6 | type CartItem {
7 | product: Product!
8 | quantity: Int!
9 | }
10 |
11 | type Category {
12 | name: String!
13 | products: [Product!]!
14 | }
15 |
16 | """
17 | A date string, such as 2007-12-03, compliant with the `full-date` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.
18 | """
19 | scalar Date
20 |
21 | """
22 | The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
23 | """
24 | scalar JSON
25 |
26 | type Mutation {
27 | _version: String!
28 | addToCart(productId: ID!, quantity: Int = 1): Cart
29 | }
30 |
31 | interface Node {
32 | id: ID!
33 | }
34 |
35 | type Product implements Node {
36 | description: String
37 | id: ID!
38 | image: String!
39 | name: String!
40 | price: Float!
41 | }
42 |
43 | type Query {
44 | _version: String!
45 | categories: [Category!]!
46 | myCart: Cart
47 | node(id: ID!): Node
48 | nodes(ids: [ID!]!): [Node]!
49 | product(id: ID!): Product
50 | }
--------------------------------------------------------------------------------
/examples/ecommerce/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "Bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "types": [
17 | "webpack-env" // here
18 | ],
19 | "plugins": [
20 | {
21 | "name": "next"
22 | },
23 | {
24 | "name": "@0no-co/graphqlsp",
25 | "schema": "./schema.graphql",
26 | "disableTypegen": true,
27 | "templateIsCallExpression": true,
28 | "template": "graphql"
29 | }
30 | ],
31 | "paths": {
32 | "@/*": ["./*"]
33 | }
34 | },
35 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
36 | "exclude": ["node_modules"]
37 | }
38 |
--------------------------------------------------------------------------------
/examples/ecommerce/types/Cart.ts:
--------------------------------------------------------------------------------
1 | import { addMutationFields, addQueryFields, objectType } from 'fuse'
2 | import { ProductNode } from './Product'
3 |
4 | type CartItem = {
5 | productId: number
6 | quantity: number
7 | }
8 |
9 | interface Cart {
10 | id: number
11 | products: Array
12 | }
13 |
14 | const CartItemObject = objectType({
15 | name: 'CartItem',
16 | fields: (t) => ({
17 | quantity: t.exposeInt('quantity', { nullable: false }),
18 | product: t.field({
19 | nullable: false,
20 | type: ProductNode,
21 | // We only return the product id here and let the ProductNode
22 | // automatically resolve everything
23 | resolve: (p) => p.productId,
24 | }),
25 | }),
26 | })
27 |
28 | const CartObject = objectType({
29 | name: 'Cart',
30 | fields: (t) => ({
31 | id: t.exposeID('id'),
32 | items: t.field({
33 | type: [CartItemObject],
34 | resolve: (parent) => parent.products,
35 | }),
36 | }),
37 | })
38 |
39 | addMutationFields((t) => ({
40 | addToCart: t.field({
41 | type: CartObject,
42 | args: {
43 | productId: t.arg.id({ required: true }),
44 | quantity: t.arg.int({ defaultValue: 1 }),
45 | },
46 | resolve: async (_, args, ctx) => {
47 | const result = await fetch('https://fakestoreapi.com/carts', {
48 | method: 'POST',
49 | body: JSON.stringify({
50 | userId: ctx.userId,
51 | date: '2020-01-02',
52 | products: [{ productId: args.productId, quantity: args.quantity }],
53 | }),
54 | }).then((res) => res.json())
55 |
56 | return {
57 | ...result,
58 | products: [{ productId: args.productId, quantity: args.quantity }],
59 | }
60 | },
61 | }),
62 | }))
63 |
64 | addQueryFields((t) => ({
65 | myCart: t.field({
66 | type: CartObject,
67 | resolve: async (_, __, ctx) => {
68 | const carts = await fetch(
69 | 'https://fakestoreapi.com/carts/user/' + ctx.userId,
70 | ).then((x) => x.json())
71 | return carts[0]
72 | },
73 | }),
74 | }))
75 |
--------------------------------------------------------------------------------
/examples/ecommerce/types/Category.ts:
--------------------------------------------------------------------------------
1 | import { addQueryFields, objectType } from 'fuse'
2 |
3 | export const CategoryObject = objectType<{ name: string }>({
4 | name: 'Category',
5 | fields: (t) => ({
6 | name: t.exposeString('name', { nullable: false }),
7 | }),
8 | })
9 |
10 | addQueryFields((t) => ({
11 | categories: t.field({
12 | type: [CategoryObject],
13 | nullable: false,
14 | resolve: async (_, args) => {
15 | const categories = await fetch(
16 | `https://fakestoreapi.com/products/categories`,
17 | ).then((x) => x.json())
18 |
19 | return categories.map((name: string) => ({ name }))
20 | },
21 | }),
22 | }))
23 |
--------------------------------------------------------------------------------
/examples/ecommerce/types/Product.ts:
--------------------------------------------------------------------------------
1 | import { addObjectFields, node } from 'fuse'
2 | import { CategoryObject } from './Category'
3 |
4 | interface Product {
5 | id: number
6 | title: string
7 | price: number
8 | description: string
9 | image: string
10 | }
11 |
12 | export const ProductNode = node({
13 | name: 'Product',
14 | load: (ids) => getProducts(ids),
15 | fields: (t) => ({
16 | name: t.exposeString('title', { nullable: false }),
17 | price: t.exposeFloat('price', { nullable: false }),
18 | description: t.exposeString('description'),
19 | image: t.exposeString('image', { nullable: false }),
20 | }),
21 | })
22 |
23 | addObjectFields(CategoryObject, (t) => ({
24 | products: t.loadableList({
25 | nullable: false,
26 | type: ProductNode,
27 | resolve: (parent) => {
28 | return parent.name
29 | },
30 | load: async (keys) => {
31 | const result = await Promise.allSettled(
32 | keys.map((key) =>
33 | fetch(`https://fakestoreapi.com/products/category/` + key).then((x) =>
34 | x.json(),
35 | ),
36 | ),
37 | )
38 | return result.map((x) =>
39 | x.status === 'fulfilled' ? x.value : new Error(x.reason),
40 | )
41 | },
42 | }),
43 | }))
44 |
45 | async function getProducts(ids: (number | string)[]) {
46 | const result = await Promise.allSettled(
47 | ids.map((id) =>
48 | fetch(`https://fakestoreapi.com/products/` + id).then((x) => x.json()),
49 | ),
50 | )
51 | return result.map((x) =>
52 | x.status === 'fulfilled' ? x.value : new Error(x.reason),
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/examples/ecommerce/types/context.ts:
--------------------------------------------------------------------------------
1 | import 'fuse'
2 |
3 | declare module 'fuse' {
4 | export interface UserContext {
5 | userId: string
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/spacex/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/examples/spacex/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/spacex/README.md:
--------------------------------------------------------------------------------
1 | # SpaceX
2 |
3 | This API can be run locally with `pnpm dev` or can be viewed [as deployed here](https://spacex-fuse.vercel.app/).
4 |
--------------------------------------------------------------------------------
/examples/spacex/app/api/fuse/route.ts:
--------------------------------------------------------------------------------
1 | import { createAPIRouteHandler } from 'fuse/next'
2 |
3 | const layer = createAPIRouteHandler({
4 | context: (req) => ({ user: true }),
5 | })
6 |
7 | export const GET = layer
8 | export const POST = layer
9 |
--------------------------------------------------------------------------------
/examples/spacex/app/client/page.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | min-height: 100vh;
6 | }
7 |
8 | .list {
9 | padding: 0;
10 | margin: 0;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/spacex/app/client/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 |
5 | import { graphql } from '@/fuse'
6 | import { LaunchItem } from '@/components/LaunchItem'
7 | import { LaunchDetails } from '@/components/LaunchDetails'
8 | import styles from './page.module.css'
9 | import { PageNumbers } from '@/components/PageNumbers'
10 |
11 | import { useQuery } from '@/fuse/client'
12 | import { useSearchParams } from 'next/navigation'
13 |
14 | export default function Page() {
15 | return (
16 |
17 | SpaceX Launches
18 | Loading launches...}>
19 |
20 |
21 |
22 | )
23 | }
24 |
25 | const LaunchesQuery = graphql(`
26 | query Launches_SSR($limit: Int, $offset: Int) {
27 | launches(limit: $limit, offset: $offset) {
28 | nodes {
29 | id
30 | ...LaunchFields
31 | }
32 | ...TotalCountFields
33 | }
34 | }
35 | `)
36 |
37 | function Launches() {
38 | const searchparams = useSearchParams()
39 |
40 | const selected = searchparams!.get('selected')
41 | const offset = searchparams!.has('offset')
42 | ? Number(searchparams!.get('offset'))
43 | : 0
44 |
45 | const [result] = useQuery({
46 | query: LaunchesQuery,
47 | variables: { limit: 10, offset },
48 | })
49 |
50 | return (
51 | <>
52 |
53 | {result.data?.launches.nodes.map(
54 | (node) => node && ,
55 | )}
56 |
57 | {result.data && (
58 |
59 | )}
60 | Loading details...}>
61 | {selected && }
62 |
63 | >
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/examples/spacex/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/examples/spacex/app/favicon.ico
--------------------------------------------------------------------------------
/examples/spacex/app/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | max-width: 100vw;
4 | overflow-x: hidden;
5 | }
6 |
7 | body {
8 | color: black;
9 | }
10 |
11 | a {
12 | color: inherit;
13 | text-decoration: none;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/spacex/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Inter } from 'next/font/google'
2 | import './globals.css'
3 | import { DatalayerProvider } from '@/components/DatalayerProvider'
4 |
5 | const inter = Inter({ subsets: ['latin'] })
6 |
7 | export default function RootLayout({
8 | children,
9 | }: {
10 | children: React.ReactNode
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/examples/spacex/app/page.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | min-height: 100vh;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/spacex/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 |
5 | import styles from './page.module.css'
6 | import Link from 'next/link'
7 |
8 | export default function Page() {
9 | return (
10 |
11 | Welcome to Fuse
12 | Try the Streaming SSR example
13 | Try the RSC example
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/examples/spacex/app/rsc/page.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | min-height: 100vh;
6 | }
7 |
8 | .list {
9 | padding: 0;
10 | margin: 0;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/spacex/app/rsc/page.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import { graphql } from '@/fuse'
4 | import { execute } from '@/fuse/server'
5 | import { LaunchItem } from '@/components/LaunchItem'
6 | import { headers } from 'next/headers'
7 |
8 | import styles from './page.module.css'
9 | import { PageNumbers } from '@/components/PageNumbers'
10 | import { LaunchDetails } from '@/components/LaunchDetails'
11 |
12 | const LaunchesQuery = graphql(`
13 | query Launches_RSC($limit: Int, $offset: Int) {
14 | launches(limit: $limit, offset: $offset) {
15 | nodes {
16 | id
17 | ...LaunchFields
18 | }
19 | ...TotalCountFields
20 | }
21 | }
22 | `)
23 |
24 | export default async function Page({
25 | searchParams,
26 | }: {
27 | searchParams: { offset: string; selected?: string }
28 | }) {
29 | const selectedLaunch = searchParams.selected
30 | const offset = Number(searchParams.offset || 0)
31 |
32 | const result = await execute({
33 | query: LaunchesQuery,
34 | variables: { limit: 10, offset },
35 | context: () => ({ user: true }),
36 | })
37 |
38 | return (
39 |
40 | SpaceX Launches
41 |
42 | {result.data?.launches.nodes.map(
43 | (node) => node && ,
44 | )}
45 |
46 | {result.data && (
47 |
52 | )}
53 | {selectedLaunch && (
54 | Loading launch...}>
55 |
56 |
57 | )}
58 |
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/examples/spacex/components/DatalayerProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Provider, createClient } from '@/fuse/client'
4 | import React, { Suspense } from 'react'
5 |
6 | export const DatalayerProvider = (props: any) => {
7 | const [client, ssr] = React.useMemo(() => {
8 | const { client, ssr } = createClient({
9 | url:
10 | process.env.NODE_ENV === 'production'
11 | ? 'https://spacex-fuse.vercel.app/api/fuse'
12 | : 'http://localhost:3000/api/fuse',
13 | })
14 |
15 | return [client, ssr]
16 | }, [])
17 |
18 | return (
19 |
20 | {props.children}
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/examples/spacex/components/LaunchDetails.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { graphql } from '@/fuse'
4 | import { useQuery } from '@/fuse/client'
5 | import { LaunchSite } from './LaunchSite'
6 | import { usePathname, useRouter } from 'next/navigation'
7 |
8 | const LaunchDetailsQuery = graphql(`
9 | query LaunchDetails($id: ID!) {
10 | node(id: $id) {
11 | ... on Launch {
12 | id
13 | name
14 | details
15 | launchDate
16 | image
17 | site {
18 | ...LaunchSiteFields
19 | }
20 | rocket {
21 | cost
22 | country
23 | company
24 | description
25 | }
26 | }
27 | }
28 | }
29 | `)
30 |
31 | export const LaunchDetails = (props: { id: string }) => {
32 | const [result] = useQuery({
33 | query: LaunchDetailsQuery,
34 | variables: { id: props.id },
35 | })
36 |
37 | const router = useRouter()
38 | const pathname = usePathname()
39 |
40 | if (result.data?.node?.__typename !== 'Launch') return null
41 |
42 | const { node } = result.data
43 |
44 | return (
45 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/examples/spacex/components/LaunchItem.module.css:
--------------------------------------------------------------------------------
1 | .item {
2 | cursor: pointer;
3 | display: flex;
4 | align-items: center;
5 | padding: 16px;
6 | list-style-type: none;
7 | }
8 |
9 | .badge {
10 | width: 48px;
11 | height: 48px;
12 | margin-right: 8px;
13 | }
14 |
15 | .info {
16 | display: flex;
17 | flex-direction: column;
18 | }
19 |
20 | .launchTitle {
21 | margin: 0;
22 | margin-bottom: 4px;
23 | }
24 |
25 | .launchDate {
26 | margin: 0;
27 | }
28 |
--------------------------------------------------------------------------------
/examples/spacex/components/LaunchItem.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { FragmentType, graphql, useFragment } from '@/fuse'
4 | import styles from './LaunchItem.module.css'
5 | import { usePathname, useRouter } from 'next/navigation'
6 |
7 | const LaunchFields = graphql(`
8 | fragment LaunchFields on Launch {
9 | id
10 | name
11 | launchDate
12 | image
13 | }
14 | `)
15 |
16 | export const LaunchItem = (props: {
17 | launch: FragmentType
18 | }) => {
19 | const router = useRouter()
20 | const pathname = usePathname()
21 | const node = useFragment(LaunchFields, props.launch)
22 |
23 | return (
24 | router.replace(`${pathname}?selected=${node.id}`)}
28 | >
29 |
30 |
31 | {node.name}
32 | {node.launchDate && (
33 |
34 | Launched at {new Date(node.launchDate).toUTCString()}
35 |
36 | )}
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/examples/spacex/components/LaunchSite.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentType, graphql, useFragment } from '@/fuse'
2 | import { Location } from './Location'
3 |
4 | const LaunchSiteFields = graphql(`
5 | fragment LaunchSiteFields on Site {
6 | id
7 | name
8 | details
9 | status
10 | location {
11 | ...SiteLocationFields
12 | }
13 | }
14 | `)
15 |
16 | // TODO: make pretty
17 | export const LaunchSite = (props: {
18 | site: FragmentType
19 | }) => {
20 | const result = useFragment(LaunchSiteFields, props.site)
21 |
22 | return (
23 |
24 |
{result.name}
25 |
Status: {result.status}
26 |
{result.details}
27 | {result.location &&
}
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/examples/spacex/components/Location.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentType, graphql, useFragment } from '@/fuse'
2 |
3 | const SiteLocationFields = graphql(`
4 | fragment SiteLocationFields on Location {
5 | latitude
6 | longitude
7 | name
8 | region
9 | }
10 | `)
11 |
12 | export const Location = (props: {
13 | location: FragmentType
14 | }) => {
15 | const result = useFragment(SiteLocationFields, props.location)
16 |
17 | return (
18 |
19 |
20 | {result.region} - {result.name}
21 |
22 |
23 | Coordinates {result.longitude} {result.latitude}
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/examples/spacex/components/PageNumbers.module.css:
--------------------------------------------------------------------------------
1 | .list {
2 | display: flex;
3 | margin: 0;
4 | margin-top: 12px;
5 | list-style: none;
6 | }
7 |
8 | .pageNumber {
9 | background: none;
10 | color: inherit;
11 | border: none;
12 | padding: 0;
13 | font: inherit;
14 | cursor: pointer;
15 | outline: inherit;
16 | padding: 8px;
17 | }
18 |
19 | .active {
20 | text-decoration: underline;
21 | }
22 |
--------------------------------------------------------------------------------
/examples/spacex/components/PageNumbers.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useRouter, usePathname } from 'next/navigation'
4 |
5 | import { FragmentType, graphql, useFragment } from '@/fuse'
6 |
7 | import styles from './PageNumbers.module.css'
8 | import { sayHello } from './actions/sayHello'
9 |
10 | const TotalCountFields = graphql(`
11 | fragment TotalCountFields on QueryLaunchesList {
12 | totalCount
13 | }
14 | `)
15 |
16 | export const PageNumbers = (props: {
17 | list: FragmentType
18 | limit: number
19 | offset: number
20 | }) => {
21 | const router = useRouter()
22 | const pathname = usePathname()
23 |
24 | const node = useFragment(TotalCountFields, props.list)
25 |
26 | if (!node.totalCount) return null
27 |
28 | const amountOfPages = Math.ceil(node.totalCount / props.limit)
29 | const currentPage = props.offset / props.limit
30 |
31 | const sayHelloFuse = sayHello.bind(undefined, { name: 'fuse' })
32 |
33 | return (
34 | <>
35 |
36 | {Array(amountOfPages)
37 | .fill(0)
38 | .map((_, i) => (
39 | -
40 |
50 |
51 | ))}
52 |
53 |
56 | >
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/examples/spacex/components/Rocket.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/examples/spacex/components/Rocket.tsx
--------------------------------------------------------------------------------
/examples/spacex/components/actions/sayHello.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { graphql } from '@/fuse'
4 | import { execute } from '@/fuse/server'
5 | import { redirect } from 'next/navigation'
6 |
7 | const SayHello = graphql(`
8 | mutation Hello($name: String!) {
9 | sayHello(name: $name)
10 | }
11 | `)
12 |
13 | export async function sayHello(args: { name: string }) {
14 | const result = await execute({
15 | query: SayHello,
16 | variables: { name: args.name || 'fuse' },
17 | })
18 |
19 | console.log(result.data?.sayHello)
20 |
21 | redirect('/')
22 | }
23 |
--------------------------------------------------------------------------------
/examples/spacex/fuse/client.ts:
--------------------------------------------------------------------------------
1 | // This is a generated file!
2 |
3 | export * from 'fuse/next/client'
4 |
--------------------------------------------------------------------------------
/examples/spacex/fuse/index.ts:
--------------------------------------------------------------------------------
1 | export * from './fragment-masking'
2 | export * from './gql'
3 |
--------------------------------------------------------------------------------
/examples/spacex/fuse/pages.ts:
--------------------------------------------------------------------------------
1 | // This is a generated file!
2 |
3 | export * from 'fuse/next/pages'
4 |
--------------------------------------------------------------------------------
/examples/spacex/fuse/server.ts:
--------------------------------------------------------------------------------
1 | // This is a generated file!
2 |
3 | export * from 'fuse/next/server'
4 | export { __internal_execute as execute } from 'fuse/next/server'
5 |
--------------------------------------------------------------------------------
/examples/spacex/next.config.js:
--------------------------------------------------------------------------------
1 | const { nextFusePlugin } = require('fuse/next/plugin')
2 |
3 | /** @type {import('next').NextConfig} */
4 | const nextConfig = nextFusePlugin()({})
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/examples/spacex/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fuse-examples/spacex",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "typecheck": "tsc"
11 | },
12 | "dependencies": {
13 | "@graphql-typed-document-node/core": "^3.2.0",
14 | "graphql": "^16.8.1",
15 | "next": "14.0.3",
16 | "react": "^18",
17 | "react-dom": "^18"
18 | },
19 | "devDependencies": {
20 | "@0no-co/graphqlsp": "^1.0.2",
21 | "@types/node": "^20",
22 | "@types/react": "^18",
23 | "@types/react-dom": "^18",
24 | "@types/webpack-env": "^1.18.4",
25 | "fuse": "workspace:*",
26 | "typescript": "^5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/spacex/pages/test.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { useRouter } from 'next/router'
5 | import {
6 | useQuery,
7 | withGraphQLClient,
8 | initGraphQLClient,
9 | ssrExchange,
10 | cacheExchange,
11 | fetchExchange,
12 | } from '@/fuse/pages'
13 |
14 | import { graphql } from '@/fuse'
15 |
16 | import styles from '../app/client/page.module.css'
17 |
18 | function Page() {
19 | return (
20 |
21 | SpaceX Launches
22 |
23 |
24 | )
25 | }
26 |
27 | const LaunchesQuery = graphql(`
28 | query PageLaunches($limit: Int, $offset: Int) {
29 | launches(limit: $limit, offset: $offset) {
30 | nodes {
31 | id
32 | name
33 | }
34 | totalCount
35 | }
36 | }
37 | `)
38 |
39 | function Launches() {
40 | const router = useRouter()
41 |
42 | const offset = router.query['offset'] ? Number(router.query['offset']) : 0
43 |
44 | const [result] = useQuery({
45 | query: LaunchesQuery,
46 | variables: { limit: 10, offset },
47 | })
48 |
49 | return (
50 | <>
51 |
52 | {result.data?.launches.nodes.map(
53 | (node) => node && - {node.name}
,
54 | )}
55 |
56 | {result.data?.launches.totalCount}
57 | >
58 | )
59 | }
60 |
61 | export async function getServerSideProps() {
62 | const ssrCache = ssrExchange({ isClient: false })
63 | const client = initGraphQLClient({
64 | url:
65 | process.env.NODE_ENV === 'production'
66 | ? 'https://spacex-fuse.vercel.app/api/fuse'
67 | : 'http://localhost:3000/api/fuse',
68 | exchanges: [cacheExchange, ssrCache, fetchExchange],
69 | })
70 |
71 | await client.query(LaunchesQuery, { limit: 10, offset: 0 }).toPromise()
72 |
73 | const graphqlState = ssrCache.extractData()
74 |
75 | return {
76 | props: {
77 | graphqlState,
78 | },
79 | }
80 | }
81 |
82 | export default withGraphQLClient(() => ({
83 | url:
84 | process.env.NODE_ENV === 'production'
85 | ? 'https://spacex-fuse.vercel.app/api/fuse'
86 | : 'http://localhost:3000/api/fuse',
87 | }))(Page)
88 |
--------------------------------------------------------------------------------
/examples/spacex/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/spacex/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/spacex/schema.graphql:
--------------------------------------------------------------------------------
1 | """
2 | A date string, such as 2007-12-03, compliant with the `full-date` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.
3 | """
4 | scalar Date
5 |
6 | """
7 | The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
8 | """
9 | scalar JSON
10 |
11 | type Launch implements Node {
12 | details: String
13 | id: ID!
14 | image: String!
15 | launchDate: String!
16 | name: String!
17 | rocket: Rocket!
18 | site: Site!
19 | }
20 |
21 | type Location {
22 | coordinates: [Float!]
23 | latitude: Float
24 | longitude: Float
25 | name: String
26 | region: String
27 | }
28 |
29 | type Mutation {
30 | _version: String!
31 | sayHello(name: String): String
32 | }
33 |
34 | interface Node {
35 | id: ID!
36 | }
37 |
38 | type Query {
39 | _version: String!
40 | launch(id: ID!): Launch
41 | launches(limit: Int, offset: Int): QueryLaunchesList!
42 | node(id: ID!): Node
43 | nodes(ids: [ID!]!): [Node]!
44 | rocket(id: ID!): Rocket
45 | site(id: ID!): Site
46 | }
47 |
48 | type QueryLaunchesList {
49 | nodes: [Launch]!
50 | totalCount: Int
51 | }
52 |
53 | type Rocket implements Node {
54 | company: String
55 | cost: Int
56 | country: String
57 | description: String
58 | id: ID!
59 | }
60 |
61 | type Site implements Node {
62 | details: String
63 | id: ID!
64 | location: Location
65 | name: String
66 | status: SiteStatus
67 | }
68 |
69 | enum SiteStatus {
70 | ACTIVE
71 | INACTIVE
72 | UNKNOWN
73 | }
74 |
--------------------------------------------------------------------------------
/examples/spacex/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "Bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "types": [
17 | "webpack-env" // here
18 | ],
19 | "plugins": [
20 | {
21 | "name": "next"
22 | },
23 | {
24 | "name": "@0no-co/graphqlsp",
25 | "schema": "./schema.graphql",
26 | "disableTypegen": true,
27 | "templateIsCallExpression": true,
28 | "template": "graphql"
29 | }
30 | ],
31 | "paths": {
32 | "@/*": ["./*"]
33 | }
34 | },
35 | "include": [
36 | "next-env.d.ts",
37 | "**/*.ts",
38 | "**/*.tsx",
39 | ".next/types/**/*.ts",
40 | "types/scopes.ts"
41 | ],
42 | "exclude": ["node_modules"]
43 | }
44 |
--------------------------------------------------------------------------------
/examples/spacex/types/launch/Rocket.ts:
--------------------------------------------------------------------------------
1 | import { node, addNodeFields } from 'fuse'
2 | import { LaunchNode } from '../Launch'
3 |
4 | interface Rocket {
5 | id: string
6 | cost_per_launch: number
7 | country: string
8 | company: string
9 | description: string
10 | }
11 |
12 | const RocketNode = node({
13 | name: 'Rocket',
14 | async load(ids) {
15 | const rockets = await Promise.allSettled(
16 | ids.map((id) =>
17 | fetch('https://api.spacexdata.com/v3/rockets/' + id, {
18 | method: 'GET',
19 | }).then((x) => x.json()),
20 | ),
21 | )
22 |
23 | return await Promise.all(
24 | rockets.map((rocket) =>
25 | rocket.status === 'fulfilled' ? rocket.value : new Error(rocket.reason),
26 | ),
27 | )
28 | },
29 | fields: (t) => ({
30 | cost: t.exposeInt('cost_per_launch'),
31 | country: t.exposeString('country'),
32 | company: t.exposeString('company'),
33 | description: t.exposeString('description'),
34 | }),
35 | })
36 |
37 | addNodeFields(LaunchNode, (t) => ({
38 | rocket: t.field({
39 | type: RocketNode,
40 | nullable: false,
41 | resolve: (parent) => parent.rocket.rocket_id,
42 | }),
43 | }))
44 |
--------------------------------------------------------------------------------
/examples/spacex/types/scopes.ts:
--------------------------------------------------------------------------------
1 | import 'fuse'
2 | import { defineAuthScopes, Scopes } from 'fuse'
3 |
4 | declare module 'fuse' {
5 | export interface Scopes {
6 | isLoggedIn: boolean
7 | }
8 | }
9 |
10 | defineAuthScopes((ctx) => ({ isLoggedIn: !!ctx.user }))
11 |
--------------------------------------------------------------------------------
/examples/standalone/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | build
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/examples/standalone/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | export default {
18 | // other rules...
19 | parserOptions: {
20 | ecmaVersion: 'latest',
21 | sourceType: 'module',
22 | project: ['./tsconfig.json', './tsconfig.node.json'],
23 | tsconfigRootDir: __dirname,
24 | },
25 | }
26 | ```
27 |
28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
31 |
--------------------------------------------------------------------------------
/examples/standalone/_context.ts:
--------------------------------------------------------------------------------
1 | import type { GetContext, InitialContext } from 'fuse'
2 |
3 | declare module 'fuse' {
4 | export interface UserContext {
5 | ua: string | null
6 | }
7 | }
8 |
9 | export const getContext = (
10 | ctx: InitialContext,
11 | ): GetContext<{ ua: string | null }> => {
12 | return {
13 | ua: ctx.request.headers.get('user-agent'),
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/standalone/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/standalone/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fuse-examples/standalone",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "pnpm concurrently 'fuse dev' 'vite --force'",
8 | "build": "vite build && fuse build"
9 | },
10 | "dependencies": {
11 | "gql.tada": "1.0.1",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@0no-co/graphqlsp": "^1.0.2",
17 | "@graphql-typed-document-node/core": "^3.2.0",
18 | "@types/react": "^18.2.37",
19 | "@types/react-dom": "^18.2.15",
20 | "@vitejs/plugin-react": "^4.2.0",
21 | "concurrently": "^8.2.2",
22 | "fuse": "file:../../packages/core",
23 | "typescript": "^5.2.2",
24 | "vite": "^5.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/standalone/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/standalone/schema.graphql:
--------------------------------------------------------------------------------
1 | """
2 | A date string, such as 2007-12-03, compliant with the `full-date` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.
3 | """
4 | scalar Date
5 |
6 | """
7 | The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
8 | """
9 | scalar JSON
10 |
11 | type Launch implements Node {
12 | details: String
13 | id: ID!
14 | image: String!
15 | launchDate: String!
16 | name: String!
17 | rocket: Rocket!
18 | site: Site!
19 | }
20 |
21 | type Location {
22 | coordinates: [Float!]
23 | latitude: Float
24 | longitude: Float
25 | name: String
26 | region: String
27 | }
28 |
29 | type Mutation {
30 | _version: String!
31 | sayHello(name: String): String
32 | }
33 |
34 | interface Node {
35 | id: ID!
36 | }
37 |
38 | type Query {
39 | _version: String!
40 | launch(id: ID!): Launch
41 | launches(limit: Int, offset: Int): QueryLaunchesList!
42 | node(id: ID!): Node
43 | nodes(ids: [ID!]!): [Node]!
44 | rocket(id: ID!): Rocket
45 | site(id: ID!): Site
46 | }
47 |
48 | type QueryLaunchesList {
49 | nodes: [Launch]!
50 | totalCount: Int
51 | }
52 |
53 | type Rocket implements Node {
54 | company: String
55 | cost: Int
56 | country: String
57 | description: String
58 | id: ID!
59 | }
60 |
61 | type Site implements Node {
62 | details: String
63 | id: ID!
64 | location: Location
65 | name: String
66 | status: SiteStatus
67 | }
68 |
69 | enum SiteStatus {
70 | ACTIVE
71 | INACTIVE
72 | UNKNOWN
73 | }
74 |
--------------------------------------------------------------------------------
/examples/standalone/src/components/LaunchDetails.tsx:
--------------------------------------------------------------------------------
1 | import { graphql, useQuery } from '../fuse'
2 | import { LaunchSite, LaunchSiteFields } from './LaunchSite'
3 |
4 | const LaunchDetailsQuery = graphql(
5 | `
6 | query LaunchDetails($id: ID!) {
7 | node(id: $id) {
8 | __typename
9 | ... on Launch {
10 | id
11 | name
12 | details
13 | launchDate
14 | __typename
15 | site {
16 | ...LaunchSiteFields
17 | }
18 | }
19 | }
20 | }
21 | `,
22 | [LaunchSiteFields],
23 | )
24 |
25 | export const LaunchDetails = (props: { id: string; deselect: () => void }) => {
26 | const [result] = useQuery({
27 | query: LaunchDetailsQuery,
28 | variables: { id: props.id },
29 | })
30 |
31 | if (result.data?.node?.__typename !== 'Launch') return null
32 |
33 | const { node } = result.data
34 |
35 | return (
36 |
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/examples/standalone/src/components/LaunchItem.module.css:
--------------------------------------------------------------------------------
1 | .item {
2 | cursor: pointer;
3 | display: flex;
4 | align-items: center;
5 | padding: 16px;
6 | list-style-type: none;
7 | }
8 |
9 | .badge {
10 | width: 48px;
11 | height: 48px;
12 | margin-right: 8px;
13 | }
14 |
15 | .info {
16 | display: flex;
17 | flex-direction: column;
18 | }
19 |
20 | .launchTitle {
21 | margin: 0;
22 | margin-bottom: 4px;
23 | }
24 |
25 | .launchDate {
26 | margin: 0;
27 | }
28 |
--------------------------------------------------------------------------------
/examples/standalone/src/components/LaunchItem.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentOf, graphql, readFragment } from '../fuse'
2 | import styles from './LaunchItem.module.css'
3 |
4 | export const LaunchFields = graphql(`
5 | fragment LaunchFields on Launch {
6 | name
7 | launchDate
8 | image
9 | }
10 | `)
11 |
12 | export const LaunchItem = (props: {
13 | launch: FragmentOf
14 | select: () => void
15 | }) => {
16 | const node = readFragment(LaunchFields, props.launch)
17 | return (
18 |
19 |
20 |
21 | {node.name}
22 | {node.launchDate && (
23 |
24 | Launched at {new Date(node.launchDate).toUTCString()}
25 |
26 | )}
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/examples/standalone/src/components/LaunchSite.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentOf, graphql, readFragment } from '../fuse'
2 | import { Location, SiteLocationFields } from './Location'
3 |
4 | export const LaunchSiteFields = graphql(
5 | `
6 | fragment LaunchSiteFields on Site {
7 | id
8 | name
9 | details
10 | status
11 | location {
12 | ...SiteLocationFields
13 | }
14 | }
15 | `,
16 | [SiteLocationFields],
17 | )
18 |
19 | export const LaunchSite = (props: {
20 | site: FragmentOf
21 | }) => {
22 | const result = readFragment(LaunchSiteFields, props.site)
23 |
24 | return (
25 |
26 |
{result.name}
27 |
Status: {result.status}
28 |
{result.details}
29 | {result.location &&
}
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/examples/standalone/src/components/Location.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentOf, graphql, readFragment } from '../fuse'
2 |
3 | export const SiteLocationFields = graphql(`
4 | fragment SiteLocationFields on Location {
5 | latitude
6 | longitude
7 | name
8 | region
9 | }
10 | `)
11 |
12 | export const Location = (props: {
13 | location: FragmentOf
14 | }) => {
15 | const result = readFragment(SiteLocationFields, props.location)
16 |
17 | return (
18 |
19 |
20 | {result.region} - {result.name}
21 |
22 |
23 | Coordinates {result.longitude} {result.latitude}
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/examples/standalone/src/components/PageNumbers.module.css:
--------------------------------------------------------------------------------
1 | .list {
2 | display: flex;
3 | margin: 0;
4 | margin-top: 12px;
5 | list-style: none;
6 | }
7 |
8 | .pageNumber {
9 | background: none;
10 | color: inherit;
11 | border: none;
12 | padding: 0;
13 | font: inherit;
14 | cursor: pointer;
15 | outline: inherit;
16 | padding: 8px;
17 | }
18 |
19 | .active {
20 | text-decoration: underline;
21 | }
22 |
--------------------------------------------------------------------------------
/examples/standalone/src/components/PageNumbers.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentType, graphql, useFragment } from '../fuse'
2 |
3 | import styles from './PageNumbers.module.css'
4 |
5 | export const TotalCountFields = graphql(`
6 | fragment TotalCountFields on QueryLaunchesList {
7 | totalCount
8 | }
9 | `)
10 |
11 | export const PageNumbers = (props: {
12 | list: FragmentType
13 | limit: number
14 | offset: number
15 | setOffset: (x: number) => void
16 | }) => {
17 | const node = useFragment(TotalCountFields, props.list)
18 |
19 | if (!node.totalCount) return null
20 |
21 | const amountOfPages = Math.ceil(node.totalCount / props.limit)
22 | const currentPage = props.offset / props.limit
23 |
24 | return (
25 |
26 | {Array(amountOfPages)
27 | .fill(0)
28 | .map((_, i) => (
29 | -
30 |
38 |
39 | ))}
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/examples/standalone/src/fuse/index.ts:
--------------------------------------------------------------------------------
1 | // This is a generated file!
2 |
3 | export * from './tada'
4 | export * from 'fuse/client'
5 |
--------------------------------------------------------------------------------
/examples/standalone/src/fuse/tada.ts:
--------------------------------------------------------------------------------
1 | import { initGraphQLTada } from 'gql.tada'
2 | import type { introspection } from './introspection'
3 |
4 | export const graphql = initGraphQLTada<{
5 | introspection: typeof introspection
6 | }>()
7 |
8 | export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'
9 | export type { FragmentOf as FragmentType } from 'gql.tada'
10 | export { readFragment } from 'gql.tada'
11 | export { readFragment as useFragment } from 'gql.tada'
12 |
--------------------------------------------------------------------------------
/examples/standalone/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | width: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 | a:hover {
23 | color: #535bf2;
24 | }
25 |
26 | body {
27 | margin: 0;
28 | display: flex;
29 | place-items: center;
30 | min-width: 320px;
31 | min-height: 100vh;
32 | }
33 |
34 | h1 {
35 | font-size: 3.2em;
36 | line-height: 1.1;
37 | }
38 |
39 | button {
40 | border-radius: 8px;
41 | border: 1px solid transparent;
42 | padding: 0.6em 1.2em;
43 | font-size: 1em;
44 | font-weight: 500;
45 | font-family: inherit;
46 | background-color: #1a1a1a;
47 | cursor: pointer;
48 | transition: border-color 0.25s;
49 | }
50 | button:hover {
51 | border-color: #646cff;
52 | }
53 | button:focus,
54 | button:focus-visible {
55 | outline: 4px auto -webkit-focus-ring-color;
56 | }
57 |
58 | @media (prefers-color-scheme: light) {
59 | :root {
60 | color: #213547;
61 | background-color: #ffffff;
62 | width: 100%;
63 | }
64 | a:hover {
65 | color: #747bff;
66 | }
67 | button {
68 | background-color: #f9f9f9;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/examples/standalone/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { createClient, Provider } from './fuse'
4 | import App from './App.tsx'
5 | import './index.css'
6 |
7 | const client = createClient({
8 | url: 'http://localhost:4000/graphql',
9 | })
10 |
11 | ReactDOM.createRoot(document.getElementById('root')!).render(
12 |
13 |
14 |
15 |
16 | ,
17 | )
18 |
--------------------------------------------------------------------------------
/examples/standalone/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/standalone/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 | "plugins": [
17 | {
18 | "name": "@0no-co/graphqlsp",
19 | "schema": "./schema.graphql",
20 | "tadaOutputLocation": "./src/fuse/introspection.ts"
21 | }
22 | ],
23 | "paths": {
24 | "@/*": ["./*"]
25 | },
26 |
27 | /* Linting */
28 | "strict": true,
29 | "noUnusedLocals": true,
30 | "noUnusedParameters": true,
31 | "noFallthroughCasesInSwitch": true
32 | },
33 | "include": ["src", "types", "_context.ts"],
34 | "references": [{ "path": "./tsconfig.node.json" }]
35 | }
36 |
--------------------------------------------------------------------------------
/examples/standalone/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/standalone/types/launch/Rocket.ts:
--------------------------------------------------------------------------------
1 | import { node, addNodeFields } from 'fuse'
2 | import { LaunchNode } from '../Launch'
3 |
4 | interface Rocket {
5 | id: string
6 | cost_per_launch: number
7 | country: string
8 | company: string
9 | description: string
10 | }
11 |
12 | const RocketNode = node({
13 | name: 'Rocket',
14 | async load(ids) {
15 | const rockets = await Promise.allSettled(
16 | ids.map((id) =>
17 | fetch('https://api.spacexdata.com/v3/rockets/' + id, {
18 | method: 'GET',
19 | }).then((x) => x.json()),
20 | ),
21 | )
22 |
23 | return await Promise.all(
24 | rockets.map((rocket) =>
25 | rocket.status === 'fulfilled' ? rocket.value : new Error(rocket.reason),
26 | ),
27 | )
28 | },
29 | fields: (t) => ({
30 | cost: t.exposeInt('cost_per_launch'),
31 | country: t.exposeString('country'),
32 | company: t.exposeString('company'),
33 | description: t.exposeString('description'),
34 | }),
35 | })
36 |
37 | addNodeFields(LaunchNode, (t) => ({
38 | rocket: t.field({
39 | type: RocketNode,
40 | nullable: false,
41 | resolve: (parent) => parent.rocket.rocket_id,
42 | }),
43 | }))
44 |
--------------------------------------------------------------------------------
/examples/standalone/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fuse",
3 | "private": true,
4 | "scripts": {
5 | "build": "pnpm --filter fuse build && pnpm --filter create-fuse-app build",
6 | "dev": "pnpm --filter @fuse-examples/spacex dev",
7 | "prepare": "husky install && pnpm build",
8 | "website": "pnpm --filter @fuse/website dev"
9 | },
10 | "devDependencies": {
11 | "@changesets/cli": "^2.27.0",
12 | "husky": "^8.0.0",
13 | "lint-staged": "^15.1.0",
14 | "prettier": "^3.1.0",
15 | "typescript": "^5.2.2"
16 | },
17 | "prettier": {
18 | "tabWidth": 2,
19 | "trailingComma": "all",
20 | "singleQuote": true,
21 | "jsxSingleQuote": true,
22 | "semi": false,
23 | "printWidth": 80
24 | },
25 | "lint-staged": {
26 | "*.{mjs,js,jsx,ts,tsx,json,md,graphql}": "prettier --write"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # Fuse
2 |
3 | 
4 |
5 | # Getting Started
6 |
7 | When you are in the root of your app run the following command. This will
8 | install all the packages and generate the files you need.
9 |
10 | ```sh
11 | npx create-fuse-app
12 | ```
13 |
14 | Then, run `npx fuse dev` and your API will be running at `localhost:4000/graphql`!
15 |
16 | > If you are **using Next.js, you don't need to manually run `fuse dev`**. `create-fuse-app` will add a Next.js plugin to your `next.config.js/ts/mjs`` and an API route at `/api/fuse` for you to access your API. ([learn more](https://fusedata.dev/docs/setting-fuse-up-manually/nextjs))
17 |
18 | ## Querying your data layer
19 |
20 | ```tsx
21 | import { graphql } from '@/fuse'
22 | import { execute } from '@/fuse/server'
23 |
24 | const UserQuery = graphql(`
25 | query User($id: ID!) {
26 | user(id: $id) {
27 | id
28 | name
29 | }
30 | }
31 | `)
32 |
33 | export default async function Page() {
34 | const result = await execute({
35 | query: UserQuery,
36 | variables: { id: '1' },
37 | })
38 |
39 | return Welcome {result.data?.user?.name}
40 | }
41 | ```
42 |
43 | # [Docs](https://fusedata.dev/docs)
44 |
45 | **Read [the documentation](https://fusedata.dev/docs) for more information about using Fuse**.
46 |
47 | Quicklinks to some of the most-visited pages:
48 |
49 | - [Getting started](https://fusedata.dev/docs)
50 | - [Querying your API (client)](https://fusedata.dev/docs/client)
51 | - [Building your API (server)](https://fusedata.dev/docs/server/queries-and-mutations)
52 | - [Deploying your API (server)](https://fusedata.dev/docs/deployment)
53 | - [The Fuse Method](https://fusedata.dev/docs/fuse-method)
54 |
55 | # License
56 |
57 | Licensed under the MIT License, Copyright © 2023-present Stellate, Inc.
58 |
59 | See LICENSE for more information.
60 |
--------------------------------------------------------------------------------
/packages/core/client.d.ts:
--------------------------------------------------------------------------------
1 | // src/next/client.ts
2 | import {
3 | useQuery,
4 | UrqlProvider,
5 | ClientOptions,
6 | SSRExchange,
7 | Client,
8 | } from '@urql/next'
9 | export * from 'urql'
10 | export { UrqlProvider as Provider, useQuery }
11 |
12 | type Optional = Pick, K> & Omit
13 | export function createClient(opts: Optional): {
14 | client: Client
15 | ssr: SSRExchange
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/loader.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = function (code) {
4 | const { fs, resourcePath, rootContext: cwd } = this
5 |
6 | if (
7 | !resourcePath.includes('fuse/route.ts') &&
8 | !resourcePath.includes('fuse/server.ts') &&
9 | !resourcePath.includes('api/fuse.ts')
10 | )
11 | return code
12 |
13 | if (code.includes('require.context(')) {
14 | console.warn(
15 | 'Found require.context in code, this can be removed from the codebase.',
16 | )
17 | return code
18 | }
19 |
20 | let hasSrcDir = false
21 | try {
22 | fs.statSync(path.resolve(cwd, 'src'))
23 | hasSrcDir = true
24 | } catch (e) {}
25 |
26 | let hasTypesDir = false
27 | const typesDir = hasSrcDir
28 | ? path.resolve(cwd, 'src', 'types')
29 | : path.resolve(cwd, 'types')
30 | try {
31 | fs.statSync(typesDir)
32 | hasTypesDir = true
33 | } catch (e) {}
34 |
35 | if (!hasTypesDir) {
36 | return code
37 | }
38 |
39 | function getFiles(dir, files = []) {
40 | const fileList = fs.readdirSync(dir)
41 | for (const file of fileList) {
42 | const name = `${dir}/${file}`
43 | if (fs.statSync(name).isDirectory()) {
44 | getFiles(name, files)
45 | } else {
46 | files.push(name)
47 | }
48 | }
49 | return files
50 | }
51 |
52 | const results = getFiles(typesDir)
53 |
54 | code = `${results.reduce((acc, cur) => {
55 | const curPath = path.resolve(typesDir, cur)
56 | let rel = path.relative(this.context, curPath)
57 |
58 | if (rel === cur) {
59 | rel = `./${rel}`
60 | }
61 |
62 | return `${acc}import '${rel}'\n`
63 | }, '')}\n${code}`
64 |
65 | return code
66 | }
67 |
--------------------------------------------------------------------------------
/packages/core/rsc.d.ts:
--------------------------------------------------------------------------------
1 | // src/next/rsc.ts
2 | import {
3 | Client,
4 | ClientOptions,
5 | AnyVariables,
6 | GraphQLRequestParams,
7 | } from '@urql/core'
8 | import { ExecutionResult } from 'graphql'
9 | import { GraphQLParams } from 'graphql-yoga'
10 | import { UserContext } from 'fuse'
11 |
12 | export { registerUrql as registerClient } from '@urql/next/rsc'
13 | export * from '@urql/core'
14 |
15 | type Optional = Pick, K> & Omit
16 | export function createClient(opts: Optional): Client
17 |
18 | export function __internal_execute<
19 | Data = any,
20 | Variables extends AnyVariables = AnyVariables,
21 | >(
22 | request: GraphQLRequestParams & {
23 | context?: (params: GraphQLParams) => UserContext
24 | },
25 | ): Promise>
26 |
--------------------------------------------------------------------------------
/packages/core/src/adapters/bun.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { createYoga } from 'graphql-yoga'
3 | // @ts-ignore
4 | import { builder } from 'fuse'
5 | import { getYogaPlugins, wrappedContext } from '../utils/yoga-helpers'
6 |
7 | export async function main() {
8 | let ctx
9 | import.meta.glob('/types/**/*.ts', { eager: true })
10 | const context = import.meta.glob('/_context.ts', { eager: true })
11 | if (context['/_context.ts']) {
12 | const mod = context['/_context.ts']
13 | if ((mod as any).getContext) {
14 | ctx = (mod as any).getContext
15 | }
16 | }
17 |
18 | const completedSchema = builder.toSchema({})
19 |
20 | const yoga = createYoga({
21 | graphiql: false,
22 | maskedErrors: true,
23 | schema: completedSchema,
24 | // We allow batching by default
25 | batching: true,
26 | context: wrappedContext(ctx),
27 | plugins: getYogaPlugins(),
28 | })
29 |
30 | Bun.serve(
31 | // @ts-ignore this is a typing bug, it works. https://github.com/dotansimha/graphql-yoga/issues/3003
32 | yoga,
33 | )
34 | }
35 |
36 | main()
37 |
--------------------------------------------------------------------------------
/packages/core/src/adapters/cloudflare.ts:
--------------------------------------------------------------------------------
1 | import { createYoga } from 'graphql-yoga'
2 | // @ts-ignore
3 | import { builder } from 'fuse'
4 | import { getYogaPlugins, wrappedContext } from '../utils/yoga-helpers'
5 |
6 | function fetch(request) {
7 | let ctx
8 | import.meta.glob('/types/**/*.ts', { eager: true })
9 | const context = import.meta.glob('/_context.ts', { eager: true })
10 | if (context['/_context.ts']) {
11 | const mod = context['/_context.ts']
12 | if ((mod as any).getContext) {
13 | ctx = (mod as any).getContext
14 | }
15 | }
16 |
17 | const completedSchema = builder.toSchema({})
18 |
19 | const yoga = createYoga({
20 | graphiql: false,
21 | maskedErrors: true,
22 | schema: completedSchema,
23 | // We allow batching by default
24 | batching: true,
25 | context: wrappedContext(ctx),
26 | plugins: getYogaPlugins(),
27 | })
28 |
29 | return yoga.fetch(request, ctx)
30 | }
31 |
32 | export default { fetch }
33 |
--------------------------------------------------------------------------------
/packages/core/src/adapters/lambda.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | APIGatewayEvent,
3 | APIGatewayProxyResult,
4 | Context,
5 | } from 'aws-lambda'
6 | import { createYoga } from 'graphql-yoga'
7 | // @ts-ignore
8 | import { builder } from 'fuse'
9 | import { getYogaPlugins, wrappedContext } from '../utils/yoga-helpers'
10 |
11 | export async function fetch(
12 | event: APIGatewayEvent,
13 | lambdaContext: Context,
14 | ): Promise {
15 | let ctx
16 | import.meta.glob('/types/**/*.ts', { eager: true })
17 | const context = import.meta.glob('/_context.ts', { eager: true })
18 | if (context['/_context.ts']) {
19 | const mod = context['/_context.ts']
20 | if ((mod as any).getContext) {
21 | ctx = (mod as any).getContext
22 | }
23 | }
24 |
25 | const completedSchema = builder.toSchema({})
26 |
27 | const yoga = createYoga({
28 | graphiql: false,
29 | maskedErrors: true,
30 | schema: completedSchema,
31 | // We allow batching by default
32 | batching: true,
33 | context: wrappedContext(ctx),
34 | plugins: getYogaPlugins(),
35 | })
36 |
37 | const response = await yoga.fetch(
38 | event.path +
39 | '?' +
40 | new URLSearchParams(
41 | (event.queryStringParameters as Record) || {},
42 | ).toString(),
43 | {
44 | method: event.httpMethod,
45 | headers: event.headers as HeadersInit,
46 | body: event.body
47 | ? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8')
48 | : undefined,
49 | },
50 | {
51 | event,
52 | lambdaContext,
53 | },
54 | )
55 |
56 | const responseHeaders = Object.fromEntries(response.headers.entries())
57 |
58 | return {
59 | statusCode: response.status,
60 | headers: responseHeaders,
61 | body: await response.text(),
62 | isBase64Encoded: false,
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/packages/core/src/adapters/node.ts:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 | import { createYoga } from 'graphql-yoga'
3 | // @ts-ignore
4 | import { builder } from 'fuse'
5 | import { getYogaPlugins, wrappedContext } from '../utils/yoga-helpers'
6 |
7 | export async function main() {
8 | let ctx
9 | import.meta.glob('/types/**/*.ts', { eager: true })
10 | const context = import.meta.glob('/_context.ts', { eager: true })
11 | if (context['/_context.ts']) {
12 | const mod = context['/_context.ts']
13 | if ((mod as any).getContext) {
14 | ctx = (mod as any).getContext
15 | }
16 | }
17 |
18 | const completedSchema = builder.toSchema({})
19 |
20 | const yoga = createYoga({
21 | graphiql: false,
22 | maskedErrors: true,
23 | schema: completedSchema,
24 | // We allow batching by default
25 | batching: true,
26 | context: wrappedContext(ctx),
27 | plugins: getYogaPlugins(),
28 | })
29 |
30 | const server = http.createServer(yoga)
31 | server.listen(process.env.PORT || 4000)
32 | }
33 |
34 | main()
35 |
--------------------------------------------------------------------------------
/packages/core/src/client.ts:
--------------------------------------------------------------------------------
1 | import { createClient as create, fetchExchange } from 'urql'
2 | import type { Client, ClientOptions } from 'urql'
3 | import { cacheExchange } from './exchanges/cache'
4 |
5 | export * from 'urql'
6 |
7 | export { cacheExchange } from './exchanges/cache'
8 |
9 | type Optional = Pick, K> & Omit
10 | export const createClient = (
11 | opts: Optional,
12 | ): Client => {
13 | const options: ClientOptions = {
14 | ...opts,
15 | suspense: opts.suspense ?? true,
16 | exchanges: opts.exchanges || [cacheExchange, fetchExchange],
17 | }
18 | return create(options)
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/dev.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import { builder } from 'fuse'
3 | import { printSchema } from 'graphql'
4 | import { createYoga } from 'graphql-yoga'
5 |
6 | import { getYogaPlugins, wrappedContext } from './utils/yoga-helpers'
7 |
8 | // prettier-ignore
9 | const defaultQuery = /* GraphQL */ `query {
10 | _version
11 | }
12 | `
13 |
14 | export async function main() {
15 | const modules = import.meta.glob('/types/**/*.ts')
16 | const context = import.meta.glob('/_context.ts')
17 |
18 | const promises: Array = []
19 | let ctx
20 | if (context['/_context.ts']) {
21 | promises.push(
22 | context['/_context.ts']().then((mod) => {
23 | if ((mod as any).getContext) {
24 | ctx = (mod as any).getContext
25 | }
26 | }),
27 | )
28 | }
29 |
30 | for (const path in modules) {
31 | promises.push(modules[path]())
32 | }
33 |
34 | await Promise.all(promises)
35 |
36 | const completedSchema = builder.toSchema({})
37 |
38 | const yoga = createYoga({
39 | schema: completedSchema,
40 | // We allow batching by default
41 | graphiql: {
42 | title: 'Fuse GraphiQL',
43 | defaultQuery,
44 | },
45 | batching: true,
46 | context: wrappedContext(ctx),
47 | plugins: getYogaPlugins(),
48 | })
49 |
50 | ;(yoga as any).stringifiedSchema = printSchema(completedSchema)
51 | return yoga
52 | }
53 |
--------------------------------------------------------------------------------
/packages/core/src/errors.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLError } from 'graphql'
2 |
3 | export abstract class FuseError extends GraphQLError {
4 | abstract readonly name: string
5 |
6 | constructor(
7 | message: string,
8 | extensions: {
9 | code: string
10 | http?: {
11 | status: number
12 | }
13 | },
14 | ) {
15 | super(message, {
16 | extensions,
17 | })
18 | }
19 | }
20 |
21 | /** For use when user is not authenticated or unknown. */
22 | export class AuthenticationError extends FuseError {
23 | name = 'UnauthenticatedError'
24 | constructor(message = 'Unauthenticated') {
25 | super(message, { code: 'UNAUTHENTICATED' })
26 | }
27 | }
28 |
29 | /** For use when a resource is not found or not accessible by an authenticated user. */
30 | export class ForbiddenError extends FuseError {
31 | name = 'ForbiddenError'
32 | constructor(message = 'Forbidden') {
33 | super(message, { code: 'FORBIDDEN' })
34 | }
35 | }
36 |
37 | /** For use when a resource is not found. */
38 | export class NotFoundError extends FuseError {
39 | name = 'NotFoundError'
40 | constructor(message = 'Not Found') {
41 | super(message, { code: 'NOT_FOUND' })
42 | }
43 | }
44 |
45 | /** For use when any input was invalid or when a resource does not exist but is assumed to exist. */
46 | export class BadRequestError extends FuseError {
47 | name = 'BadRequestError'
48 | constructor(message = 'Bad Request') {
49 | super(message, { code: 'BAD_REQUEST' })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/core/src/next/client.ts:
--------------------------------------------------------------------------------
1 | import {
2 | useQuery,
3 | UrqlProvider,
4 | createClient as create,
5 | fetchExchange,
6 | ssrExchange,
7 | } from '@urql/next'
8 | import type { Client, ClientOptions, SSRExchange } from '@urql/next'
9 | import { cacheExchange } from '../exchanges/cache'
10 |
11 | export * from 'urql'
12 | export { useQuery, UrqlProvider as Provider }
13 | export { cacheExchange } from '../exchanges/cache'
14 |
15 | type Optional = Pick, K> & Omit
16 | export const createClient = (
17 | opts: Optional,
18 | ): { client: Client; ssr: SSRExchange } => {
19 | const ssr = ssrExchange()
20 | const options: ClientOptions = {
21 | ...opts,
22 | suspense: opts.suspense ?? true,
23 | exchanges: opts.exchanges || [cacheExchange, ssr, fetchExchange],
24 | }
25 | return { client: create(options), ssr }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/pothos-list/index.ts:
--------------------------------------------------------------------------------
1 | import './global-types'
2 | import './schema-builder'
3 | import SchemaBuilder, {
4 | BasePlugin,
5 | FieldKind,
6 | SchemaTypes,
7 | RootFieldBuilder,
8 | } from '@pothos/core'
9 | import { ListShape } from './types'
10 |
11 | const pluginName = 'fuselist' as const
12 |
13 | export default pluginName
14 |
15 | export class PothosListPlugin<
16 | Types extends SchemaTypes,
17 | > extends BasePlugin {}
18 |
19 | try {
20 | SchemaBuilder.registerPlugin(pluginName, PothosListPlugin)
21 | } catch (e) {}
22 |
23 | const fieldBuilderProto =
24 | RootFieldBuilder.prototype as PothosSchemaTypes.RootFieldBuilder<
25 | SchemaTypes,
26 | unknown,
27 | FieldKind
28 | >
29 |
30 | fieldBuilderProto.list = function list(fieldOptions) {
31 | const ref =
32 | this.builder.objectRef>(
33 | 'Unnamed list',
34 | )
35 |
36 | const fieldRef = this.field({
37 | ...fieldOptions,
38 | type: ref,
39 | args: {
40 | ...fieldOptions.args,
41 | },
42 | resolve: fieldOptions.resolve as never,
43 | } as never)
44 |
45 | this.builder.configStore.onFieldUse(fieldRef, (fieldConfig) => {
46 | const name = fieldConfig.name[0].toUpperCase() + fieldConfig.name.slice(1)
47 | const listName = `${this.typename}${name}${
48 | fieldConfig.name.toLowerCase().endsWith('list') ? '' : 'List'
49 | }`
50 |
51 | this.builder.listObject({
52 | type: fieldOptions.type,
53 | name: listName,
54 | nullable: fieldOptions.nodeNullable ?? true,
55 | })
56 |
57 | this.builder.configStore.associateRefWithName(ref, listName)
58 | })
59 |
60 | return fieldRef
61 | }
62 |
--------------------------------------------------------------------------------
/packages/core/src/pothos-list/schema-builder.ts:
--------------------------------------------------------------------------------
1 | import SchemaBuilder, { ObjectRef, SchemaTypes, verifyRef } from '@pothos/core'
2 | import { ListShape } from './types'
3 |
4 | const schemaBuilderProto =
5 | SchemaBuilder.prototype as PothosSchemaTypes.SchemaBuilder
6 |
7 | export const listRefs = new WeakMap<
8 | PothosSchemaTypes.SchemaBuilder,
9 | ObjectRef>[]
10 | >()
11 |
12 | export const globalListFieldsMap = new WeakMap<
13 | PothosSchemaTypes.SchemaBuilder,
14 | ((ref: ObjectRef>) => void)[]
15 | >()
16 |
17 | schemaBuilderProto.listObject = function listObject({
18 | type,
19 | name: listName,
20 | nullable,
21 | }) {
22 | verifyRef(type)
23 |
24 | const listRef =
25 | this.objectRef>(listName)
26 |
27 | this.objectType(listRef, {
28 | fields: (t) => ({
29 | totalCount: t.int({
30 | nullable: true,
31 | resolve: (parent) => parent.totalCount || null,
32 | }),
33 | nodes: t.field({
34 | nullable: {
35 | items: nullable ?? true,
36 | list: false,
37 | },
38 | type: [type],
39 | resolve: (parent) => parent.nodes as any,
40 | }),
41 | }),
42 | })
43 |
44 | if (!listRefs.has(this)) {
45 | listRefs.set(this, [])
46 | }
47 |
48 | listRefs.get(this)!.push(listRef)
49 |
50 | globalListFieldsMap.get(this)?.forEach((fieldFn) => void fieldFn(listRef))
51 |
52 | return listRef as never
53 | }
54 |
--------------------------------------------------------------------------------
/packages/core/src/pothos-list/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | SchemaTypes,
3 | MaybePromise,
4 | ShapeFromTypeParam,
5 | OutputType,
6 | } from '@pothos/core'
7 |
8 | export interface ListResultShape {
9 | totalCount?: number | null
10 | nodes: MaybePromise[]
11 | }
12 |
13 | export type ListShape<
14 | Types extends SchemaTypes,
15 | T,
16 | Nullable,
17 | ListResult extends ListResultShape = ListResultShape,
18 | > =
19 | | (Nullable extends false ? never : null | undefined)
20 | | (ListResult & Types['ListWrapper'])
21 |
22 | export type ListShapeFromBaseShape<
23 | Types extends SchemaTypes,
24 | Shape,
25 | Nullable extends boolean,
26 | > = ListShape
27 |
28 | export type ListShapeForType<
29 | Types extends SchemaTypes,
30 | Type extends OutputType,
31 | Nullable extends boolean,
32 | ListResult extends ListResultShape<
33 | ShapeFromTypeParam
34 | > = ListResultShape>,
35 | > = ListShape<
36 | Types,
37 | ShapeFromTypeParam,
38 | Nullable,
39 | ListResult
40 | >
41 |
42 | export type ListShapeFromResolve<
43 | Types extends SchemaTypes,
44 | Type extends OutputType,
45 | Nullable extends boolean,
46 | Resolved,
47 | ListResult extends ListResultShape<
48 | ShapeFromTypeParam
49 | > = ListResultShape>,
50 | > = Resolved extends Promise
51 | ? NonNullable extends ListShapeForType
52 | ? NonNullable
53 | : ListShapeForType & NonNullable
54 | : Resolved extends ListShapeForType
55 | ? NonNullable
56 | : ListShapeForType &
57 | NonNullable
58 |
--------------------------------------------------------------------------------
/packages/core/test/fixtures/simple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fuse-fixtures/simple",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "devDependencies": {
7 | "fuse": "file:../../../",
8 | "typescript": "^5.2.2"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/test/fixtures/simple/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/test/fixtures/simple/types/Test.ts:
--------------------------------------------------------------------------------
1 | import { node } from 'fuse'
2 |
3 | type UserSource = {
4 | id: string
5 | name: string
6 | avatar_url: string
7 | }
8 |
9 | // "Nodes" are the core abstraction of Fuse. Each node represents
10 | // a resource/entity with multiple fields and has to define two things:
11 | // 1. load(): How to fetch from the underlying data source
12 | // 2. fields: What fields should be exposed and added for clients
13 | export const UserNode = node({
14 | name: 'User',
15 | load: async (ids) => getUsers(ids),
16 | fields: (t) => ({
17 | name: t.exposeString('name'),
18 | // rename to camel-case
19 | avatarUrl: t.exposeString('avatar_url'),
20 | // Add an additional firstName field
21 | firstName: t.string({
22 | resolve: (user) => user.name.split(' ')[0],
23 | }),
24 | }),
25 | })
26 |
27 | // Fake function to fetch users. In real applications, this would
28 | // talk to an underlying REST API/gRPC service/third-party API/…
29 | async function getUsers(ids: string[]): Promise {
30 | return ids.map((id) => ({
31 | id,
32 | name: `Peter #${id}`,
33 | avatar_url: `https://i.pravatar.cc/300?u=${id}`,
34 | }))
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/test/fixtures/tada/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fuse-fixtures/tada",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "devDependencies": {
7 | "@0no-co/graphqlsp": "1.3.3",
8 | "fuse": "file:../../../",
9 | "typescript": "^5.2.2",
10 | "gql.tada": "1.2.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/test/fixtures/tada/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "plugins": [
9 | {
10 | "name": "@0no-co/graphqlsp",
11 | "schema": "./schema.graphql",
12 | "tadaOutputLocation": "./fuse/introspection.ts"
13 | }
14 | ],
15 | "moduleResolution": "bundler",
16 | "allowImportingTsExtensions": true,
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/test/fixtures/tada/types/Test.ts:
--------------------------------------------------------------------------------
1 | import { node } from 'fuse'
2 |
3 | type UserSource = {
4 | id: string
5 | name: string
6 | avatar_url: string
7 | }
8 |
9 | // "Nodes" are the core abstraction of Fuse. Each node represents
10 | // a resource/entity with multiple fields and has to define two things:
11 | // 1. load(): How to fetch from the underlying data source
12 | // 2. fields: What fields should be exposed and added for clients
13 | export const UserNode = node({
14 | name: 'User',
15 | load: async (ids) => getUsers(ids),
16 | fields: (t) => ({
17 | name: t.exposeString('name'),
18 | // rename to camel-case
19 | avatarUrl: t.exposeString('avatar_url'),
20 | // Add an additional firstName field
21 | firstName: t.string({
22 | resolve: (user) => user.name.split(' ')[0],
23 | }),
24 | }),
25 | })
26 |
27 | // Fake function to fetch users. In real applications, this would
28 | // talk to an underlying REST API/gRPC service/third-party API/…
29 | async function getUsers(ids: string[]): Promise {
30 | return ids.map((id) => ({
31 | id,
32 | name: `Peter #${id}`,
33 | avatar_url: `https://i.pravatar.cc/300?u=${id}`,
34 | }))
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "lib": ["esnext"],
5 | "jsx": "preserve",
6 | "target": "es2018",
7 | "module": "es2020",
8 | "moduleResolution": "node",
9 | "allowJs": true,
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "skipLibCheck": true,
13 | "declaration": false,
14 | "noEmit": true,
15 | "strict": true,
16 | "noImplicitAny": false,
17 | "noUnusedParameters": false,
18 | "forceConsistentCasingInFileNames": true,
19 | "isolatedModules": true,
20 | "useUnknownInCatchVariables": false,
21 | "types": ["node", "vite/client"]
22 | },
23 | "exclude": ["**/dist", "**/build"]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/vitest.config.mts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config'
2 |
3 | export default defineConfig({
4 |
5 | test: {
6 | alias: {
7 | fuse: './builder.mjs'
8 | }
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/packages/create-fuse-app/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # create-fuse-app
2 |
3 | ## 0.7.0
4 |
5 | ### Minor Changes
6 |
7 | - db8b67d: Support creating a fuse app in an empty directory
8 |
9 | ## 0.6.0
10 |
11 | ### Minor Changes
12 |
13 | - 1f225c5: Fix typo in the babel-plugin when rewriting an export default expression
14 |
15 | ## 0.5.0
16 |
17 | ### Minor Changes
18 |
19 | - d55a2f0: Default to using `gql.tada`
20 |
21 | ## 0.4.1
22 |
23 | ### Patch Changes
24 |
25 | - 23c0264: Fix writing of `.mjs` next config
26 |
27 | ## 0.4.0
28 |
29 | ### Minor Changes
30 |
31 | - 2b4073e: Add support for generating a fuse-app without being in Next.JS
32 |
33 | ### Patch Changes
34 |
35 | - 0a58c27: Check for `src` when looking for the `/app` directory
36 |
37 | ## 0.3.0
38 |
39 | ### Minor Changes
40 |
41 | - e7f037b: Add a webpack-loader that automatically imports all entries in the `types/` directory.
42 | In doing so it removes the need for `require.context`, next time you run the application,
43 | you are encouraged to remove `require.context` from your `/pages/api/fuse.ts` or `/app/api/fuse/route.ts`
44 | files.
45 |
46 | ## 0.2.1
47 |
48 | ### Patch Changes
49 |
50 | - f699e31: Fix duplicate entries
51 |
52 | ## 0.2.0
53 |
54 | ### Minor Changes
55 |
56 | - fd89e23: Add support for `bun` and `pnpm`
57 |
58 | ## 0.1.1
59 |
60 | ### Patch Changes
61 |
62 | - 35abb16: Fix crash when dir exists
63 |
--------------------------------------------------------------------------------
/packages/create-fuse-app/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Stellate
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/packages/create-fuse-app/README.md:
--------------------------------------------------------------------------------
1 | # create-fuse-app
2 |
3 | This will automatically generate all the needed files to get
4 | started with [`fuse`](https://fusedata.dev/)
5 |
6 | ```sh
7 | npx create-fuse-app
8 | ## or
9 | npm init fuse-app
10 | ## or
11 | yarn create fuse-app
12 | ## or
13 | pnpm create fuse-app
14 | ```
15 |
16 | ## [Read the docs](https://fusedata.dev/docs): [fusedata.dev/docs](https://fusedata.dev)
17 |
--------------------------------------------------------------------------------
/packages/create-fuse-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-fuse-app",
3 | "version": "0.7.0",
4 | "description": "The magical GraphQL framework",
5 | "homepage": "https://github.com/StellateHQ/fuse",
6 | "bugs": "https://github.com/StellateHQ/fuse/issues",
7 | "license": "MIT",
8 | "author": "Stellate engineering ",
9 | "keywords": [],
10 | "bin": "./dist/index.js",
11 | "type": "module",
12 | "files": [
13 | "dist",
14 | "LICENSE",
15 | "README.md",
16 | "CHANGELOG.md"
17 | ],
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/StellateHQ/fuse",
21 | "directory": "packages/create-fuse-app"
22 | },
23 | "scripts": {
24 | "build": "tsup",
25 | "prepublishOnly": "tsup"
26 | },
27 | "dependencies": {
28 | "@babel/core": "^7.23.5",
29 | "@clack/prompts": "^0.7.0",
30 | "comment-json": "^4.2.3",
31 | "execa": "^8.0.1",
32 | "kolorist": "^1.8.0"
33 | },
34 | "devDependencies": {
35 | "@types/node": "^20.10.3",
36 | "tsup": "^7.2.0",
37 | "type-fest": "^4.8.3",
38 | "typescript": "^5.3.2"
39 | },
40 | "publishConfig": {
41 | "access": "public",
42 | "provenance": true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/create-fuse-app/src/get-package-manager.ts:
--------------------------------------------------------------------------------
1 | // This is copied from https://github.com/vercel/next.js/blob/canary/packages/create-next-app/helpers/get-pkg-manager.ts
2 | export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
3 |
4 | export function getPkgManager(): PackageManager {
5 | const userAgent = process.env.npm_config_user_agent || ''
6 |
7 | if (userAgent.startsWith('yarn')) {
8 | return 'yarn'
9 | }
10 |
11 | if (userAgent.startsWith('pnpm')) {
12 | return 'pnpm'
13 | }
14 |
15 | if (userAgent.startsWith('bun')) {
16 | return 'bun'
17 | }
18 |
19 | return 'npm'
20 | }
21 |
--------------------------------------------------------------------------------
/packages/create-fuse-app/src/install-package.ts:
--------------------------------------------------------------------------------
1 | import type { PackageManager } from './get-package-manager'
2 | import { execa } from 'execa'
3 |
4 | export async function install(
5 | packageManager: PackageManager,
6 | env: 'prod' | 'dev',
7 | packages: string[],
8 | ): Promise {
9 | let args: string[] = []
10 | switch (packageManager) {
11 | case 'npm': {
12 | args.push('install')
13 | if (env === 'dev') {
14 | args.push('--save-dev')
15 | } else {
16 | args.push('--save')
17 | }
18 | break
19 | }
20 | case 'yarn': {
21 | args.push('add')
22 | if (env === 'dev') {
23 | args.push('-D')
24 | }
25 | break
26 | }
27 | case 'pnpm': {
28 | args.push('add')
29 | if (env === 'dev') {
30 | args.push('-D')
31 | }
32 | break
33 | }
34 | case 'bun': {
35 | args.push('add')
36 | if (env === 'dev') {
37 | args.push('-D')
38 | }
39 | break
40 | }
41 | }
42 |
43 | args.push(...packages)
44 | /**
45 | * Return a Promise that resolves once the installation is finished.
46 | */
47 | await execa(packageManager, args, {
48 | stdio: 'inherit',
49 | env: {
50 | ...process.env,
51 | NODE_ENV: 'development',
52 | },
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/packages/create-fuse-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "lib": ["esnext"],
5 | "jsx": "preserve",
6 | "target": "es2018",
7 | "module": "es2020",
8 | "moduleResolution": "node",
9 | "allowJs": true,
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "skipLibCheck": true,
13 | "declaration": false,
14 | "noEmit": true,
15 | "strict": true,
16 | "noImplicitAny": false,
17 | "noUnusedParameters": false,
18 | "forceConsistentCasingInFileNames": true,
19 | "isolatedModules": true,
20 | "useUnknownInCatchVariables": false,
21 | "types": ["node"]
22 | },
23 | "exclude": ["**/dist", "**/build"]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/create-fuse-app/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, Options } from 'tsup'
2 |
3 | export default defineConfig(async () => {
4 | const baseOptions: Options = {
5 | platform: 'node',
6 |
7 | splitting: false,
8 | format: ['esm'],
9 | skipNodeModulesBundle: false,
10 | target: 'node18',
11 | env: {
12 | // env var `npm_package_version` gets injected in runtime by npm/yarn automatically
13 | // this replacement is for build time, so it can be used for both
14 | npm_package_version:
15 | process.env.npm_package_version ??
16 | (await import('./package.json')).version,
17 | },
18 | minify: false,
19 | clean: true,
20 | }
21 |
22 | /**
23 | * We create distinct options so that no type declarations are reused and
24 | * exported into a separate file, in other words, we want all `d.ts` files
25 | * to not contain any imports.
26 | */
27 | return [
28 | {
29 | ...baseOptions,
30 | entry: ['src/index.ts'],
31 | },
32 | ]
33 | })
34 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'examples/*'
3 | - 'packages/core'
4 | - 'packages/core/test/fixtures/*'
5 | - 'packages/create-fuse-app'
6 | - 'website'
7 |
--------------------------------------------------------------------------------
/website/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
--------------------------------------------------------------------------------
/website/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | public
4 | pnpm-lock.yaml
--------------------------------------------------------------------------------
/website/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "plugins": ["prettier-plugin-tailwindcss"]
5 | }
6 |
--------------------------------------------------------------------------------
/website/.svgrrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | typescript: true,
3 | outDir: './src/components/icons/',
4 | prettier: false,
5 | expandProps: 'end',
6 | svgProps: {
7 | 'aria-hidden': 'true',
8 | },
9 | svgoConfig: {
10 | plugins: [
11 | {
12 | name: 'preset-default',
13 | params: {
14 | overrides: {
15 | removeViewBox: false,
16 | },
17 | },
18 | },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | ## Fuse website
2 |
3 | Build using Nextra.
4 |
5 | ### Development
6 |
7 | ```bash
8 | # run from monorepo root
9 | pnpm website
10 | ```
11 |
12 | ### Udpating sitemap
13 |
14 | When adding new pages, run `pnpm --filter @fuse/website build` to update the sitemap files.
15 |
--------------------------------------------------------------------------------
/website/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 |
--------------------------------------------------------------------------------
/website/next-sitemap.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next-sitemap').IConfig} */
2 | module.exports = {
3 | siteUrl: 'https://fusedata.dev',
4 | generateRobotsTxt: true,
5 | }
6 |
--------------------------------------------------------------------------------
/website/next.config.js:
--------------------------------------------------------------------------------
1 | const withNextra = require('nextra')({
2 | theme: 'nextra-theme-docs',
3 | themeConfig: './theme.config.tsx',
4 | })
5 |
6 | /** @type {import('next').NextConfig} */
7 | const nextConfig = {
8 | reactStrictMode: true,
9 | transpilePackages: ['geist', 'react-tweet'],
10 | }
11 |
12 | module.exports = withNextra(nextConfig)
13 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fuse/website",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build && next-sitemap",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "generate": "svgr -- src/components/icons/svg && prettier ./src/components/icons --write"
11 | },
12 | "dependencies": {
13 | "@radix-ui/react-tooltip": "^1.0.7",
14 | "@vercel/analytics": "^1.1.1",
15 | "clsx": "^2.0.0",
16 | "geist": "^1.1.0",
17 | "next": "14.0.3",
18 | "next-sitemap": "^4.2.3",
19 | "nextra": "^2.13.2",
20 | "nextra-theme-docs": "^2.13.2",
21 | "react": "^18.0.0",
22 | "react-countup": "^6.5.0",
23 | "react-dom": "^18.0.0",
24 | "react-fast-marquee": "^1.6.2",
25 | "react-tweet": "^3.2.0",
26 | "sass": "^1.69.5",
27 | "tailwind-merge": "^2.0.0"
28 | },
29 | "devDependencies": {
30 | "@svgr/cli": "^8.1.0",
31 | "@svgr/webpack": "^8.1.0",
32 | "@types/node": "^20",
33 | "@types/react": "^18",
34 | "@types/react-dom": "^18",
35 | "autoprefixer": "^10.0.1",
36 | "eslint": "^8",
37 | "eslint-config-next": "14.0.3",
38 | "postcss": "^8",
39 | "prettier": "3.1.0",
40 | "prettier-plugin-tailwindcss": "0.5.7",
41 | "tailwindcss": "^3.3.0",
42 | "typescript": "^5"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/website/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/website/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/favicon.ico
--------------------------------------------------------------------------------
/website/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/favicon.png
--------------------------------------------------------------------------------
/website/public/images/data-layers-vs-api-gateways.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/images/data-layers-vs-api-gateways.png
--------------------------------------------------------------------------------
/website/public/images/data-layers-vs-bffs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/images/data-layers-vs-bffs.png
--------------------------------------------------------------------------------
/website/public/images/data-layers-vs-graphql-federation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/images/data-layers-vs-graphql-federation.png
--------------------------------------------------------------------------------
/website/public/images/fuse-circles-with-logos.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/images/fuse-circles-with-logos.webp
--------------------------------------------------------------------------------
/website/public/images/fuse-grid-logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/images/fuse-grid-logo.webp
--------------------------------------------------------------------------------
/website/public/images/fuse-outline.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/website/public/images/nextjs-logo.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/website/public/images/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/images/og-image.png
--------------------------------------------------------------------------------
/website/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/robots.txt:
--------------------------------------------------------------------------------
1 | # *
2 | User-agent: *
3 | Allow: /
4 |
5 | # Host
6 | Host: https://fusedata.dev
7 |
8 | # Sitemaps
9 | Sitemap: https://fusedata.dev/sitemap.xml
10 |
--------------------------------------------------------------------------------
/website/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://fusejs.org/sitemap-0.xml
4 |
--------------------------------------------------------------------------------
/website/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/videos/video-poster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/videos/video-poster.png
--------------------------------------------------------------------------------
/website/public/videos/video-sample-vertical.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/videos/video-sample-vertical.mp4
--------------------------------------------------------------------------------
/website/public/videos/video-sample.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/videos/video-sample.mp4
--------------------------------------------------------------------------------
/website/public/videos/video-vertical-poster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StellateHQ/fuse/26ea459f0075fa083734033c96014d37b762089a/website/public/videos/video-vertical-poster.png
--------------------------------------------------------------------------------
/website/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentProps } from 'react'
2 | import { cn } from '@/utils/tailwind'
3 | import { ClassValue } from 'clsx'
4 |
5 | type ButtonVariant = 'light' | 'dark' | 'starship'
6 |
7 | function getClassNames(variant: ButtonVariant, className?: ClassValue) {
8 | return cn(
9 | 'inline-flex items-center gap-2 rounded-[30px] px-[20px] py-[10px] text-sm font-medium',
10 | variant === 'light'
11 | ? 'border-[0.5px] border-gravel-300 bg-white text-black shadow-actions hover:bg-gravel-100 hover:shadow-light-button-hover-shadow'
12 | : variant === 'dark'
13 | ? 'bg-gravel-950 text-white hover:bg-gravel-800 hover:shadow-dark-button-hover-shadow'
14 | : 'bg-starship-950 text-starship-500 hover:text-starship-950 hover:bg-starship-500 hover:shadow-starship-button-hover-shadow',
15 | className,
16 | )
17 | }
18 |
19 | type ButtonProps = {
20 | variant: ButtonVariant
21 | } & ComponentProps<'button'>
22 |
23 | export function Button({ className, ...props }: ButtonProps) {
24 | return (
25 |
26 | )
27 | }
28 |
29 | type ButtonLinkProps = {
30 | variant: ButtonVariant
31 | } & ComponentProps<'a'>
32 |
33 | export function ButtonLink({ className, ...props }: ButtonLinkProps) {
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/website/src/components/Card.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils/tailwind'
2 | import { ComponentProps } from 'react'
3 |
4 | type CardProps = {
5 | height?: 'full' | 'auto'
6 | } & ComponentProps<'div'>
7 |
8 | export function Card({ className, height = 'full', ...props }: CardProps) {
9 | return (
10 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/website/src/components/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import { External } from '@/components/icons'
2 |
3 | type ExternalLinkProps = {
4 | href: string
5 | children: React.ReactNode
6 | }
7 |
8 | export function ExternalLink({ href, children }: ExternalLinkProps) {
9 | return (
10 |
16 | {children}
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/website/src/components/Heading.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils/tailwind'
2 | import { ComponentProps } from 'react'
3 |
4 | type HeadingProps = {
5 | level: 1 | 2 | 3 | 4 | 5 | 6
6 | wrapBalance?: boolean
7 | } & ComponentProps<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>
8 |
9 | export function Heading({
10 | level,
11 | className,
12 | wrapBalance = true,
13 | ...props
14 | }: HeadingProps) {
15 | const Element = `h${level}` as const
16 | return (
17 |
25 | )
26 | }
27 |
28 | type HeadingEyebrowProps = {
29 | variant?: 'starship' | 'gqlPink' | 'gravel'
30 | } & ComponentProps<'p'>
31 |
32 | /** Used for text above headings */
33 | export function HeadingEyebrow({
34 | className,
35 | variant = 'starship',
36 | ...props
37 | }: HeadingEyebrowProps) {
38 | return (
39 |
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/website/src/components/MobileMenuLines.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils/tailwind'
2 | import { ComponentProps } from 'react'
3 |
4 | export function MobileMenuLines({
5 | className,
6 | ...props
7 | }: ComponentProps<'svg'>) {
8 | return (
9 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/website/src/components/PageVerticaLines.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils/tailwind'
2 |
3 | const count = 7
4 |
5 | export function PageVerticalLines({ inverted }: { inverted?: boolean }) {
6 | // return null
7 | return (
8 |
13 | {Array.from(Array(count)).map((_, i) => (
14 |
15 | ))}
16 |
17 | )
18 | }
19 |
20 | function VerticalLine({ inverted }: { inverted?: boolean }) {
21 | return (
22 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/website/src/components/Section.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils/tailwind'
2 | import { ComponentProps } from 'react'
3 |
4 | type SectionProps = {
5 | variant?: 'light' | 'dark'
6 | } & ComponentProps<'div'>
7 |
8 | export function Section({
9 | variant = 'light',
10 | className,
11 | ...props
12 | }: SectionProps) {
13 | return (
14 |
22 | )
23 | }
24 |
25 | type SmallBleedProps = {} & ComponentProps<'div'>
26 |
27 | export function SmallBleed({ className, ...props }: SmallBleedProps) {
28 | return
29 | }
30 |
31 | export function MaxWidthContainer({
32 | className,
33 | variant = 'default',
34 | ...props
35 | }: ComponentProps<'div'> & { variant?: 'larger' | 'default' }) {
36 | return (
37 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/website/src/components/StarOnGithub.tsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react'
2 | import { useCountUp } from 'react-countup'
3 | import { ButtonLink } from '@/components/Button'
4 | import { GithubLogo, Star } from '@/components/icons'
5 |
6 | type StarOnGithubProps = {
7 | stars: number
8 | }
9 |
10 | export function StarOnGithub({ stars = 0 }: StarOnGithubProps) {
11 | const countUpRef = useRef(null)
12 | useCountUp({
13 | ref: countUpRef,
14 | start: stars - 50,
15 | end: stars,
16 | duration: 4,
17 | })
18 |
19 | return (
20 |
27 |
28 | Star on GitHub
29 |
30 |
31 | {stars > 50 ? (
32 |
33 | ) : null}
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/website/src/components/Testimonials.tsx:
--------------------------------------------------------------------------------
1 | import type { Tweet } from 'react-tweet/api'
2 | import { TweetHeader, TweetBody, enrichTweet } from 'react-tweet'
3 | import Marquee from 'react-fast-marquee'
4 | import { Section } from '@/components/Section'
5 | import { Card } from '@/components/Card'
6 |
7 | type TestimonialsProps = {
8 | tweets: Tweet[]
9 | }
10 |
11 | export function Testimonials({ tweets }: TestimonialsProps) {
12 | return (
13 |
14 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/website/src/components/Text.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils/tailwind'
2 | import { ComponentProps } from 'react'
3 |
4 | type Props = {} & ComponentProps<'p'>
5 |
6 | export function Text({ className, ...props }: Props) {
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/website/src/components/TheGrid.tsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react'
2 |
3 | const blockSize = 100
4 |
5 | const getPath = (size: number) => {
6 | const start = 1
7 | const end = size - 1
8 | return `M ${start} ${start} L ${end} ${start} ${end} ${end} ${start} ${end} ${start} ${start}`
9 | }
10 |
11 | export function TheGrid() {
12 | const circleRef = useRef(null)
13 |
14 | return (
15 | <>
16 | {
19 | if (!circleRef.current) {
20 | return
21 | }
22 | console.log(e)
23 | }}
24 | >
25 |

30 |
31 | {/*
*/}
66 |
67 | >
68 | )
69 | }
70 |
--------------------------------------------------------------------------------
/website/src/components/icons/ArrowConnectingNodes.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgArrowConnectingNodes = (props: SVGProps) => (
4 |
25 | )
26 | export default SvgArrowConnectingNodes
27 |
--------------------------------------------------------------------------------
/website/src/components/icons/ArrowOpeningPath.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgArrowOpeningPath = (props: SVGProps) => (
4 |
21 | )
22 | export default SvgArrowOpeningPath
23 |
--------------------------------------------------------------------------------
/website/src/components/icons/Automatic.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgAutomatic = (props: SVGProps) => (
4 |
37 | )
38 | export default SvgAutomatic
39 |
--------------------------------------------------------------------------------
/website/src/components/icons/BuildingBlock.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgBuildingBlock = (props: SVGProps) => (
4 |
23 | )
24 | export default SvgBuildingBlock
25 |
--------------------------------------------------------------------------------
/website/src/components/icons/External.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgExternal = (props: SVGProps) => (
4 |
20 | )
21 | export default SvgExternal
22 |
--------------------------------------------------------------------------------
/website/src/components/icons/GithubLogo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgGithubLogo = (props: SVGProps) => (
4 |
20 | )
21 | export default SvgGithubLogo
22 |
--------------------------------------------------------------------------------
/website/src/components/icons/GlobalUnique.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgGlobalUnique = (props: SVGProps) => (
4 |
29 | )
30 | export default SvgGlobalUnique
31 |
--------------------------------------------------------------------------------
/website/src/components/icons/GraphqlLogoOutline.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgGraphqlLogoOutline = (props: SVGProps) => (
4 |
20 | )
21 | export default SvgGraphqlLogoOutline
22 |
--------------------------------------------------------------------------------
/website/src/components/icons/HttpLogo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgHttpLogo = (props: SVGProps) => (
4 |
26 | )
27 | export default SvgHttpLogo
28 |
--------------------------------------------------------------------------------
/website/src/components/icons/NodeStack.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgNodeStack = (props: SVGProps) => (
4 |
23 | )
24 | export default SvgNodeStack
25 |
--------------------------------------------------------------------------------
/website/src/components/icons/NpmLogo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgNpmLogo = (props: SVGProps) => (
4 |
18 | )
19 | export default SvgNpmLogo
20 |
--------------------------------------------------------------------------------
/website/src/components/icons/Observability.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgObservability = (props: SVGProps) => (
4 |
28 | )
29 | export default SvgObservability
30 |
--------------------------------------------------------------------------------
/website/src/components/icons/Relay.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgRelay = (props: SVGProps) => (
4 |
25 | )
26 | export default SvgRelay
27 |
--------------------------------------------------------------------------------
/website/src/components/icons/Scalable.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgScalable = (props: SVGProps) => (
4 |
33 | )
34 | export default SvgScalable
35 |
--------------------------------------------------------------------------------
/website/src/components/icons/Security.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgSecurity = (props: SVGProps) => (
4 |
28 | )
29 | export default SvgSecurity
30 |
--------------------------------------------------------------------------------
/website/src/components/icons/Star.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgStar = (props: SVGProps) => (
4 |
21 | )
22 | export default SvgStar
23 |
--------------------------------------------------------------------------------
/website/src/components/icons/StarSparkle.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgStarSparkle = (props: SVGProps) => (
4 |
20 | )
21 | export default SvgStarSparkle
22 |
--------------------------------------------------------------------------------
/website/src/components/icons/StellateLogo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgStellateLogo = (props: SVGProps) => (
4 |
18 | )
19 | export default SvgStellateLogo
20 |
--------------------------------------------------------------------------------
/website/src/components/icons/Terminal.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgTerminal = (props: SVGProps) => (
4 |
24 | )
25 | export default SvgTerminal
26 |
--------------------------------------------------------------------------------
/website/src/components/icons/XLogo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { SVGProps } from 'react'
3 | const SvgXLogo = (props: SVGProps) => (
4 |
22 | )
23 | export default SvgXLogo
24 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/arrowConnectingNodes.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/arrowOpeningPath.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/automatic.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/buildingBlock.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/external.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/httpLogo.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/nodeStack.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/npmLogo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/observability.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/relay.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/scalable.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/security.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/star.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/starSparkle.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/stellateLogo.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/terminal.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/website/src/components/icons/svg/xLogo.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/website/src/components/snippets/client.mdx:
--------------------------------------------------------------------------------
1 | ```ts
2 | // /components/BlogPost.tsx
3 |
4 | const BlogPostFields = graphql(`
5 | fragment BlogPost_Fields on BlogPost {
6 | id
7 | title
8 | content
9 | author {
10 | # Combine fragments from other components to fetch the data they need
11 | ...UserCard_Fields
12 | }
13 | }
14 | `)
15 |
16 | export const BlogPost = (props: {
17 | blogPost: FragmentType
18 | }) => {
19 | const blogPost = useFragment(BlogPostFields, props.blogPost)
20 |
21 | return (
22 |
23 | {/* Fully typesafe, component-level data fetching */}
24 |
25 |
{blogPost.title}
26 | {blogPost.content}
27 |
28 | )
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/website/src/components/snippets/node.mdx:
--------------------------------------------------------------------------------
1 | ```ts
2 | // /types/BlogPost.ts
3 |
4 | // Turn blog posts from your CMS into
5 | // a GraphQL object type & query
6 | export const BlogPostNode = node({
7 | name: 'BlogPost',
8 | load: async (ids) => fetchBlogPosts(ids),
9 | fields: (t) => ({
10 | // Exose a field as-is
11 | title: t.exposeString('title'),
12 | // Rename a field
13 | content: t.exposeString('content_html'),
14 | // Connect it to other data
15 | author: t.field({
16 | type: UserNode,
17 | resolve: (blogPost) => blogPost.author_id
18 | })
19 | }),
20 | })
21 | ```
22 |
--------------------------------------------------------------------------------
/website/src/mdx.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.mdx' {
2 | const content: () => JSX.Element
3 | export default content
4 | }
5 |
--------------------------------------------------------------------------------
/website/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '@/styles/globals.scss'
2 | import { GeistSans } from 'geist/font/sans'
3 | import { GeistMono } from 'geist/font/mono'
4 | import type { AppProps } from 'next/app'
5 | import { Analytics } from '@vercel/analytics/react'
6 |
7 | export default function App({ Component, pageProps }: AppProps) {
8 | return (
9 |
10 |
11 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/website/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/website/src/pages/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "type": "page",
4 | "title": "Fuse",
5 | "display": "hidden",
6 | "theme": {
7 | "layout": "full"
8 | }
9 | },
10 | "docs": {
11 | "type": "page",
12 | "display": "hidden",
13 | "title": "Documentation"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/website/src/pages/docs/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Getting Started",
3 | "client": "Querying your API (client)",
4 | "server": "Building your API (server)",
5 | "deployment": "Deploying your API (server)",
6 | "fuse-method": "The Fuse Method",
7 | "setting-fuse-up-manually": "Setting up Fuse manually",
8 | "troubleshooting": "Troubleshooting"
9 | }
10 |
--------------------------------------------------------------------------------
/website/src/pages/docs/client/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Overview",
3 | "best-practices": "Best practices",
4 | "types": "Types",
5 | "javascript": "JavaScript frameworks",
6 | "other": "Other languages"
7 | }
8 |
--------------------------------------------------------------------------------
/website/src/pages/docs/client/javascript/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "react": "React & React Native",
3 | "nextjs": "Next.js",
4 | "vue": "Vue",
5 | "angular": "Angular"
6 | }
7 |
--------------------------------------------------------------------------------
/website/src/pages/docs/client/javascript/angular.mdx:
--------------------------------------------------------------------------------
1 | # Angular
2 |
3 | Fuse supports querying your data from angular, as long as your querying library supports typed-document nodes then
4 | your output and variables will be typed. An example client would be [Apollo-angular](https://the-guild.dev/graphql/apollo-angular/docs)
5 |
6 | Constructing a typed-document-node for a query:
7 |
8 | ```tsx
9 | import { graphql } from '../fuse'
10 | import { Avatar, AvatarFragment } from './components/Avatar'
11 |
12 | const UserQuery = graphql(`
13 | query User ($id: ID!) {
14 | user(id: $id) {
15 | name
16 | ...Avatar_UserFields
17 | }
18 | }
19 | `, [AvatarFragment])
20 | ```
21 |
22 | and for a fragment:
23 |
24 | ```tsx
25 | import { graphql, readFragment, FragmentOf } from '../../fuse'
26 |
27 | export const AvatarFragment = graphql(`
28 | fragment Avatar_UserFields on User {
29 | name
30 | avatarUrl
31 | }
32 | `)
33 |
34 | // Derive the input-type: FragmentOf
35 | // Apply fragment-masking: readFragment(AvatarFragment, user);
36 | ```
37 |
--------------------------------------------------------------------------------
/website/src/pages/docs/client/javascript/vue.mdx:
--------------------------------------------------------------------------------
1 | # Vue
2 |
3 | Fuse supports querying your data from Vue, as long as your querying library supports typed-document nodes then
4 | your output and variables will be typed. Some of these clients include [@urql/vue](https://formidable.com/open-source/urql/docs/basics/vue) and
5 | [vue/apollo](http://apollo.vuejs.org/).
6 |
7 | Constructing a typed-document-node for a query:
8 |
9 | ```tsx
10 | import { graphql } from '../fuse'
11 | import { Avatar, AvatarFragment } from './components/Avatar'
12 |
13 | const UserQuery = graphql(`
14 | query User ($id: ID!) {
15 | user(id: $id) {
16 | name
17 | ...Avatar_UserFields
18 | }
19 | }
20 | `, [AvatarFragment])
21 | ```
22 |
23 | and for a fragment:
24 |
25 | ```tsx
26 | import { graphql, readFragment, FragmentOf } from '../../fuse'
27 |
28 | export const AvatarFragment = graphql(`
29 | fragment Avatar_UserFields on User {
30 | name
31 | avatarUrl
32 | }
33 | `)
34 |
35 | // Derive the input-type: FragmentOf
36 | // Apply fragment-masking: readFragment(AvatarFragment, user);
37 | ```
38 |
--------------------------------------------------------------------------------
/website/src/pages/docs/client/other/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "ios": "iOS",
3 | "android": "Android (Java/Kotlin)",
4 | "http": "HTTP"
5 | }
6 |
--------------------------------------------------------------------------------
/website/src/pages/docs/client/other/android.mdx:
--------------------------------------------------------------------------------
1 | # Android (Java/Kotlin)
2 |
3 | Fuse does not have built-in code generation support for Android yet. However, you can access the GraphQL API you create with Fuse with **any GraphQL client that works on Android!**
4 |
5 | For example, [Apollo Kotlin](https://www.apollographql.com/docs/kotlin) is a popular option.
--------------------------------------------------------------------------------
/website/src/pages/docs/client/other/http.mdx:
--------------------------------------------------------------------------------
1 | # Raw HTTP access to your Fuse API
2 |
3 | Fuse creates a standard GraphQL API under the hood, which means you can use any HTTP client to construct a request and fetch data from your API.
4 |
5 | Note that we **strongly recommend using a GraphQL client if one is available**. Practically all popular mobile and web frameworks and languages have a GraphQL client.
6 |
7 | ## How to query your GraphQL API via HTTP
8 |
9 |
10 | Fundamentally, requests to GraphQL APIs are `POST` requests with a JSON body. The JSON body contains a `query` field, which is a string containing the GraphQL query, and an optional `variables` field, which is a JSON object containing the variables used in the query.
11 |
12 | Example:
13 |
14 | ```
15 | POST http://localhost:4000/graphql
16 |
17 | Content-Type: application/json
18 | Accept: application/json
19 |
20 | {
21 | "query": "query getUser($id: ID!) { user(id: $id) { name } }",
22 | "variables": { "id": 1 }
23 | }
24 | ```
--------------------------------------------------------------------------------
/website/src/pages/docs/client/other/ios.mdx:
--------------------------------------------------------------------------------
1 | # iOS (Swift)
2 |
3 | Fuse does not have built-in code generation support for iOS yet. However, you can access the GraphQL API you create with Fuse with **any GraphQL client that works on iOS!**
4 |
5 | For example, [Apollo Client for iOS](https://www.apollographql.com/docs/ios/) is a popular option.
--------------------------------------------------------------------------------
/website/src/pages/docs/client/types.mdx:
--------------------------------------------------------------------------------
1 | # Type generation
2 |
3 | By default fuse will use [`gql.tada`](https://gql-tada.0no.co/) to type your GraphQL documents.
4 |
5 | Folks who have been with fuse longer might still be on the GraphQL Code Generator default,
6 | we still support this! We'll check whether `tadaOutputLocation` is set in the `tsconfig` and
7 | whether you have `gql.tada` intalled. If not, we'll fall back to the GraphQL Code Generator.
8 |
9 | The biggest difference between the two in terms of writing code is that fragments in `gql.tada`
10 | aren't global which means you'll have to explicitly add them in the second argument of the `graphql`
11 | function.
12 |
13 | ```ts
14 | const UserQuery = graphql(`
15 | query User {
16 | user(id: "1") {
17 | id
18 | ...UserFields
19 | }
20 | }
21 | `, [UserFragment]) // needed in tada but not codegen.
22 | ```
23 |
24 | The other difference is that `gql.tada` will calculate everything at runtime while with codegen
25 | there will always be a background process running to calculate the types.
26 |
27 | ## Migrating to gql.tada
28 |
29 | To migrate to `gql.tada` we'll need to ensure that the LSP is working correctly and is upgraded to v1,
30 | when that is working we can install `gql.tada` and add `tadaOutputLocation: '{src}/fuse/introspection.ts'` to
31 | the graphqlsp config in the `tsconfig` file.
32 |
33 | After that you can delete the `fragment-masking`, `gql` and `graphql` typescript files which are present in
34 | the `fuse` folder.
35 |
36 | ## Opting out of gql.tada
37 |
38 | You can opt out of `gql.tada by` uninstalling the package and removing `tadaOutputLocation` from the `tsconfig`.
39 |
--------------------------------------------------------------------------------
/website/src/pages/docs/deployment/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Overview",
3 | "node": "Node.js",
4 | "lambda": "Lambda",
5 | "cloudflare-workers": "Cloudflare Workers",
6 | "nextjs": "Next.js API Routes"
7 | }
8 |
--------------------------------------------------------------------------------
/website/src/pages/docs/deployment/bun.mdx:
--------------------------------------------------------------------------------
1 | # Deploying your Fuse API with Bun
2 |
3 | First, build your API server into a single JS file that can be run with Bun:
4 |
5 | ```sh
6 | fuse build --adapter bun
7 | ```
8 |
9 | This will build your server into `build/bun.js`, which you can deploy to any hosting provider that supports running JS with Bun.
10 |
--------------------------------------------------------------------------------
/website/src/pages/docs/deployment/cloudflare-workers.mdx:
--------------------------------------------------------------------------------
1 | # Deploying your Fuse API with Cloudflare Workers
2 |
3 | First, build your API server into a single JS file that can be deployed to Cloudflare Workers:
4 |
5 | ```sh
6 | fuse build --adapter cloudflare
7 | ```
8 |
9 | This will build your server into `build/cloudflare.js`, which you can deploy to Cloudflare Workers.
10 |
--------------------------------------------------------------------------------
/website/src/pages/docs/deployment/index.mdx:
--------------------------------------------------------------------------------
1 | # Deployment
2 |
3 | Your Fuse API is built in TypeScript and can theoretically be compiled to run on any JavaScript runtime in production. Practically, we have tested and officially support the following compilation targets:
4 |
5 | - [Node](/docs/deployment/node)
6 | - [Next.js API Routes](/docs/deployment/nextjs)
7 | - [Lambda](/docs/deployment/lambda)
8 | - [Cloudflare Workers](/docs/deployment/cloudflare-workers)
9 | - [Bun](/docs/deployment/bun)
10 |
11 | TL;DR: The `fuse build` command takes an `--adapter` argument that will compile your API to run on the specified runtime:
12 |
13 | ```sh
14 | fuse build --adapter node/lambda/cloudflare/bun
15 | ```
--------------------------------------------------------------------------------
/website/src/pages/docs/deployment/lambda.mdx:
--------------------------------------------------------------------------------
1 | # Deploying your Fuse API with Lambda
2 |
3 | First, build your API server into a single JS file that can be run on a Lambda:
4 |
5 | ```sh
6 | fuse build --adapter lambda
7 | ```
8 |
9 | This will build your server into `build/lambda.js`, which you can deploy to your favorite lambda host.
10 |
--------------------------------------------------------------------------------
/website/src/pages/docs/deployment/nextjs.mdx:
--------------------------------------------------------------------------------
1 | # Deploying with Next.js API Routes
2 |
3 | Fuse exposes its API as an API route at `/api/fuse` when you use [the Next.js plugin](/docs/setting-fuse-up-manually/nextjs).
4 |
5 | That means when you deploy your Next.js app (to Vercel, Netlify, or similar hosts that support Next.js API routes) your API will be deployed with it automatically! No extra steps needed.
--------------------------------------------------------------------------------
/website/src/pages/docs/deployment/node.mdx:
--------------------------------------------------------------------------------
1 | # Deploying your Fuse API with Node
2 |
3 | First, build your API server into a single JS file that can be run with Node:
4 |
5 | ```sh
6 | fuse build --adapter node
7 | ```
8 |
9 | This will build your server into `build/node.js`. You can deploy it to your favorite Cloud and run it with node (`node build/node.js`).
10 |
--------------------------------------------------------------------------------
/website/src/pages/docs/fuse-method/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Overview",
3 | "vs-backend-for-frontends": "vs. Backend-for-Frontends",
4 | "vs-graphql-federation": "vs. GraphQL Federation"
5 | }
6 |
--------------------------------------------------------------------------------
/website/src/pages/docs/fuse-method/vs-graphql-federation.mdx:
--------------------------------------------------------------------------------
1 | # GraphQL Federation vs. the Fuse Method
2 |
3 | 
4 |
5 | Another solution commonly adopted to address [the core problems at the interface between backend and frontend](/docs/fuse-method) is GraphQL Federation. Federation composes a central GraphQL API (”supergraph”) from many underlying microservices that each expose a small part of the GraphQL schema (”subgraphs”).
6 |
7 | Federation addresses the second and third problems at the interface between backend and frontend teams: unblocking UI development from API development and different UIs needing different data.
8 |
9 | However, it significantly exacerbates the first problem: the differences in how backend and frontend teams think.
10 |
11 | Federation requires that every microservice expose a schema that matches not only one UI’s needs but *all* the UIs’ needs, as it gets accessed by all the clients directly, with no ability for frontend engineers to transform it.
12 |
13 | This requires significantly more communication between frontend and backend teams as backend engineers are forced to learn about requirements across all the UIs. They must also learn an additional technology they otherwise do not utilize in their day-to-day work for their frontend teams.
14 |
15 | In comparison, the Fuse Method solve all three problems at the interface between backend and frontend teams while allowing backend teams to continue working the way they know and love without adaptations.
16 |
--------------------------------------------------------------------------------
/website/src/pages/docs/server/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "queries-and-mutations": "Queries and Mutations",
3 | "nodes": "Nodes",
4 | "lists": "Handling lists",
5 | "objects-enums-unions-interfaces": "Objects, Enums, Unions, and Interfaces",
6 | "context": "Context",
7 | "integrations": "Integrating data sources",
8 | "error-handling": "Error handling",
9 | "authorization": "Authorization",
10 | "mocking": "Mocking data"
11 | }
12 |
--------------------------------------------------------------------------------
/website/src/pages/docs/server/integrations/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Overview",
3 | "rest": "REST",
4 | "prisma": "Prisma",
5 | "drizzle": "Drizzle",
6 | "kysely": "Kysely",
7 | "grpc": "gRPC"
8 | }
9 |
--------------------------------------------------------------------------------
/website/src/pages/docs/server/integrations/grpc.mdx:
--------------------------------------------------------------------------------
1 | # gRPC
2 |
3 | We recommend using [connect-es](https://github.com/connectrpc/connect-es) to get TypeScript typings and a client for your gRPC services. That will make integrating them into your Fuse API a breeze.
4 |
5 | We are working on an example that shows this in practice. Stay tuned!
6 |
--------------------------------------------------------------------------------
/website/src/pages/docs/server/integrations/rest.mdx:
--------------------------------------------------------------------------------
1 | # REST
2 |
3 | With REST often you won't have static typing readily available, unless you are
4 | using OpenAPI, when you are using the OpenAPI spec you can leverage that to
5 | generate types for your API with [openapi-typescript](https://github.com/drwpow/openapi-typescript).
6 |
7 | When you have generated these you can use that to type your `node` or `query-fields`
8 |
9 | ```ts
10 | import { node, NotFoundError, addQueryFields } from 'fuse'
11 | import { inArray, sql } from 'drizzle-orm';
12 | import { paths } from './generated'
13 |
14 | type User = paths["/user"]["get"]["responses"][200]["content"]["application/json"]["schema"]
15 |
16 | export const UserNode = node({
17 | name: 'User',
18 | async load(ids) {
19 | // Query for the list of users with the `ids`
20 | const result = await getUsers(ids);
21 | return ids.map((id) => result.find((x) => x.id === id) || new NotFoundError('Could not find user.'));
22 | },
23 | fields: (t) => ({
24 | name: t.exposeString('name'),
25 | }),
26 | })
27 | ```
28 |
--------------------------------------------------------------------------------
/website/src/pages/docs/setting-fuse-up-manually/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Overview",
3 | "nextjs": "Next.js Integration"
4 | }
5 |
--------------------------------------------------------------------------------
/website/src/pages/docs/setting-fuse-up-manually/nextjs.mdx:
--------------------------------------------------------------------------------
1 | import { Callout } from 'nextra/components'
2 | import { Explain } from "@/components/Explain"
3 |
4 | # Next.js
5 |
6 | ## Using Fuse with Next.js
7 |
8 | Fuse ships with first-class support for Next.js. If you run `create-fuse-app` in a Next.js project it will automatically:
9 |
10 | 1. Add the Next.js plugin to your `next.config.js`
11 | 1. Create a `/api/fuse` API route
12 |
13 | You do not have to manually run `fuse dev` or `fuse build` as the Next.js plugin does this for you automatically.
14 |
15 | ### Setting up Fuse with Next.js manually
16 |
17 | #### Add the Next.js plugin to your `next.config.js`
18 |
19 | ```js
20 | const { nextFusePlugin } = require('fuse/next/plugin')
21 |
22 | /** @type {import('next').NextConfig} */
23 | const nextConfig = nextFusePlugin()({
24 | // Your Next.js config here
25 | })
26 |
27 | module.exports = nextConfig
28 | ```
29 |
30 | #### Create the `/api/fuse` API route
31 |
32 | This API route will serve as the entrypoint to the GraphQL API that Fuse creates. If you are using Next.js’s app router, add a file at `app/api/fuse/route.ts` and copy the below code to it:
33 |
34 | ```ts
35 | import { createAPIRouteHandler } from 'fuse/next'
36 |
37 | const handler = createAPIRouteHandler()
38 |
39 | export const GET = handler
40 | export const POST = handler
41 | ```
42 |
43 |
44 | If you are using Next.js's Pages Router, replace createAPIRouteHandler
with createPagesRouteHandler
instead.
45 |
46 |
47 | That’s it! Fuse will now serve a GraphQL API at `/api/fuse`.
48 |
--------------------------------------------------------------------------------
/website/src/pages/docs/troubleshooting.mdx:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | ## Next runs on a different port than 3000
4 |
5 | You can alter the port in the fuse-plugin in the `next.config` as follows
6 |
7 | ```js
8 | const nextConfig = nextFusePlugin({ port: 3001 })({})
9 | ```
10 |
11 |
--------------------------------------------------------------------------------
/website/src/svg.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | import { SVGProps } from 'react'
3 | const content: (props: SVGProps) => JSX.Element
4 | export default content
5 | }
6 |
--------------------------------------------------------------------------------
/website/src/utils/tailwind.ts:
--------------------------------------------------------------------------------
1 | import clsx, { ClassValue } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | /**
5 | * Composes Tailwind classes
6 | */
7 | export function cn(...inputs: ClassValue[]) {
8 | return twMerge(clsx(inputs))
9 | }
10 |
--------------------------------------------------------------------------------
/website/theme.config.tsx:
--------------------------------------------------------------------------------
1 | import { useConfig, type DocsThemeConfig } from 'nextra-theme-docs'
2 | import { useRouter } from 'next/router'
3 | import { FuseLogoWithName } from './src/components/icons'
4 | import { getHeadMetaContent } from './src/components/HeadMeta'
5 |
6 | function HeadMeta() {
7 | const { asPath } = useRouter()
8 | const { frontMatter, title } = useConfig()
9 | const url = 'https://fusedata.dev' + asPath
10 |
11 | return getHeadMetaContent({
12 | title,
13 | url,
14 | description: frontMatter.description,
15 | })
16 | }
17 |
18 | const themeConfig: DocsThemeConfig = {
19 | logo: ,
20 | primaryHue: 88,
21 | primarySaturation: {
22 | light: 50,
23 | dark: 99,
24 | },
25 | docsRepositoryBase: 'https://github.com/StellateHQ/fuse/tree/main/website',
26 |
27 | useNextSeoProps() {
28 | return {}
29 | },
30 |
31 | head: HeadMeta,
32 |
33 | footer: {
34 | text: (
35 |
36 | © {new Date().getFullYear()}{' '}
37 |
38 | Stellate, Inc
39 |
40 |
41 | ),
42 | },
43 |
44 | gitTimestamp: null,
45 | }
46 |
47 | export default themeConfig
48 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "paths": {
17 | "@/*": ["./src/*"]
18 | }
19 | },
20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "./src/svg.d.ts"],
21 | "exclude": ["node_modules"]
22 | }
23 |
--------------------------------------------------------------------------------