Giter VIP home page Giter VIP logo

fuse's Introduction

Fuse

Fuse: End-to-end typesafe data fetching for frontend teams at scale

Getting Started

When you are in the root of your app run the following command. This will install all the packages and generate the files you need.

npx create-fuse-app

Then, run npx fuse dev and your API will be running at localhost:4000/graphql!

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)

Querying your data layer

import { graphql } from '@/fuse'
import { execute } from '@/fuse/server'

const UserQuery = graphql(`
  query User($id: ID!) {
    user(id: $id) {
      id
      name
    }
  }
`)

export default async function Page() {
  const result = await execute({
    query: UserQuery,
    variables: { id: '1' },
  })

  return <p>Welcome {result.data?.user?.name}</p>
}

Read the documentation for more information about using Fuse.

Quicklinks to some of the most-visited pages:

License

Licensed under the MIT License, Copyright © 2023-present Stellate, Inc.

See LICENSE for more information.

fuse's People

Contributors

jovidecroock avatar mxstbr avatar themightypenguin avatar github-actions[bot] avatar bogdansoare avatar nathanchapman avatar snlaight avatar koichikiyokawa avatar y-temp4 avatar csol705 avatar kramhuber avatar usrnk1 avatar

Stargazers

 avatar Mario Costa Junior avatar Leandro Otoni avatar  avatar  avatar Tahmid Bin Taslim Rafi avatar Kuwa avatar Luis Enrique avatar Esteban Saiz avatar Dennis O'Keeffe avatar Soumyajit Pathak avatar Simon Babay avatar SIRAWICH VOUNGCHUY avatar Nkwelle Epie avatar Jyeu avatar blurk avatar tosh shimayama (satake) avatar Atiwat Seenark avatar Matt Sahr avatar  avatar Lemuel Okoli avatar Beno avatar Dawid Makos avatar Jackson Kasi avatar Louis Loo avatar Muhammad Iqbal Fasri avatar Eric Vintimilla avatar 3zbumban avatar Pete Richardson avatar Richard avatar Ali Torki avatar Shantanu Gautam avatar Christian Boyle avatar  avatar Alex avatar Lukas Holzer avatar nickluger avatar Anthony BILLON avatar Daniel Sandiego avatar Luke Grecki avatar Park Hee Chan avatar Enrico Emiro avatar Pkmmte Xeleon avatar Alex Whiteside avatar Johny Hoffman avatar Maximilian Stümpfl avatar Tim Pulver avatar @lpha avatar Emanuel Herrera avatar Abdessalam Benharira avatar tangkikodo avatar Rodrigo Weilg avatar Agata Naomichi avatar Ivan Nikolić avatar Konrad Pasternak avatar Carlos Valdez avatar Hannah Chartier avatar Khoa avatar Tim Mikeladze avatar CsabaK avatar Hannah A avatar BlasterPistol avatar Nikita avatar Sarah Egan avatar Arkadiusz Chodór avatar Roshan Jonnalagadda avatar souvik dutta avatar Soumya Subhrajit Bag avatar Hiro Inagaki avatar  avatar Aaron Lifton avatar Ernst Salzmann avatar Jeremy Pinnix avatar  avatar Jeffrey avatar Emin Gasimov avatar  avatar Ahmed Eid avatar Joshua Wilson avatar Mathieu Acthernoene avatar  avatar Mustapha Turki avatar Pepijn avatar Mohammed Zeeshan avatar Viorel Cojocaru avatar Bastian Kistner avatar Satoshi Ebisawa avatar Kyle Gray avatar Robert McGuinness avatar Brian Anglin avatar Damián Ricobelli avatar  avatar Matt avatar  avatar Roland Kiraly avatar Daniel Williams avatar Wayan jimmy avatar Sehyun Chung avatar Joe Seifi avatar Nikita Zhenev avatar

Watchers

Andreas Heiberg avatar Uri Goldshtein avatar Camilo avatar  avatar  avatar Tom Houlé avatar

fuse's Issues

Testing

We cannot trust this or recommend it to our clients when there is only 1 test file

Screenshot 2023-12-16 at 12 35 27 Screenshot 2023-12-16 at 12 38 46

