/

graphql-suite GitHub

Авто-генерація GraphQL CRUD, типобезпечні клієнти та React Query хуки з Drizzle PostgreSQL схем. Повний вивід типів, без кодогенерації.

157/mo 4 typescriptgraphqldrizzlereact-querynpmopen-source
bun add graphql-suite

CRUD-частину GraphQL-API ніхто не хоче писати руками — і все одно всі пишуть. Схема вже знає таблиці, колонки, зв’язки й типи, а ви однаково вручну описуєте резолвери для кожної, потім вручну описуєте типи клієнта під них, а далі вічно тримаєте всі три речі синхронними.

graphql-suite генерує цей шар зі схеми Drizzle ORM і проводить типи аж до React-хуків.

Приклади

Описали схему один раз — віддали як API:

import { buildSchema } from '@graphql-suite/schema'
import { createYoga } from 'graphql-yoga'
import { db } from './db'

const { schema } = buildSchema(db, {
  tables: { exclude: ['session', 'verification'] },
})

const yoga = createYoga({ schema })

Запитуйте з типами, виведеними з тих самих таблиць Drizzle — без кодогенерації й без згенерованих файлів у репозиторії:

import { createDrizzleClient } from '@graphql-suite/client'
import * as schema from './db/schema'

const client = createDrizzleClient({ schema, url: '/api/graphql' })

const users = await client.entity('user').query({
  select: { id: true, name: true, posts: { id: true, title: true } },
  where: { name: { ilike: '%john%' } },
  limit: 10,
})

users типізується за вибіркою, тож якщо прибрати name із select, кожне подальше user.name стане помилкою компіляції.

Навіщо це

Я починав з використання drizzle-graphql, який гарно показує, що ідея працює. Але мені постійно бракувало саме того, що перетворює згенерований API на придатний до продакшену: фільтрації через зв’язки, а не лише за колонками верхнього рівня; хуків, щоб виконати авторизацію до резолвера; count-запитів для пагінації; рідної підтримки JSON/JSONB-колонок; налаштовуваних суфіксів у назвах; зв’язків таблиці на саму себе з окремим лімітом глибини; контролю над тим, які таблиці взагалі потрапляють назовні.

У певний момент обгортка стала більшою за те, що вона обгортає, — і зробилася окремим проєктом.

Чим відрізняється

ПідхідЩо це
graphql-suiteзгенерований CRUD з Drizzle плюс типізований клієнт і React-хуки в одному пакеті
drizzle-graphqlвихідна ідея — генерація схеми з Drizzle, без фільтрації по зв’язках, хуків і дозволів
Hasura, PostGraphileокремий сервіс перед базою, зі своїми метаданими, деплоєм і моделлю дозволів
Резолвери вручнуповний контроль і повна відповідальність за підтримку

Беріть Hasura чи PostGraphile, якщо хочете продукт над базою з консоллю та інтерфейсом дозволів — вони вміють значно більше й експлуатуються як інфраструктура. Пишіть резолвери руками, коли ваш API свідомо не повторює таблиці. Цей інструментарій — для випадку, коли API і є здебільшого ваша схема, і потрібна наскрізна типобезпека без ще одного сервісу.

Що всередині

Три пакети, три набори peer-залежностей

ІмпортПотребує
@graphql-suite/schemadrizzle-orm, graphql
@graphql-suite/clientdrizzle-orm
@graphql-suite/queryreact, @tanstack/react-query

Бекенду не доведеться ставити React, щоб збудувати схему. Кожен пакет — @graphql-suite/schema, @graphql-suite/client і @graphql-suite/query — публікується окремо, а загальний graphql-suite просто залежить від усіх трьох: це зручний спосіб установити їх разом і в узгоджених версіях. Якщо потрібен лише бекенд, ставте сам @graphql-suite/schema — по суті, розширений drizzle-graphql.

Хуки — для логіки, яку генерація не вгадає

const { schema, withPermissions } = buildSchema(db, {
  hooks: {
    user: {
      query: {
        before: async ({ context }) => {
          if (!context.user) throw new Error('Unauthorized')
        },
      },
    },
  },
})

Схеми під ролі

import { permissive, restricted, readOnly } from '@graphql-suite/schema'

const schemas = {
  admin: schema,
  editor: withPermissions(permissive('editor', { audit: false, user: readOnly() })),
  viewer: withPermissions(restricted('viewer', { post: { query: true } })),
}

Ролі перетворюються на окрему схему для кожної, тож недозволене поле відсутнє у схемі, а не відхиляється під час виконання.

React Query хуки

Хуки очікують над собою обидва провайдери — від TanStack Query і від цього пакета, який несе клієнт, зібраний через @graphql-suite/client:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { GraphQLProvider, useEntity, useEntityList } from '@graphql-suite/query'

const queryClient = new QueryClient()

const App = () => (
  <QueryClientProvider client={queryClient}>
    <GraphQLProvider client={graphqlClient}>
      <UserList />
    </GraphQLProvider>
  </QueryClientProvider>
)

const UserList = () => {
  const user = useEntity('user')
  const { data, isLoading } = useEntityList(user, {
    select: { id: true, name: true, email: true },
    limit: 20,
  })
  // рендеримо data…
}

Кешування, пагінація й інвалідація — від TanStack Query; цей шар лише дає типізовані ключі та функції завантаження.

Нотатки про рішення

Виведення типів — головна фіча. Жодного додаткового кроку збірки, жодних згенерованих файлів у рев’ю: клієнт виводить запити, фільтри й типи результатів прямо з модуля схеми Drizzle, тож усе просто працює і лишається рівно настільки типобезпечним, наскільки типобезпечна сама схема — зміна таблиці одразу спливає як помилка типів, а не як застарілий артефакт. Кодогенерація теж є (buildSchemaFromDrizzle()), але лише для єдиного випадку, якому вона справді потрібна: клієнт в окремому репозиторії, який не може імпортувати схему.

Згенерована схема платить за глибину двічі. Глибокі зв’язки і зв’язки таблиці на саму себе роздувають і GraphQL-схему, і типи TypeScript, які мусить обчислювати редактор. Ліміти глибини (limitRelationDepth, окремо limitSelfRelationDepth для самозв’язків), обрізання окремих зв’язків і перемикачі для таблиць зменшують обидва: налаштована схема виходить до 90% меншою, а виведені типи лишаються досить компактними, щоб IDE встигала.

Дозволи дають схеми, а не перевірки. Якщо фільтрувати поле під час виконання, воно все одно видно в інтроспекції. Окрема схема на роль означає, що у схемі глядача справді немає полів, які йому не можна читати.

Один загальний пакет над трьома справжніми. Окремі пакети публікуються самостійно; загальний фіксує узгоджені версії всіх трьох, тож шари, згенеровані з тих самих типів, не можуть розійтися в lock-файлі. graphql-suite — зручний і узгоджений вибір за замовчуванням, окремий пакет — варіант під конкретний сервіс.

Статус

Живий, має власний сайт документації. Підтримуваний діалект — PostgreSQL: генерація спирається на типи Drizzle для Postgres, тож інші діалекти потребували б власного відображення фільтрів, а не просто прапорця.

Деталі

Автор
Dmytro Klymenko — на основі пакету від Drizzle Team
Опубліковано
Annexare на GitHub, NPM
Зроблено на
Drizzle ORM — схема, основа всього, GraphQL Yoga — сервер, TanStack Query — React-хуки
Початок
Оновлено

Інші проєкти