Please add more testing so we can trust this framework and or reach out for help ✌️

Bug: fuse directory isn't generated

Description

I've noticed the fuse directory isn't being generated after running fuse dev in a fresh project created by create-vite. Once the project was created, I ran npx create-fuse-app, which generated the types directory and _context.ts, schema.graphql, and .vscode/settings.json files, modified tsconfig.json, and started the server, but there wasn't any terminal output showing the codegen had run. Even though the server does start up, the only query field available is _version.

Versions

  • TypeScript Version: 5.3.3
  • Fuse version: 0.11.3
  • Node: 18.16.0

RFC: Authorization

Summary

We are considering adding authorization as a first-class citizen to Fuse.js.

Proposed Solution

None yet. It's unclear whether there is a good use case for this, as the common scenario will be that the underlying microservices & third-party APIs already have to handle authentication & authorization.

However, in my personal experience, handling authorization at the GraphQL level (in my case simply through making sure that each resolver has an authorization check) was terrific to work with and kept our system well secure enough. We could e.g. require each node to have an authorize check that explicitly has to mark things as public via authorize: () => true so that it's explicitly note.

Note: Pothos has two related plugins: Authz & Scope Auth.

RFC: First class mocking support

Summary

Currently we have a documentation page around mocking fields/entities that aren't implemented yet, we could take this further by integrating faker.js we could make this as easy as adding { mock: 'phoneNumber' } or something similar which would mock the value when there isn't one present on the payload.

Proposed Solution

We extend the pothos field builder API with a mock option that integrates faker, when no value is present we invoke the faker method and mock the value.

Export a `createDefaultClient` method

Currently we duplicate creating the client a lot across client/pages/server which might feel a bit intimidating. We could offer a default-client that has opinions out of the box. Then the only thing the user needs to do is pass in the url

  • client: cacheExchange, ssr, fetchExchange
  • server: fetchExchange
  • pages: cacheExchange, ssr, fetchExchange

Feedback for “Fuse Introduction” - Problem with loading initial graphql schema

Something is broken with the initial setup of the application.

Tried to generate fresh app via npx create-fuse-app in clean directory, the generator works, but after running npx fuse dev the graphql generator has some problems/errors and it doesn't pick up the schema.

Steps to reproduce

mkdir fuse-example
cd fuse-example
npx create-fuse-app

This works fine

◇  Installed fuse!
│
◇  Created Base files!
│
└  You're all set to work with your Fuse API!

But then, after starting dev server, you get:

npx fuse dev 

Server listening on http://localhost:4000/graphql
✔ Parse Configuration
⚠ Generate outputs
  ❯ Generate to /Projects/fuse-example/fuse/
    ✖
      Failed to load schema from ./schema.graphql:
      Unable to find any GraphQL type definitions for the following pointers:
      - ./schema.graphql
      Error:
      Unable to find any GraphQL type definitions for the following pointers:
      - ./schema.graphql
      at prepareResult (file:///Projects/fuse-example/node_modules/@graphql-tools/load/esm/load-typedefs.js:77:15)
      at loadTypedefs (file:///Projects/fuse-example/node_modules/@graphql-tools/load/esm/load-typedefs.js:37:20)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async loadSchema (file:///Projects/fuse-example/node_modules/@graphql-tools/load/esm/schema.js:12:21)
      at async loadSchema (file:///Projects/fuse-example/node_modules/@graphql-codegen/cli/esm/load.js:33:24)
      at async file:///Projects/fuse-example/node_modules/@graphql-codegen/cli/esm/codegen.js:186:69
      at async file:///Projects/fuse-example/node_modules/@graphql-codegen/cli/esm/codegen.js:185:56
      at async file:///Projects/fuse-example/node_modules/@graphql-codegen/cli/esm/codegen.js:62:17
      at async Task.run (file:///Projects/fuse-example/node_modules/listr2/dist/index.js:960:11)
      at async /Projects/fuse-example/node_modules/p-map/index.js:57:22
      GraphQL Code Generator supports:
      - ES Modules and CommonJS exports (export as default or named export "schema")
      - Introspection JSON File
      - URL of GraphQL endpoint
      - Multiple files with type definitions (glob expression)
      - String in config file
      Try to use one of above options and run codegen again.
    ◼ Load GraphQL documents
    ◼ Generate
  ℹ Watching for changes in /Projects/fuse-example...

The server starts, but the users schema isn't loaded correctly.

Discussion: Custom scalars support

Summary

We need to support custom-scalars, this currently has a bit of a roadblock with regards to the schema-builder where it will expect the custom scalars to be present on the generic or it won't know how to type them. Maybe we can be clever and tell TypeScript that anything we don't know how to type should be unknown or any but that does loosen our strict-typings.

We would then need to extend the custom scalars to the configuration in our codegen, it's possible that they are automatically picked up and case to unknown but ideally the user is enabled to type these themselves.

Centralize `load(ids) => Node` on the Node

I don't know if this is possible, but defining .load on every t.loadable is seemingly unnecessary duplication since the node itself could define how to load itself.

I.e. instead of

builder.objectField(LaunchNode, 'rocket', (t) =>
  t.loadable({
    type: RocketNode,
    resolve: (parent) => {
      return parent.rocket.rocket_id
    },
    load: async (ids: string[]) => {
      return Promise.all(ids.map((id) => rocketsDatasources.getOne(id)))
    },
  }),
)

It would be

const RocketNode = node('Rocket', rocketsDatasources).implement({
  isTypeOf: () => true,
  fields: (t) => ({
    cost: t.exposeInt('cost_per_launch'),
    country: t.exposeString('country'),
    company: t.exposeString('company'),
    description: t.exposeString('description'),
  }),
  // This data loader applies to all t.loadable definitions with RocketNode
  load: async (ids: string[]) => {
      return Promise.all(ids.map((id) => rocketsDatasources.getOne(id)))
    },
  }),
})

builder.objectField(LaunchNode, 'rocket', (t) =>
  t.loadable({
    type: RocketNode,
    resolve: (parent) => {
      return parent.rocket.rocket_id
    },
  }),
)

Then, the question becomes: do we still need the distinction between t.field and t.loadable or could we just have t.field? That would further simplify the API surface area to:

builder.objectField(LaunchNode, 'rocket', (t) =>
  t.field({
    type: RocketNode,
    resolve: (parent) => {
      return parent.rocket.rocket_id
    },
  }),
)

RFC(create-fuse-app): gql.tada by default in empty dirs

Summary

Currently, when running create-fuse-app in an empty dir without a tsconfig.json it defaults to codegen. However, moving forward we want people to be using gql.tada.

Proposed Solution

In a dir without a tsconfig.json, write a default tsconfig.json to the dir and add the gql.tada setup to it.

RFC: Turbopack support

Summary

I tried next dev --turbo after running create-fuse-app and everything worked great—except the filesystem-based types/ folder isn't being picked up (as seen here because my types/Story.ts file isn't being picked up):

CleanShot 2024-01-14 at 11 16 06@2x

If I remove --turbo it works great:

CleanShot 2024-01-14 at 11 41 19@2x

Everything else seems to work!

Bug: type in create-fuse-app doesn't modify next.config.mjs

Description

I've encountered a typo in the custom Babel plugin provided by the Fuse project create-fuse-app. The typo occurs in the ExportDefaultDeclaration visitor function where t.callExprssion is used instead of the correct method name t.callExpression. This typo prevents the plugin from correctly transforming the default export declaration in ES module files.
CleanShot 2024-02-05 at 13 51 29@2x

Versions

  • TypeScript Version: 5.3.3
  • Create Fuse App version: 0.5.0

`create-fuse-app` fails with the app dir

Description

I've created a new project using npx create-next-app@latest. After that I'm running pnpm create fuse-app. This fails with the following error

[Error: ENOENT: no such file or directory, mkdir '/myrepo/src/pages/api'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'mkdir',
  path: '/myrepo/src/pages/api'
}

This is because the cli checks if the there is an app folder in the root rather than also checking for src/app

Reproduction

  1. Create new app router next app using npx create-next-app@latest
  2. Run pnpm create fuse-app
  3. Error

Versions

  • TypeScript Version: 5.0.0
  • Fuse version: 0.9.0

RFC: Data source connectors

Summary

Allow quick adding of nodes that are backed by REST, gRPC, and/or other data sources. (unsure about dbs)

Proposed Solution

TODO

Bug: Duplicate typename error when saving types file

Description

I'm unsure if these are related, but the schema in a fresh Vite app with Fuse doesn't contain the example user field after running fuse dev and an error is thrown when saving the User.ts file.

pnpm create vite test-fuse --template react-ts
cd test-fuse
pnpm create fuse-app
pnpm fuse dev

The server starts as expected but the user field isn't present in the schema. Hitting save on /types/User.ts throws the following error:

Error when evaluating SSR module /types/User.ts:
|- PothosSchemaError: Duplicate typename: Another type with name User already exists.
    at new PothosError (file:///path/to/node_modules/.pnpm/@[email protected][email protected]/node_modules/@pothos/core/esm/errors.js:4:9)
    at new PothosSchemaError (file:///path/to/node_modules/.pnpm/@[email protected][email protected]/node_modules/@pothos/core/esm/errors.js:10:9)
    at ConfigStore.addTypeConfig (file:///path/to/node_modules/.pnpm/@[email protected][email protected]/node_modules/@pothos/core/esm/config-store.js:116:19)
    at SchemaBuilder.objectType (file:///path/to/node_modules/.pnpm/@[email protected][email protected]/node_modules/@pothos/core/esm/builder.js:59:26)
    at ImplementableLoadableNodeRef.implement (file:///path/to/node_modules/.pnpm/@[email protected][email protected]/node_modules/@pothos/core/esm/refs/object.js:30:29)
    at SchemaBuilder.loadableNode (file:///path/to/node_modules/.pnpm/@[email protected]_@[email protected][email protected][email protected]/node_modules/@pothos/plugin-dataloader/esm/schema-builder.js:59:9)
    at Module.node (file:///path/to/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/fuse/dist/builder.mjs:183:25)
    at eval (/path/to/types/User.ts:5:40)
    at async instantiateModule (file:///path/to/node_modules/.pnpm/[email protected]/node_modules/vite/dist/node/chunks/dep-9A4-l-43.js:50861:9)

Versions

  • TypeScript Version: 5.3.3
  • Fuse version: 0.11.4

Bug: webpack parse error in Next.js

Description

Using Fuse with Next.js, when I run next dev I get an ugly parse error in my terminal despite everything looking like it works fine:

yarn dev
yarn run v1.22.17
$ next dev
   ▲ Next.js 14.0.5-canary.41
   - Local:        http://localhost:3000
   - Environments: .env.local
   - Experiments (use at your own risk):
     · ppr

   automatically enabled Fast Refresh for 1 custom loader
 ✓ Ready in 1880ms
 ○ Compiling /api/fuse ...
 ✓ Compiled /api/fuse in 1177ms (636 modules)
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/codegen.js for build dependencies failed at 'import(isESMModule
<w>             ? /**
<w>                * For ESM we currently have no "resolve path" solution
<w>                * as import.meta is unavailable in a CommonJS context
<w>                * and furthermore unavailable in stable Node.js.
<w>                **/
<w>                 mod
<w>             : relativeRequire.resolve(mod))'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/config.js for build dependencies failed at 'import(relativeRequire.resolve(mod, {
<w>             paths: [process.cwd()],
<w>         }))'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@graphql-tools/url-loader/esm/index.js for build dependencies failed at 'import(`${moduleName}`)'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/load/esm/load-typedefs/collect-sources.js for build dependencies failed at 'import(m)'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/code-file-loader/esm/index.js for build dependencies failed at 'import(m)'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/code-file-loader/esm/load-from-module.js for build dependencies failed at 'import(filepath)'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy] Caching failed for pack: Error: Can't resolve 'svelte2tsx' in '/Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/graphql-tag-pluck/esm'
<w> while resolving 'svelte2tsx' in /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/graphql-tag-pluck/esm as file
<w>  at resolve esm file svelte2tsx
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/graphql-tag-pluck/esm/index.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/graphql-tag-pluck/esm/index.js
<w>  at resolve esm file @graphql-tools/graphql-tag-pluck
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/code-file-loader/esm/index.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/code-file-loader/esm/index.js
<w>  at resolve esm file @graphql-tools/code-file-loader
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/load.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/load.js
<w>  at resolve esm file ./load.js
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/config.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/config.js
<w>  at resolve esm file ./config.js
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/index.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/index.js
<w>  at resolve esm file @graphql-codegen/cli
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected][email protected]/node_modules/fuse/dist/next/plugin.mjs
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected][email protected]/node_modules/fuse/dist/next/plugin.mjs
<w>  at resolve esm file fuse/next/plugin
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/next.config.mjs
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/next.config.mjs
<w>  at resolve commonjs /Users/mxstbr/projects/rauchg/next-ai-news/next.config.mjs
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/codegen.js for build dependencies failed at 'import(isESMModule
<w>             ? /**
<w>                * For ESM we currently have no "resolve path" solution
<w>                * as import.meta is unavailable in a CommonJS context
<w>                * and furthermore unavailable in stable Node.js.
<w>                **/
<w>                 mod
<w>             : relativeRequire.resolve(mod))'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/config.js for build dependencies failed at 'import(relativeRequire.resolve(mod, {
<w>             paths: [process.cwd()],
<w>         }))'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@graphql-tools/url-loader/esm/index.js for build dependencies failed at 'import(`${moduleName}`)'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/load/esm/load-typedefs/collect-sources.js for build dependencies failed at 'import(m)'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/code-file-loader/esm/index.js for build dependencies failed at 'import(m)'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy/webpack.FileSystemInfo] Parsing of /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/code-file-loader/esm/load-from-module.js for build dependencies failed at 'import(filepath)'.
<w> Build dependencies behind this expression are ignored and might cause incorrect cache invalidation.
<w> [webpack.cache.PackFileCacheStrategy] Caching failed for pack: Error: Can't resolve 'svelte2tsx' in '/Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/graphql-tag-pluck/esm'
<w> while resolving 'svelte2tsx' in /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/graphql-tag-pluck/esm as file
<w>  at resolve esm file svelte2tsx
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/graphql-tag-pluck/esm/index.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/graphql-tag-pluck/esm/index.js
<w>  at resolve esm file @graphql-tools/graphql-tag-pluck
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/code-file-loader/esm/index.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected][email protected]/node_modules/@graphql-tools/code-file-loader/esm/index.js
<w>  at resolve esm file @graphql-tools/code-file-loader
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/load.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/load.js
<w>  at resolve esm file ./load.js
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/config.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/config.js
<w>  at resolve esm file ./config.js
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/index.js
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@graphql-codegen/cli/esm/index.js
<w>  at resolve esm file @graphql-codegen/cli
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected][email protected]/node_modules/fuse/dist/next/plugin.mjs
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected][email protected]/node_modules/fuse/dist/next/plugin.mjs
<w>  at resolve esm file fuse/next/plugin
<w>  at file dependencies /Users/mxstbr/projects/rauchg/next-ai-news/next.config.mjs
<w>  at file /Users/mxstbr/projects/rauchg/next-ai-news/next.config.mjs
<w>  at resolve commonjs /Users/mxstbr/projects/rauchg/next-ai-news/next.config.mjs
^C

Reproduction: Run this PR to next-ai-news locally and run next dev

Versions

  • TypeScript Version: ^5
  • Fuse version: 0.12.1

RFC: Federation/stitching/composition/…

Summary

Many companies that use GraphQL at scale today use GraphQL Federation, which essentially composes many microservices that expose a part of the GraphQL schema.

When there is a backend team that's already using GraphQL and liking it, being able to stitch their schema into the data layer might be appealing.

Another use case is third-party APIs that are already GraphQL.

Another use case is companies that are already using GraphQL and want to stitch their existing API into Fuse—but I'm unsure why they would use Fuse.js on top of that 🤔

Proposed Solution

Fuse.js could either:

  1. Support composing/connecting subgraphs (which would require us to reimplement Federation)
  2. Support stitching in other GraphQL APIs, including existing supergraphs

RFC: Give visibility into the cost of an operation

Summary

In fuse we already optimise a lot by going for dataloader,... however it's still hard to observe the potential cost of an operation. We should work on getting insights for this.

Proposed Solution

There are a number of ways to tackle this, we could generate a file that has all the paths of a given operation and cross-reference that in development to how long the server takes to resolve all of that.

We could leverage complexity scoring to have a base heuristic for query-cost.

We could leverage grafast under the hood, grafast already has a base concept of query-plans which makes it easier to reason about query-cost. We own the server-layer so there's a good chance that we can hide the complexity of grafast for the consumer of the library but give them the performance benefits.

RFC: ESLint preset

Summary

We would do good in helping people see GraphQL best practices like, fragment co-location, ... This isn't just something we can do with documentation, it should be a continuous effort which we can achieve with some type of linting.

The linting should be fuse-specific so we can see what's being done on the server and how that can be improved or how it can be more fuse-like. What's being done on the client and how we can set folks up for better re-use, when we see a component using data from a query we could encourage folks to create a fragment so that when they use that component next time it declares its own data-requirements making it easier to iterate on it.

Initial set of things to lint for

  • Fragment co-location
  • Fragment naming conventions
  • Setting up limits (i.e. pagination limit can't exceed 100)
  • Queried fields that aren't being used
  • ...

Would love to know what others think is worth highlighting here both on client and/or server.

RFC: Allow overriding the `id` field

Summary

I have a type called Story that has an ID that is always a string. However, Fuse auto-generates id: ID!, which (in TypeScript) converts to string | number. That, in turn, means that I'm getting errors from typechecking when I do story.id.replace(…) because .replace doesn't exist on number—but my ID will always be a string!

CleanShot 2024-01-18 at 07 14 48@2x

Proposed Solution

Allow overriding the id field manually:

export const Story = node({
  // …
  fields: t => ({
    id: t.exposeString('id', { nullable: false })
  })
})

RFC: Merge types/ and fuse/ folders

Summary

Right now, users interact with a types/ folder that contains ~a file per node, and then Fuse generates a fuse/ folder that people use from their client(s). (import { useQuery } from '@/fuse/client')

Proposed Solution

I propose we centralize all of it into the fuse/ folders. Users write their types in their, and then we also generate the files in there and they import from there from the client.

First idea for a potential folder structure:

.
└── fuse/
    ├── User.ts
    ├── Product.ts
    ├── _context.ts
    └── _generated/
        ├── client.ts
        ├── fragment-masking.ts
        ├── gql.ts
        ├── graphql.ts
        ├── index.ts
        ├── pages.ts
        └── server.ts

The big question is what happens to the client imports. With this structure, they'd look like import { useQuery } from '@/fuse/_generated/client', which isn't great.

Since client.ts, index.ts, pages.ts, and server.ts are simple re-exports of the underlying libraries and the really truly generated custom files are fragment-masking.ts, gql.ts, and graphql.ts, we could try this folder structure instead:

.
└── fuse/
    ├── User.ts
    ├── Product.ts
    ├── client.ts
    ├── index.ts
    ├── pages.ts
    ├── server.ts
    ├── context.ts
    └── generated/
        ├── fragment-masking.ts
        ├── gql.ts
        └── graphql.ts

This would fully preserve the client imports while centralizing everything else. The downside of this is that the index/pages/server/client.ts files get muddled with the types/nodes, and it's a bit of an odd semantics because users are meant to touch context.ts but not server.ts — that's not intuitive.

Another option that clearly marks index/pages/server/client.ts distinct from the types files would be to prefix them with a _, just like we do with _generated and _context in the original example:

.
└── fuse/
    ├── User.ts
    ├── Product.ts
    ├── context.ts
    ├── _client.ts
    ├── _index.ts
    ├── _pages.ts
    ├── _server.ts
    └── _generated/
        ├── fragment-masking.ts
        ├── gql.ts
        └── graphql.ts

But that makes client-side imports ugly. import from '@/fuse/_client 😕 Also, _index.ts obviously doesn't work for import from '@/fuse' so we'd need a different name for those exports.

Any other ideas?

RFC: Trusted Documents/Persisted Operations

Summary

We do not want to expose all of our GraphQL API to our users, we already do a decent chunk of obfuscation in production by disabling introspection, masking errors and disabling field suggestions. That however doesn't stop anyone from deriving this out of the calls the front-end makes, Persisted Operations are currently being specced out however they are a pretty established concept already in Relay/...

In doing so we will send a hash of the operation to the server rather than the complete document which makes for further obfuscation of the available GraphQL API and reduces the attack surface as we can embed pagination parameters like limit into the hash so it can't be increased to an unreasonable amount.

Proposed Solution

We enable the GraphQL Code Generator plugin to generate this by default, our server can pick up the generated file and store it so it's aware of all persisted-operations and the last part would be adding the persisted-exchange.

This however does leave a few problems on the table, what if our user is multi-client, their mobile app might have slightly different operations that need to be combined with the web-application. We need to take this into consideration when building the solution so we can for instance...

  • support multiple stores of operations
  • allow combinations of files

RFC: Run prettier over generated files if a config exists

Summary

Every time I run dev my VSCode sidebar looks like this, even if I haven't changed anything about my schema:

CleanShot 2023-12-15 at 10 07 26@2x

This happens because I use prettier, which formats files differently than the codegen.

Proposed Solution

If there is a prettier config file in the folder or a parent folder, run prettier over the generated code with the actual config to avoid unnecessary git diffs.

RFC: Add optional `PageInfo` support to `t.list`

Summary

I have a list of items that is paginated with pages. Using t.connection with edgeNullable: false (which I want because the edges can't be nullable and it simplifies the client code) requires me to return cursor on every edge—which doesn't make a lot of sense for page-based pagination.

While t.list doesn't require me to return a cursor and is the better fit for this use case, with t.list I can't add the same PageInfo type that t.connection uses to return pagination information ( especially hasNextPage and hasPreviousPage)

Proposed Solution

t.list({
  pageInfo: true,
  // → Adds the PageInfo type to the schema if no `t.connection` has already added it
  // → Requires returning `pageInfo` from `resolve`
})

Discussion: Config option for including .js on import paths

Summary

When Fuse generates files in a Node project with the module compiler option set to NodeNext, the import paths cause a TS error:

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './tada.js'?

Code Generator has an emitLegacyCommonJSImports option for controlling whether to add the .js extension to import paths. If gql.tada will be preferred over codegen going forward, would it be possible to include a way to control the import paths in generated Fuse files?

RFC: Debugging client-cache issues

Summary

We have two types of caches that can be used the document cache and the normalised cache. In the document cache we track the __typename properties contained in operations, when a mutation passes by we check the affected __typename and clear those cache entries, any active query will be refetched and our application is fresh again. With a normalised cache we store all entries and mutations will be attempted to reconcile with the data in-memory, custom updaters, resolvers, ... can be written to do advanced reconciliation.

Each has their own set of issues, for instance when we are dealing with the document cache we can have issues with queries that return null or an empty array as we can't derive any __typename properties for them and mutations that return a scalar value won't be used to derive the affected types. In a normalised cache we face the issue that when we do a create mutation that we need a custom updater and similar when a scalar value is returned.

Proposed Solution

We wrap the document cache to warn folks for when a scalar value is returned from a mutation so that they are aware of a potential need for context.additionalTypenames and we warn for when an empty array/null value is returned.

For the normalised cache we do something similar that when we perform a mutation and we can't find the entity in cache that we warn the user, this could prompt them to write a custom updater and similarly when we see a scalar value returned we warn.

These warnings should be disabled in production environments and are means to guide our users to cache issues before they can reach production.

Discussion: Export-based API instead of builder.xyz

I have the strong opinion that this is the much better API, as it more closely aligns with the common frontend mental model that Next.js's routes & API routes have:

export const LaunchNode = node({}) # See #3 and #4

export const queries: Queries = (t) => ({})

export const mutations: Mutations = (t) => ({})

Don't empty the generated files during generation

When making a change to a client fragment, all the typings disappear until the codegen has finished. You can see this in the demo video when I change the client fragment because all the fragment typings get red underlines:

video-sample.mp4

Ideally, they wouldn't disappear while the codegen is running to make for a neater experience in-editor.

RFC: Error handling

Summary

Error handling is a hotly debated topic in the GraphQL ecosystem that comes with no broadly recommended best practice today.

Proposed Solution

The way we handle this at Stellate (which we quite like) looks like this:

import { GraphQLError } from 'graphql'

export abstract class StellateError extends GraphQLError {
  abstract readonly name: string

  constructor(
    message: string,
    extensions: {
      code: string
      http?: {
        status: number
      }
    },
  ) {
    super(message, {
      extensions,
    })
  }
}

/** For use when user is not authenticated or unknown. */
export class AuthenticationError extends StellateError {
  name = 'UnauthenticatedError'
  constructor(message = 'Unauthenticated') {
    super(message, { http: { status: 401 }, code: 'UNAUTHENTICATED' })
  }
}

/** For use when a resource is not found or not accessible by an authenticated user. */
export class ForbiddenError extends StellateError {
  name = 'ForbiddenError'
  constructor(message = 'Forbidden') {
    super(message, { http: { status: 403 }, code: 'FORBIDDEN' })
  }
}

/** For use when a resource is not found. */
export class NotFoundError extends StellateError {
  name = 'NotFoundError'
  constructor(message = 'Not Found') {
    super(message, { http: { status: 404 }, code: 'NOT_FOUND' })
  }
}

/** For use when any input was invalid or when a resource does not exist but is assumed to exist. */
export class BadRequestError extends StellateError {
  name = 'BadRequestError'
  constructor(message = 'Bad Request') {
    super(message, { http: { status: 400 }, code: 'BAD_REQUEST' })
  }
}

/** For use when any internal billing calls encounter invalid states. */
export class PaymentError extends StellateError {
  name = 'PaymentError'
  // TODO: Replace with more specific error per-service
  constructor(message = 'An invalid Payment Request has been made') {
    super(message, { http: { status: 400 }, code: 'BAD_PAYMENT_REQUEST' })
  }
}

/** For use when any internal call or fetch request to another API fails. */
export class SlackCallError extends StellateError {
  name = 'SlackAPIError'
  // TODO: Replace with more specific error per-service
  constructor(message = 'Internal call to Slack failed') {
    super(message, { http: { status: 500 }, code: 'SLACK_API_ERROR' })
  }
}

export class ValidationError extends StellateError {
  name = 'ValidationError'
  constructor(message: string) {
    super(message, { code: 'GRAPHQL_VALIDATION_FAILED' })
  }
}

export class UserInputError extends StellateError {
  name = 'UserInputError'
  constructor(message: string) {
    super(message, { code: 'BAD_USER_INPUT' })
  }
}

Todo: Figure out what it would look like to abstract this & guide people down the path of success of error handling in GraphQL with Fuse.js.

404 page on docs

Description

From the docs website ( Fuse Introduction ), the line query your Fuse API from the client leads to a 404 page.

The error seems to be in the index.mdx page of the /docs website folder.

RFC: Remix Support

Summary

Official request for Remix support.

Proposed Solution

In theory Next.js API Routes are analogous to Resource Routes and Service Side Props to Loaders so a lot of the concepts already present should translate.

RFC: Support multiple keys on a node

Summary

Many nodes are uniquely identifiable by more than one key. Common example: BlogPost.id and BlogPost.slug.

Proposed Solution

First idea:

const BlogPostNode = node({
  keys: ['id', 'slug'], // Array
  // keys arg is an array of { id?: string, slug?: string }
  // if possible, even better if it's a union { id: string } | { slug: string }
  load: async (keys) => {
    return keys.map(({ id, slug }) => id ? getById(id) : getBySlug(slug) )
  }
})

addNodeFields(User, (t) => ({
  highlightedBlogPost: t.field({
    type: BlogPostNode,
    // Can return either { id: string } | { slug: string }
    resolve: (parent) => ({ slug: parent.highlightedBlogPostSlug })
  )}
}))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.