├── .github ├── actions │ └── pnpm │ │ └── action.yml └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .husky └── pre-commit ├── LICENSE.md ├── README.md ├── README.zh-CN.md ├── biome.json ├── docker-compose.yml ├── examples ├── adapters │ ├── package.json │ ├── schema.graphql │ └── src │ │ ├── apollo.ts │ │ ├── contexts │ │ ├── hono.ts │ │ ├── mercurius.ts │ │ └── yoga.ts │ │ ├── hono.ts │ │ ├── mercurius.ts │ │ ├── print.ts │ │ ├── resolvers │ │ └── index.ts │ │ └── yoga.ts ├── cattery-valibot │ ├── .gitignore │ ├── drizzle.config.ts │ ├── package.json │ ├── schema.graphql │ ├── src │ │ ├── contexts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── providers │ │ │ └── index.ts │ │ ├── resolvers │ │ │ ├── cat.ts │ │ │ ├── hello.ts │ │ │ ├── index.ts │ │ │ └── user.ts │ │ └── schema │ │ │ └── index.ts │ └── tsconfig.json ├── cattery-zod │ ├── .gitignore │ ├── drizzle.config.ts │ ├── package.json │ ├── schema.graphql │ ├── src │ │ ├── contexts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── providers │ │ │ └── index.ts │ │ ├── resolvers │ │ │ ├── cat.ts │ │ │ ├── hello.ts │ │ │ ├── index.ts │ │ │ └── user.ts │ │ └── schema │ │ │ └── index.ts │ └── tsconfig.json ├── drizzle │ ├── .gitignore │ ├── drizzle.config.ts │ ├── env.config.ts │ ├── package.json │ ├── schema.graphql │ └── src │ │ ├── index.ts │ │ ├── loader.ts │ │ ├── schema.ts │ │ └── seed.ts ├── middlewares │ ├── package.json │ └── src │ │ ├── context │ │ └── index.ts │ │ ├── guard-valibot.ts │ │ ├── guard-zod.ts │ │ ├── middlewares │ │ ├── auth-guard.ts │ │ ├── cache.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── output-validator.ts │ │ ├── transaction.ts │ │ ├── valibot-exception-filter.ts │ │ └── zod-exception-filter.ts │ │ ├── valibot-posts.ts │ │ ├── valibot.ts │ │ ├── zod-posts.ts │ │ └── zod.ts ├── mikro-orm │ ├── .gitignore │ ├── package.json │ ├── schema.graphql │ └── src │ │ ├── entities.ts │ │ └── index.ts ├── prisma │ ├── .gitignore │ ├── package.json │ ├── prisma │ │ └── schema.prisma │ ├── schema.graphql │ ├── src │ │ ├── index.ts │ │ └── schema.graphql │ └── tsconfig.json └── subscriptions │ ├── package.json │ └── src │ ├── valibot.ts │ └── zod.ts ├── gqloom.svg ├── package.json ├── packages ├── core │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── context │ │ │ ├── async-iterator.spec.ts │ │ │ ├── async-iterator.ts │ │ │ ├── context.spec.ts │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── use-resolving-fields.spec.ts │ │ │ └── use-resolving-fields.ts │ │ ├── index.ts │ │ ├── resolver │ │ │ ├── index.ts │ │ │ ├── input.spec.ts │ │ │ ├── input.ts │ │ │ ├── resolver-chain-factory.ts │ │ │ ├── resolver.spec.ts │ │ │ ├── resolver.ts │ │ │ ├── silk.spec.ts │ │ │ ├── silk.ts │ │ │ ├── types-loom.ts │ │ │ └── types.ts │ │ ├── schema │ │ │ ├── extensions.spec.ts │ │ │ ├── extensions.ts │ │ │ ├── index.ts │ │ │ ├── input.spec.ts │ │ │ ├── input.ts │ │ │ ├── interface.spec.ts │ │ │ ├── interface.ts │ │ │ ├── object.spec.ts │ │ │ ├── object.ts │ │ │ ├── schema-loom.spec.ts │ │ │ ├── schema-loom.ts │ │ │ ├── schema-weaver.spec.ts │ │ │ ├── schema-weaver.ts │ │ │ ├── types.ts │ │ │ ├── weaver-context.spec.ts │ │ │ └── weaver-context.ts │ │ └── utils │ │ │ ├── args.ts │ │ │ ├── constants.ts │ │ │ ├── context.ts │ │ │ ├── error.spec.ts │ │ │ ├── error.ts │ │ │ ├── index.ts │ │ │ ├── loader.spec.ts │ │ │ ├── loader.ts │ │ │ ├── middleware.spec.ts │ │ │ ├── middleware.ts │ │ │ ├── object.spec.ts │ │ │ ├── object.ts │ │ │ ├── parse-resolving-fields.spec.ts │ │ │ ├── parse-resolving-fields.ts │ │ │ ├── string.spec.ts │ │ │ ├── string.ts │ │ │ ├── symbols.ts │ │ │ ├── type.spec.ts │ │ │ ├── type.ts │ │ │ └── types.ts │ ├── test │ │ ├── context.spec.ts │ │ ├── middleware.spec.ts │ │ ├── resolver.spec-d.ts │ │ ├── resolver.spec.ts │ │ └── subscription.spec.ts │ ├── tsconfig.json │ ├── tsup.config.json │ └── vitest.config.ts ├── drizzle │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── drizzle-mysql.config.ts │ ├── drizzle-postgres.config.ts │ ├── drizzle-sqlite-1.config.ts │ ├── drizzle-sqlite.config.ts │ ├── env.config.ts │ ├── package.json │ ├── src │ │ ├── context.ts │ │ ├── factory │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ ├── resolver-mysql.ts │ │ │ ├── resolver-postgres.ts │ │ │ ├── resolver-sqlite.ts │ │ │ ├── resolver.ts │ │ │ └── types.ts │ │ ├── helper.ts │ │ ├── index.ts │ │ └── types.ts │ ├── test │ │ ├── context.spec.ts │ │ ├── helper.spec.ts │ │ ├── input-factory.spec.ts │ │ ├── resolver-factory.spec.ts │ │ ├── resolver-mysql.spec.gql │ │ ├── resolver-mysql.spec.ts │ │ ├── resolver-postgres.spec.gql │ │ ├── resolver-postgres.spec.ts │ │ ├── resolver-sqlite.spec.gql │ │ ├── resolver-sqlite.spec.ts │ │ ├── schema.spec.ts │ │ └── schema │ │ │ ├── mysql.ts │ │ │ ├── postgres.ts │ │ │ └── sqlite.ts │ ├── tsconfig.json │ ├── tsup.config.json │ └── vitest.config.ts ├── federation │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── mock-ast.ts │ │ ├── resolver.ts │ │ └── schema-loom.ts │ ├── test │ │ ├── adapters-custom-schema.spec.ts │ │ ├── adapters.spec.ts │ │ ├── resolver.spec.ts │ │ └── schema.spec.ts │ ├── tsconfig.json │ ├── tsup.config.json │ └── vitest.config.ts ├── mikro-orm │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── entity-schema.ts │ │ ├── index.ts │ │ ├── resolver-factory.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── test │ │ ├── entity-schema.spec-d.ts │ │ ├── entity-schema.spec.ts │ │ ├── resolver-factory.spec.ts │ │ └── schema.spec.ts │ ├── tsconfig.json │ ├── tsup.config.json │ └── vitest.config.ts ├── prisma │ ├── .gitignore │ ├── .graphqlrc.yml │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ └── generator.cjs │ ├── package.json │ ├── prisma │ │ └── schema.prisma │ ├── src │ │ ├── context.ts │ │ ├── generator │ │ │ ├── index.ts │ │ │ ├── js.ts │ │ │ └── ts.ts │ │ ├── index.ts │ │ ├── resolver-factory.ts │ │ ├── type-weaver.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── test │ │ ├── generator.spec.ts │ │ ├── resolver-factory.spec.ts │ │ ├── resolver.spec.gql │ │ ├── resolver.spec.ts │ │ ├── schema.spec.ts │ │ ├── type-weaver.spec.ts │ │ └── utils.spec.ts │ ├── tsconfig.json │ ├── tsup.config.json │ └── vitest.config.ts ├── valibot │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── metadata.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── test │ │ ├── metadata.spec.ts │ │ ├── resolver.spec.ts │ │ ├── schema.spec.ts │ │ └── utils.spec.ts │ ├── tsconfig.json │ ├── tsup.config.json │ └── vitest.config.ts ├── yup │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── types.ts │ │ └── union.ts │ ├── test │ │ ├── resolver.spec.ts │ │ ├── schema.spec.ts │ │ └── union.spec.ts │ ├── tsconfig.json │ ├── tsup.config.json │ └── vitest.config.ts └── zod │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── index.ts │ ├── metadata.ts │ ├── types.ts │ ├── utils.ts │ └── v3 │ │ ├── index.ts │ │ ├── metadata.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── test │ ├── resolver.spec.ts │ ├── schema.spec.ts │ └── v3 │ │ ├── resolver.spec.ts │ │ ├── schema.spec.ts │ │ └── utils.spec.ts │ ├── tsconfig.json │ ├── tsup.config.json │ └── vitest.config.ts ├── patches └── graphql@16.8.1.patch ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.json ├── tsconfig.tsup.json ├── vitest.config.ts ├── vitest.workspace.ts └── website ├── .gitignore ├── README.md ├── app ├── [lang] │ ├── (home) │ │ ├── layout.tsx │ │ ├── orm-library.tsx │ │ ├── page.tsx │ │ └── utils.tsx │ ├── docs │ │ ├── [[...slug]] │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── layout.tsx │ └── llms.txt │ │ └── route.ts ├── api │ └── search │ │ └── route.ts ├── global.css ├── layout.config.tsx ├── layout.tsx └── not-found.tsx ├── components ├── doc-text │ └── index.tsx ├── flowing-lines │ ├── index.module.css │ └── index.tsx ├── gqloom-logo │ └── index.tsx └── runtime-types │ └── index.tsx ├── content ├── docs │ ├── advanced │ │ ├── adapters │ │ │ ├── apollo.md │ │ │ ├── apollo.zh.md │ │ │ ├── elysia.md │ │ │ ├── elysia.zh.md │ │ │ ├── hono.md │ │ │ ├── hono.zh.md │ │ │ ├── index.mdx │ │ │ ├── index.zh.mdx │ │ │ ├── mercurius.md │ │ │ ├── mercurius.zh.md │ │ │ ├── yoga.md │ │ │ └── yoga.zh.md │ │ ├── executor.md │ │ ├── executor.zh.md │ │ ├── federation.mdx │ │ ├── federation.zh.mdx │ │ ├── meta.json │ │ ├── printing-schema.md │ │ ├── printing-schema.zh.md │ │ ├── subscription.mdx │ │ └── subscription.zh.mdx │ ├── context.mdx │ ├── context.zh.mdx │ ├── dataloader.mdx │ ├── dataloader.zh.mdx │ ├── getting-started.mdx │ ├── getting-started.zh.mdx │ ├── index.mdx │ ├── index.zh.mdx │ ├── meta.json │ ├── meta.zh.json │ ├── middleware.mdx │ ├── middleware.zh.mdx │ ├── resolver.mdx │ ├── resolver.zh.mdx │ ├── schema │ │ ├── drizzle.mdx │ │ ├── drizzle.zh.mdx │ │ ├── meta.json │ │ ├── mikro-orm.mdx │ │ ├── mikro-orm.zh.mdx │ │ ├── prisma.md │ │ ├── prisma.zh.md │ │ ├── valibot.md │ │ ├── valibot.zh.md │ │ ├── yup.md │ │ ├── yup.zh.md │ │ ├── zod.mdx │ │ └── zod.zh.mdx │ ├── silk.mdx │ ├── silk.zh.mdx │ ├── weave.mdx │ └── weave.zh.mdx └── home │ ├── drizzle.md │ ├── mikro-orm.md │ ├── prisma.md │ ├── schema-graphql.md │ └── schema-libraries.md ├── lib ├── i18n.ts └── source.ts ├── middleware.ts ├── next.config.mjs ├── package.json ├── postcss.config.mjs ├── public └── gqloom.svg ├── source.config.ts └── tsconfig.json /.github/actions/pnpm/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Sets up Node.js and pnpm 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup pnpm 8 | uses: pnpm/action-setup@v4 9 | with: 10 | run_install: false 11 | 12 | - name: Setup Node.js 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: 20 16 | registry-url: "https://registry.npmjs.org" 17 | cache: pnpm 18 | 19 | - name: Install dependencies 20 | shell: bash 21 | run: pnpm install 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Test and Publish 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | test: 10 | name: Run default CI of repository 11 | uses: ./.github/workflows/test.yml 12 | 13 | publish: 14 | name: Publish to NPM 15 | runs-on: ubuntu-latest 16 | needs: 17 | - test 18 | permissions: 19 | contents: read 20 | packages: write 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - name: Setup pnpm 25 | uses: ./.github/actions/pnpm 26 | - name: Build 27 | run: pnpm run build 28 | - name: Publish 29 | run: pnpm run ci:publish 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | workflow_call: 7 | 8 | jobs: 9 | install: 10 | name: Install packages 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - name: Setup pnpm 16 | uses: ./.github/actions/pnpm 17 | 18 | lint: 19 | name: Run lint 20 | needs: install 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | - name: Setup pnpm 26 | uses: ./.github/actions/pnpm 27 | - name: Lint check 28 | run: pnpm lint 29 | 30 | type-check: 31 | name: Run type check 32 | needs: install 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v4 37 | - name: Setup pnpm 38 | uses: ./.github/actions/pnpm 39 | - name: Build 40 | run: pnpm run build 41 | - name: Generate 42 | run: pnpm -r run generate 43 | - name: Type check 44 | run: pnpm run check:type 45 | 46 | test: 47 | name: Run Uint test 48 | needs: install 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v4 53 | - name: Init docker 54 | run: | 55 | docker compose -f docker-compose.yml up -d 56 | - name: Setup pnpm 57 | uses: ./.github/actions/pnpm 58 | - name: Build 59 | run: pnpm run build 60 | - name: Init Tables 61 | run: pnpm push 62 | - name: Test 63 | run: pnpm test 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Production 5 | dist 6 | build 7 | server 8 | release 9 | .vercel 10 | 11 | doc_build 12 | 13 | # Tests 14 | coverage 15 | *.vitest-temp.json 16 | 17 | # Backups 18 | backups 19 | 20 | # Services 21 | .vscode 22 | .netlify 23 | 24 | # Others 25 | logs 26 | draft 27 | *.log 28 | .cache 29 | temp 30 | tmp 31 | 32 | # Local env files 33 | .env*.local 34 | 35 | # IDEs and editors 36 | .idea 37 | .project 38 | .classpath 39 | *.launch 40 | .settings 41 | 42 | # System files 43 | .DS_Store 44 | Thumbs.db 45 | *.pem -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run test --watch=false 2 | pnpm exec lint-staged 3 | pnpm run check 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Modevol https://www.modevol.com/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | mysql: 5 | image: mysql:9.1 6 | container_name: gqloom_mysql 7 | restart: unless-stopped 8 | environment: 9 | MYSQL_ALLOW_EMPTY_PASSWORD: 1 10 | ports: 11 | - "3306:3306" 12 | volumes: 13 | - mysql_data:/var/lib/mysql 14 | 15 | postgres: 16 | image: postgres:17.2 17 | container_name: gqloom_postgres 18 | restart: unless-stopped 19 | environment: 20 | POSTGRES_HOST_AUTH_METHOD: trust 21 | ports: 22 | - "5432:5432" 23 | volumes: 24 | - postgres_data:/var/lib/postgresql/data 25 | 26 | volumes: 27 | mysql_data: 28 | postgres_data: -------------------------------------------------------------------------------- /examples/adapters/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adapters", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@apollo/server": "^4.10.4", 14 | "@gqloom/core": "^0.2.0", 15 | "@hono/graphql-server": "^0.5.1", 16 | "@hono/node-server": "^1.13.8", 17 | "fastify": "^4.28.1", 18 | "graphql": "^16.8.1", 19 | "graphql-yoga": "^5.6.0", 20 | "hono": "^4.7.2", 21 | "mercurius": "^14.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/adapters/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | hello: String! 3 | } 4 | -------------------------------------------------------------------------------- /examples/adapters/src/apollo.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from "@apollo/server" 2 | import { startStandaloneServer } from "@apollo/server/standalone" 3 | import { weave } from "@gqloom/core" 4 | import { helloResolver } from "./resolvers" 5 | 6 | const schema = weave(helloResolver) 7 | const server = new ApolloServer({ schema }) 8 | 9 | startStandaloneServer(server, { 10 | listen: { port: 4000 }, 11 | }).then(({ url }) => { 12 | console.info(`🚀 Server ready at: ${url}`) 13 | }) 14 | -------------------------------------------------------------------------------- /examples/adapters/src/contexts/hono.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "@gqloom/core/context" 2 | import type { Context } from "hono" 3 | 4 | export function useAuthorization() { 5 | return useContext().req.header().authorization 6 | } 7 | -------------------------------------------------------------------------------- /examples/adapters/src/contexts/mercurius.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "@gqloom/core/context" 2 | import type { MercuriusContext } from "mercurius" 3 | 4 | export function useAuthorization() { 5 | return useContext().reply.request.headers.authorization 6 | } 7 | -------------------------------------------------------------------------------- /examples/adapters/src/contexts/yoga.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "@gqloom/core/context" 2 | import type { YogaInitialContext } from "graphql-yoga" 3 | 4 | export function useAuthorization() { 5 | return useContext().request.headers.get("Authorization") 6 | } 7 | -------------------------------------------------------------------------------- /examples/adapters/src/hono.ts: -------------------------------------------------------------------------------- 1 | import { weave } from "@gqloom/core" 2 | import { graphqlServer } from "@hono/graphql-server" 3 | import { serve } from "@hono/node-server" 4 | import { Hono } from "hono" 5 | import { helloResolver } from "./resolvers" 6 | 7 | export const app = new Hono() 8 | 9 | const schema = weave(helloResolver) 10 | 11 | app.use("/graphql", graphqlServer({ schema, graphiql: true })) 12 | 13 | serve(app, (info) => { 14 | console.info( 15 | `GraphQL server is running on http://localhost:${info.port}/graphql` 16 | ) 17 | }) 18 | -------------------------------------------------------------------------------- /examples/adapters/src/mercurius.ts: -------------------------------------------------------------------------------- 1 | import { weave } from "@gqloom/core" 2 | import Fastify from "fastify" 3 | import mercurius from "mercurius" 4 | import { helloResolver } from "./resolvers" 5 | 6 | const schema = weave(helloResolver) 7 | 8 | const app = Fastify() 9 | app.register(mercurius, { schema }) 10 | app.listen({ port: 4000 }, () => { 11 | console.info("Mercurius server is running on http://localhost:4000") 12 | }) 13 | -------------------------------------------------------------------------------- /examples/adapters/src/print.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs" 2 | import { weave } from "@gqloom/core" 3 | import { lexicographicSortSchema, printSchema } from "graphql" 4 | import { helloResolver } from "./resolvers" 5 | 6 | const schema = weave(helloResolver) 7 | 8 | const schemaText = printSchema(lexicographicSortSchema(schema)) 9 | 10 | if (process.env.NODE_ENV === "development") { 11 | fs.writeFileSync("schema.graphql", schemaText) 12 | } 13 | -------------------------------------------------------------------------------- /examples/adapters/src/resolvers/index.ts: -------------------------------------------------------------------------------- 1 | import { query, resolver, silk } from "@gqloom/core" 2 | import { GraphQLNonNull, GraphQLString } from "graphql" 3 | 4 | export const helloResolver = resolver({ 5 | hello: query( 6 | silk(new GraphQLNonNull(GraphQLString)), 7 | () => "Hello, World" 8 | ), 9 | }) 10 | -------------------------------------------------------------------------------- /examples/adapters/src/yoga.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { weave } from "@gqloom/core" 3 | import { createYoga } from "graphql-yoga" 4 | import { helloResolver } from "./resolvers" 5 | 6 | const schema = weave(helloResolver) 7 | 8 | const yoga = createYoga({ schema }) 9 | 10 | createServer(yoga).listen(4000, () => { 11 | console.info("Server is running on http://localhost:4000/graphql") 12 | }) 13 | -------------------------------------------------------------------------------- /examples/cattery-valibot/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.db -------------------------------------------------------------------------------- /examples/cattery-valibot/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { defineConfig } from "drizzle-kit" 3 | 4 | export default defineConfig({ 5 | out: "./drizzle", 6 | schema: "./src/schema/index.ts", 7 | dialect: "sqlite", 8 | dbCredentials: { 9 | url: process.env.DB_FILE_NAME ?? "file:local.db", 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /examples/cattery-valibot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cattery-valibot", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "tsx watch src/index.ts" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "devDependencies": { 13 | "@types/node": "^22.13.4", 14 | "drizzle-kit": "^0.30.1", 15 | "tsx": "^4.7.2", 16 | "typescript": "^5.7.3" 17 | }, 18 | "dependencies": { 19 | "@gqloom/core": "latest", 20 | "@gqloom/drizzle": "latest", 21 | "@gqloom/valibot": "latest", 22 | "@libsql/client": "^0.14.0", 23 | "dotenv": "^16.4.7", 24 | "drizzle-orm": "^0.39.3", 25 | "graphql": "^16.8.1", 26 | "graphql-yoga": "^5.6.0", 27 | "valibot": "1.0.0-rc.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/cattery-valibot/src/contexts/index.ts: -------------------------------------------------------------------------------- 1 | import { createMemoization, useContext } from "@gqloom/core/context" 2 | import { eq } from "drizzle-orm" 3 | import { GraphQLError } from "graphql" 4 | import type { YogaInitialContext } from "graphql-yoga" 5 | import { db } from "../providers" 6 | import { users } from "../schema" 7 | 8 | export const useCurrentUser = createMemoization(async () => { 9 | const phone = 10 | useContext().request.headers.get("authorization") 11 | if (phone == null) throw new GraphQLError("Unauthorized") 12 | 13 | const user = await db.query.users.findFirst({ where: eq(users.phone, phone) }) 14 | if (user == null) throw new GraphQLError("Unauthorized") 15 | return user 16 | }) 17 | -------------------------------------------------------------------------------- /examples/cattery-valibot/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { weave } from "@gqloom/core" 3 | import { ValibotWeaver } from "@gqloom/valibot" 4 | import { createYoga } from "graphql-yoga" 5 | import { resolvers } from "./resolvers" 6 | 7 | const schema = weave(asyncContextProvider, ValibotWeaver, ...resolvers) 8 | 9 | const yoga = createYoga({ schema }) 10 | createServer(yoga).listen(4000, () => { 11 | console.info("Server is running on http://localhost:4000/graphql") 12 | }) 13 | 14 | import * as fs from "fs" 15 | import * as path from "path" 16 | import { asyncContextProvider } from "@gqloom/core/context" 17 | import { printSchema } from "graphql" 18 | if (process.env.NODE_ENV !== "production") { 19 | fs.writeFileSync( 20 | path.resolve(__dirname, "../schema.graphql"), 21 | printSchema(schema) 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /examples/cattery-valibot/src/providers/index.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from "drizzle-orm/libsql" 2 | import * as schema from "../schema" 3 | 4 | export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { 5 | schema, 6 | }) 7 | -------------------------------------------------------------------------------- /examples/cattery-valibot/src/resolvers/cat.ts: -------------------------------------------------------------------------------- 1 | import { field, resolver } from "@gqloom/core" 2 | import { drizzleResolverFactory } from "@gqloom/drizzle" 3 | import * as v from "valibot" 4 | import { useCurrentUser } from "../contexts" 5 | import { db } from "../providers" 6 | import { cats } from "../schema" 7 | 8 | const catResolverFactory = drizzleResolverFactory(db, "cats") 9 | 10 | export const catResolver = resolver.of(cats, { 11 | cats: catResolverFactory.selectArrayQuery(), 12 | 13 | age: field(v.pipe(v.number())) 14 | .derivedFrom("birthday") 15 | .input({ 16 | currentYear: v.nullish(v.pipe(v.number(), v.integer()), () => 17 | new Date().getFullYear() 18 | ), 19 | }) 20 | .resolve((cat, { currentYear }) => { 21 | return currentYear - cat.birthday.getFullYear() 22 | }), 23 | 24 | owner: catResolverFactory.relationField("owner"), 25 | 26 | createCats: catResolverFactory.insertArrayMutation({ 27 | input: v.pipeAsync( 28 | v.objectAsync({ 29 | values: v.arrayAsync( 30 | v.pipeAsync( 31 | v.object({ 32 | name: v.string(), 33 | birthday: v.pipe( 34 | v.string(), 35 | v.transform((x) => new Date(x)) 36 | ), 37 | }), 38 | v.transformAsync(async ({ name, birthday }) => ({ 39 | name, 40 | birthday, 41 | ownerId: (await useCurrentUser()).id, 42 | })) 43 | ) 44 | ), 45 | }) 46 | ), 47 | }), 48 | }) 49 | -------------------------------------------------------------------------------- /examples/cattery-valibot/src/resolvers/hello.ts: -------------------------------------------------------------------------------- 1 | import { query, resolver } from "@gqloom/core" 2 | import * as v from "valibot" 3 | 4 | export const helloResolver = resolver({ 5 | hello: query(v.string()) 6 | .input({ name: v.nullish(v.string(), "World") }) 7 | .resolve(({ name }) => `Hello ${name}!`), 8 | }) 9 | -------------------------------------------------------------------------------- /examples/cattery-valibot/src/resolvers/index.ts: -------------------------------------------------------------------------------- 1 | import { catResolver } from "./cat" 2 | import { helloResolver } from "./hello" 3 | import { userResolver } from "./user" 4 | 5 | export const resolvers = [helloResolver, userResolver, catResolver] 6 | -------------------------------------------------------------------------------- /examples/cattery-valibot/src/resolvers/user.ts: -------------------------------------------------------------------------------- 1 | import { mutation, query, resolver } from "@gqloom/core" 2 | import { drizzleResolverFactory } from "@gqloom/drizzle" 3 | import { eq } from "drizzle-orm" 4 | import * as v from "valibot" 5 | import { useCurrentUser } from "../contexts" 6 | import { db } from "../providers" 7 | import { users } from "../schema" 8 | 9 | const userResolverFactory = drizzleResolverFactory(db, "users") 10 | 11 | export const userResolver = resolver.of(users, { 12 | cats: userResolverFactory.relationField("cats"), 13 | 14 | mine: query(users).resolve(() => useCurrentUser()), 15 | 16 | usersByName: query(users.$list()) 17 | .input({ name: v.string() }) 18 | .resolve(({ name }) => { 19 | return db.query.users.findMany({ 20 | where: eq(users.name, name), 21 | }) 22 | }), 23 | 24 | userByPhone: query(users.$nullable()) 25 | .input({ phone: v.string() }) 26 | .resolve(({ phone }) => { 27 | return db.query.users.findFirst({ 28 | where: eq(users.phone, phone), 29 | }) 30 | }), 31 | 32 | createUser: mutation(users) 33 | .input({ 34 | data: v.object({ 35 | name: v.string(), 36 | phone: v.string(), 37 | }), 38 | }) 39 | .resolve(async ({ data }) => { 40 | const [user] = await db.insert(users).values(data).returning() 41 | return user 42 | }), 43 | }) 44 | -------------------------------------------------------------------------------- /examples/cattery-valibot/src/schema/index.ts: -------------------------------------------------------------------------------- 1 | import { drizzleSilk } from "@gqloom/drizzle" 2 | import { relations } from "drizzle-orm" 3 | import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" 4 | 5 | export const users = drizzleSilk( 6 | sqliteTable("users", { 7 | id: integer().primaryKey({ autoIncrement: true }), 8 | name: text().notNull(), 9 | phone: text().notNull().unique(), 10 | }) 11 | ) 12 | 13 | export const usersRelations = relations(users, ({ many }) => ({ 14 | cats: many(cats), 15 | })) 16 | 17 | export const cats = drizzleSilk( 18 | sqliteTable("cats", { 19 | id: integer().primaryKey({ autoIncrement: true }), 20 | name: text().notNull(), 21 | birthday: integer({ mode: "timestamp" }).notNull(), 22 | ownerId: integer() 23 | .notNull() 24 | .references(() => users.id), 25 | }) 26 | ) 27 | 28 | export const catsRelations = relations(cats, ({ one }) => ({ 29 | owner: one(users, { 30 | fields: [cats.ownerId], 31 | references: [users.id], 32 | }), 33 | })) 34 | -------------------------------------------------------------------------------- /examples/cattery-zod/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.db -------------------------------------------------------------------------------- /examples/cattery-zod/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { defineConfig } from "drizzle-kit" 3 | 4 | export default defineConfig({ 5 | out: "./drizzle", 6 | schema: "./src/schema/index.ts", 7 | dialect: "sqlite", 8 | dbCredentials: { 9 | url: process.env.DB_FILE_NAME ?? "file:local.db", 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /examples/cattery-zod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cattery-zod", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "tsx watch src/index.ts" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "devDependencies": { 13 | "@types/node": "^22.13.4", 14 | "drizzle-kit": "^0.30.1", 15 | "tsx": "^4.7.2", 16 | "typescript": "^5.7.3" 17 | }, 18 | "dependencies": { 19 | "@gqloom/core": "latest", 20 | "@gqloom/drizzle": "latest", 21 | "@gqloom/zod": "latest", 22 | "@libsql/client": "^0.14.0", 23 | "dotenv": "^16.4.7", 24 | "drizzle-orm": "^0.39.3", 25 | "graphql": "^16.8.1", 26 | "graphql-yoga": "^5.6.0", 27 | "zod": "4.0.0-beta.20250415T232143" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/cattery-zod/src/contexts/index.ts: -------------------------------------------------------------------------------- 1 | import { createMemoization, useContext } from "@gqloom/core/context" 2 | import { eq } from "drizzle-orm" 3 | import { GraphQLError } from "graphql" 4 | import type { YogaInitialContext } from "graphql-yoga" 5 | import { db } from "../providers" 6 | import { users } from "../schema" 7 | 8 | export const useCurrentUser = createMemoization(async () => { 9 | const phone = 10 | useContext().request.headers.get("authorization") 11 | if (phone == null) throw new GraphQLError("Unauthorized") 12 | 13 | const user = await db.query.users.findFirst({ 14 | where: eq(users.phone, phone), 15 | }) 16 | if (user == null) throw new GraphQLError("Unauthorized") 17 | return user 18 | }) 19 | -------------------------------------------------------------------------------- /examples/cattery-zod/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { weave } from "@gqloom/core" 3 | import { asyncContextProvider } from "@gqloom/core/context" 4 | import { ZodWeaver } from "@gqloom/zod" 5 | import { createYoga } from "graphql-yoga" 6 | import { resolvers } from "./resolvers" 7 | 8 | const schema = weave(asyncContextProvider, ZodWeaver, ...resolvers) 9 | 10 | const yoga = createYoga({ schema }) 11 | createServer(yoga).listen(4000, () => { 12 | console.info("Server is running on http://localhost:4000/graphql") 13 | }) 14 | 15 | import * as fs from "fs" 16 | import * as path from "path" 17 | import { printSchema } from "graphql" 18 | if (process.env.NODE_ENV !== "production") { 19 | fs.writeFileSync( 20 | path.resolve(__dirname, "../schema.graphql"), 21 | printSchema(schema) 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /examples/cattery-zod/src/providers/index.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from "drizzle-orm/libsql" 2 | import * as schema from "../schema" 3 | 4 | export const db = drizzle(process.env.DB_FILE_NAME ?? "file:local.db", { 5 | schema, 6 | }) 7 | -------------------------------------------------------------------------------- /examples/cattery-zod/src/resolvers/cat.ts: -------------------------------------------------------------------------------- 1 | import { field, resolver } from "@gqloom/core" 2 | import { drizzleResolverFactory } from "@gqloom/drizzle" 3 | import * as z from "zod" 4 | import { useCurrentUser } from "../contexts" 5 | import { db } from "../providers" 6 | import { cats } from "../schema" 7 | 8 | const catResolverFactory = drizzleResolverFactory(db, "cats") 9 | 10 | export const catResolver = resolver.of(cats, { 11 | cats: catResolverFactory.selectArrayQuery(), 12 | 13 | age: field(z.int()) 14 | .derivedFrom("birthday") 15 | .input({ 16 | currentYear: z 17 | .int() 18 | .nullish() 19 | .transform((x) => x ?? new Date().getFullYear()), 20 | }) 21 | .resolve((cat, { currentYear }) => { 22 | return currentYear - cat.birthday.getFullYear() 23 | }), 24 | 25 | owner: catResolverFactory.relationField("owner"), 26 | 27 | createCats: catResolverFactory.insertArrayMutation({ 28 | input: z.object({ 29 | values: z 30 | .object({ 31 | name: z.string(), 32 | birthday: z.coerce.date(), 33 | }) 34 | .transform(async ({ name, birthday }) => ({ 35 | name, 36 | birthday, 37 | ownerId: (await useCurrentUser()).id, 38 | })) 39 | .array(), 40 | }), 41 | }), 42 | }) 43 | -------------------------------------------------------------------------------- /examples/cattery-zod/src/resolvers/hello.ts: -------------------------------------------------------------------------------- 1 | import { query } from "@gqloom/core" 2 | import { resolver } from "@gqloom/core" 3 | import { z } from "zod" 4 | 5 | export const helloResolver = resolver({ 6 | hello: query(z.string()) 7 | .input({ 8 | name: z 9 | .string() 10 | .nullish() 11 | .transform((x) => x ?? "World"), 12 | }) 13 | .resolve(({ name }) => `Hello ${name}!`), 14 | }) 15 | -------------------------------------------------------------------------------- /examples/cattery-zod/src/resolvers/index.ts: -------------------------------------------------------------------------------- 1 | import { catResolver } from "./cat" 2 | import { helloResolver } from "./hello" 3 | import { userResolver } from "./user" 4 | 5 | export const resolvers = [helloResolver, userResolver, catResolver] 6 | -------------------------------------------------------------------------------- /examples/cattery-zod/src/resolvers/user.ts: -------------------------------------------------------------------------------- 1 | import { mutation, query, resolver } from "@gqloom/core" 2 | import { drizzleResolverFactory } from "@gqloom/drizzle" 3 | import { eq } from "drizzle-orm" 4 | import * as z from "zod" 5 | import { useCurrentUser } from "../contexts" 6 | import { db } from "../providers" 7 | import { users } from "../schema" 8 | 9 | const userResolverFactory = drizzleResolverFactory(db, "users") 10 | 11 | export const userResolver = resolver.of(users, { 12 | cats: userResolverFactory.relationField("cats"), 13 | 14 | mine: query(users).resolve(() => useCurrentUser()), 15 | 16 | usersByName: query(users.$list()) 17 | .input({ name: z.string() }) 18 | .resolve(({ name }) => { 19 | return db.query.users.findMany({ 20 | where: eq(users.name, name), 21 | }) 22 | }), 23 | 24 | userByPhone: query(users.$nullable()) 25 | .input({ phone: z.string() }) 26 | .resolve(({ phone }) => { 27 | return db.query.users.findFirst({ 28 | where: eq(users.phone, phone), 29 | }) 30 | }), 31 | 32 | createUser: mutation(users) 33 | .input({ 34 | data: z.object({ 35 | name: z.string(), 36 | phone: z.string(), 37 | }), 38 | }) 39 | .resolve(async ({ data }) => { 40 | const [user] = await db.insert(users).values(data).returning() 41 | return user 42 | }), 43 | }) 44 | -------------------------------------------------------------------------------- /examples/cattery-zod/src/schema/index.ts: -------------------------------------------------------------------------------- 1 | import { drizzleSilk } from "@gqloom/drizzle" 2 | import { relations } from "drizzle-orm" 3 | import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core" 4 | 5 | export const users = drizzleSilk( 6 | sqliteTable("users", { 7 | id: integer().primaryKey({ autoIncrement: true }), 8 | name: text().notNull(), 9 | phone: text().notNull().unique(), 10 | }) 11 | ) 12 | 13 | export const usersRelations = relations(users, ({ many }) => ({ 14 | cats: many(cats), 15 | })) 16 | 17 | export const cats = drizzleSilk( 18 | sqliteTable("cats", { 19 | id: integer().primaryKey({ autoIncrement: true }), 20 | name: text().notNull(), 21 | birthday: integer({ mode: "timestamp" }).notNull(), 22 | ownerId: integer() 23 | .notNull() 24 | .references(() => users.id), 25 | }) 26 | ) 27 | 28 | export const catsRelations = relations(cats, ({ one }) => ({ 29 | owner: one(users, { 30 | fields: [cats.ownerId], 31 | references: [users.id], 32 | }), 33 | })) 34 | -------------------------------------------------------------------------------- /examples/drizzle/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /examples/drizzle/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit" 2 | import { config } from "./env.config" 3 | 4 | export default defineConfig({ 5 | out: "./drizzle", 6 | schema: "./src/schema.ts", 7 | dialect: "postgresql", 8 | dbCredentials: { 9 | url: config.databaseUrl, 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /examples/drizzle/env.config.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | 3 | export const config = { 4 | databaseUrl: 5 | process.env.DATABASE_URL ?? "postgres://postgres@localhost:5432/postgres", 6 | } 7 | -------------------------------------------------------------------------------- /examples/drizzle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-drizzle", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "tsx watch --env-file .env src/index.ts", 8 | "dev:loader": "tsx watch --env-file .env src/loader.ts", 9 | "push": "drizzle-kit push", 10 | "seed": "tsx src/seed.ts" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@types/node": "^22.13.1", 17 | "@types/pg": "^8.11.10", 18 | "drizzle-kit": "^0.30.1", 19 | "drizzle-orm": "^0.39.3", 20 | "tsx": "^4.7.2", 21 | "typescript": "^5.7.3" 22 | }, 23 | "dependencies": { 24 | "@gqloom/core": "workspace:*", 25 | "@gqloom/drizzle": "workspace:*", 26 | "dotenv": "^16.4.7", 27 | "drizzle-seed": "^0.3.1", 28 | "graphql": "^16.8.1", 29 | "graphql-yoga": "^5.6.0", 30 | "pg": "^8.13.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/drizzle/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "node:fs" 2 | import { createServer } from "node:http" 3 | import * as path from "node:path" 4 | import { weave } from "@gqloom/core" 5 | import { drizzleResolverFactory } from "@gqloom/drizzle" 6 | import { drizzle } from "drizzle-orm/node-postgres" 7 | import { printSchema } from "graphql" 8 | import { createYoga } from "graphql-yoga" 9 | import { config } from "../env.config" 10 | import * as tables from "./schema" 11 | 12 | const db = drizzle(config.databaseUrl, { schema: tables }) 13 | 14 | const userResolver = drizzleResolverFactory(db, "users").resolver() 15 | 16 | const postResolver = drizzleResolverFactory(db, "posts").resolver() 17 | 18 | const schema = weave(userResolver, postResolver) 19 | 20 | fs.writeFileSync(path.join(__dirname, "../schema.graphql"), printSchema(schema)) 21 | 22 | const yoga = createYoga({ schema }) 23 | const server = createServer(yoga) 24 | server.listen(4000, () => { 25 | console.info("Server is running on http://localhost:4000/graphql") 26 | }) 27 | -------------------------------------------------------------------------------- /examples/drizzle/src/loader.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { field, query, resolver, weave } from "@gqloom/core" 3 | import { eq } from "drizzle-orm" 4 | import { drizzle } from "drizzle-orm/node-postgres" 5 | import { createYoga } from "graphql-yoga" 6 | import { config } from "../env.config" 7 | import * as tables from "./schema" 8 | import { posts, users } from "./schema" 9 | 10 | const db = drizzle(config.databaseUrl, { schema: tables, logger: true }) 11 | 12 | const userResolver = resolver.of(users, { 13 | users: query(users.$list()).resolve(() => db.select().from(users)), 14 | 15 | posts0: field(posts.$list()) 16 | .derivedFrom("id") 17 | .resolve((user) => 18 | db.select().from(posts).where(eq(posts.authorId, user.id)) 19 | ), 20 | 21 | // posts: field(posts.$list()).load(async (userList) => { 22 | // const postList = await db 23 | // .select() 24 | // .from(posts) 25 | // .where( 26 | // inArray( 27 | // posts.authorId, 28 | // userList.map((u) => u.id) 29 | // ) 30 | // ) 31 | // const postMap = Map.groupBy(postList, (p) => p.authorId) 32 | // return userList.map((u) => postMap.get(u.id) ?? []) 33 | // }), 34 | }) 35 | 36 | const schema = weave(userResolver) 37 | 38 | const yoga = createYoga({ schema }) 39 | const server = createServer(yoga) 40 | server.listen(4000, () => { 41 | console.info("Server is running on http://localhost:4000/graphql") 42 | }) 43 | -------------------------------------------------------------------------------- /examples/drizzle/src/schema.ts: -------------------------------------------------------------------------------- 1 | import { drizzleSilk } from "@gqloom/drizzle" 2 | import { relations } from "drizzle-orm" 3 | import * as t from "drizzle-orm/pg-core" 4 | 5 | export const roleEnum = t.pgEnum("role", ["user", "admin"]) 6 | 7 | export const users = drizzleSilk( 8 | t.pgTable("users", { 9 | id: t.serial().primaryKey(), 10 | createdAt: t.timestamp().defaultNow(), 11 | email: t.text().unique().notNull(), 12 | name: t.text(), 13 | role: roleEnum().default("user"), 14 | }) 15 | ) 16 | 17 | export const usersRelations = relations(users, ({ many }) => ({ 18 | posts: many(posts), 19 | })) 20 | 21 | export const posts = drizzleSilk( 22 | t.pgTable("posts", { 23 | id: t.serial().primaryKey(), 24 | createdAt: t.timestamp().defaultNow(), 25 | updatedAt: t 26 | .timestamp() 27 | .defaultNow() 28 | .$onUpdateFn(() => new Date()), 29 | published: t.boolean().default(false), 30 | title: t.varchar({ length: 255 }).notNull(), 31 | authorId: t.integer().notNull(), 32 | }) 33 | ) 34 | 35 | export const postsRelations = relations(posts, ({ one }) => ({ 36 | author: one(users, { fields: [posts.authorId], references: [users.id] }), 37 | })) 38 | -------------------------------------------------------------------------------- /examples/drizzle/src/seed.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from "drizzle-orm/node-postgres" 2 | import { reset, seed } from "drizzle-seed" 3 | import { config } from "../env.config" 4 | import * as schema from "./schema" 5 | 6 | async function main() { 7 | const db = drizzle(config.databaseUrl, { logger: true }) 8 | await reset(db, schema) 9 | await seed(db, schema).refine(() => ({ 10 | users: { 11 | count: 20, 12 | with: { 13 | posts: [ 14 | { weight: 0.6, count: [1, 2, 3] }, 15 | { weight: 0.3, count: [5, 6, 7] }, 16 | { weight: 0.1, count: [8, 9, 10] }, 17 | ], 18 | }, 19 | }, 20 | })) 21 | } 22 | 23 | main().catch((err) => { 24 | // biome-ignore lint/suspicious/noConsole: 25 | console.error(err) 26 | process.exit(1) 27 | }) 28 | -------------------------------------------------------------------------------- /examples/middlewares/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "middlewares", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev:valibot": "tsx watch src/valibot.ts", 8 | "dev:zod": "tsx watch src/zod.ts" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@gqloom/core": "workspace:*", 15 | "@gqloom/valibot": "workspace:*", 16 | "@gqloom/zod": "workspace:*", 17 | "graphql": "^16.8.1", 18 | "graphql-yoga": "^5.6.0", 19 | "valibot": "1.0.0-beta.7", 20 | "zod": "4.0.0-beta.20250415T232143" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/middlewares/src/context/index.ts: -------------------------------------------------------------------------------- 1 | import { createMemoization } from "@gqloom/core/context" 2 | 3 | const users: User[] = [ 4 | { 5 | id: 1, 6 | name: "John", 7 | roles: ["admin"], 8 | }, 9 | { 10 | id: 2, 11 | name: "Jane", 12 | roles: ["editor"], 13 | }, 14 | { 15 | id: 3, 16 | name: "Bob", 17 | roles: [], 18 | }, 19 | ] 20 | 21 | export const useUser = createMemoization(async () => { 22 | const randomUser = users[Math.floor(Math.random() * users.length)] 23 | return randomUser 24 | }) 25 | 26 | export interface User { 27 | id: number 28 | name: string 29 | roles: ("admin" | "editor")[] 30 | } 31 | -------------------------------------------------------------------------------- /examples/middlewares/src/guard-valibot.ts: -------------------------------------------------------------------------------- 1 | import { mutation, resolver } from "@gqloom/core" 2 | import * as v from "valibot" 3 | import { authGuard } from "./middlewares" 4 | 5 | export const adminResolver = resolver({ 6 | deleteArticle: mutation(v.boolean(), () => true), 7 | }) 8 | 9 | adminResolver.use(authGuard("admin")) 10 | 11 | export const editorResolver = resolver({ 12 | createArticle: mutation(v.boolean(), () => true), 13 | 14 | updateArticle: mutation(v.boolean(), () => true), 15 | }) 16 | 17 | editorResolver.use(authGuard("editor")) 18 | -------------------------------------------------------------------------------- /examples/middlewares/src/guard-zod.ts: -------------------------------------------------------------------------------- 1 | import { mutation, resolver } from "@gqloom/core" 2 | import * as z from "zod" 3 | import { authGuard } from "./middlewares" 4 | 5 | export const AdminResolver = resolver( 6 | { 7 | deleteArticle: mutation(z.boolean(), () => true), 8 | }, 9 | { 10 | middlewares: [authGuard("admin")], 11 | } 12 | ) 13 | 14 | export const EditorResolver = resolver( 15 | { 16 | createArticle: mutation(z.boolean(), () => true), 17 | 18 | updateArticle: mutation(z.boolean(), () => true), 19 | }, 20 | { middlewares: [authGuard("editor")] } 21 | ) 22 | -------------------------------------------------------------------------------- /examples/middlewares/src/middlewares/auth-guard.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware } from "@gqloom/core" 2 | import { GraphQLError } from "graphql" 3 | import { useUser } from "../context" 4 | 5 | export function authGuard(role: "admin" | "editor"): Middleware { 6 | return async (next) => { 7 | const user = await useUser() 8 | if (user == null) throw new GraphQLError("Not authenticated") 9 | if (!user.roles.includes(role)) throw new GraphQLError("Not authorized") 10 | return next() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/middlewares/src/middlewares/cache.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware } from "@gqloom/core" 2 | 3 | /** Simple in-memory cache implementation */ 4 | const cacheStore = new Map() 5 | 6 | export interface CacheOptions { 7 | /** 8 | * Time to live in milliseconds 9 | * @default 60000 10 | */ 11 | ttl?: number 12 | } 13 | 14 | export const cache = (options: CacheOptions = {}): Middleware => { 15 | const { ttl = 60000 } = options 16 | 17 | const middleware: Middleware = async ({ next, payload }) => { 18 | if (!payload?.info) { 19 | return next() 20 | } 21 | 22 | const { fieldName, parentType } = payload.info 23 | const args = payload.args || {} 24 | const cacheKey = `${parentType.name}.${fieldName}:${JSON.stringify(args)}` 25 | 26 | const cached = cacheStore.get(cacheKey) 27 | if (cached && Date.now() - cached.timestamp < ttl) { 28 | return cached.data 29 | } 30 | 31 | const result = await next() 32 | cacheStore.set(cacheKey, { data: result, timestamp: Date.now() }) 33 | return result 34 | } 35 | 36 | // Only apply cache to queries by default 37 | middleware.operations = ["query"] 38 | 39 | return middleware 40 | } 41 | -------------------------------------------------------------------------------- /examples/middlewares/src/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./output-validator" 2 | export * from "./valibot-exception-filter" 3 | export * from "./zod-exception-filter" 4 | export * from "./auth-guard" 5 | -------------------------------------------------------------------------------- /examples/middlewares/src/middlewares/logger.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware } from "@gqloom/core" 2 | import { useResolverPayload } from "@gqloom/core/context" 3 | 4 | export const logger: Middleware = async (next) => { 5 | const info = useResolverPayload()!.info 6 | 7 | const start = Date.now() 8 | const result = await next() 9 | const resolveTime = Date.now() - start 10 | 11 | console.info(`${info.parentType.name}.${info.fieldName} [${resolveTime} ms]`) 12 | return result 13 | } 14 | -------------------------------------------------------------------------------- /examples/middlewares/src/middlewares/output-validator.ts: -------------------------------------------------------------------------------- 1 | import { type Middleware, silk } from "@gqloom/core" 2 | 3 | export const outputValidator: Middleware = async (opts) => { 4 | const output = await opts.next() 5 | return await silk.parse(opts.outputSilk, output) 6 | } 7 | -------------------------------------------------------------------------------- /examples/middlewares/src/middlewares/transaction.ts: -------------------------------------------------------------------------------- 1 | const db = {} as { 2 | beginTransaction: () => Promise 3 | commit: () => Promise 4 | rollback: () => Promise 5 | } 6 | 7 | // ---cut--- 8 | import type { Middleware } from "@gqloom/core" 9 | import { GraphQLError } from "graphql" 10 | 11 | export const transaction: Middleware = async ({ next }) => { 12 | try { 13 | await db.beginTransaction() 14 | 15 | const result = await next() 16 | 17 | await db.commit() 18 | 19 | return result 20 | } catch (error) { 21 | await db.rollback() 22 | throw new GraphQLError("Transaction failed", { 23 | extensions: { originalError: error }, 24 | }) 25 | } 26 | } 27 | 28 | transaction.operations = ["mutation"] 29 | -------------------------------------------------------------------------------- /examples/middlewares/src/middlewares/valibot-exception-filter.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware } from "@gqloom/core" 2 | import { GraphQLError } from "graphql" 3 | import { ValiError } from "valibot" 4 | 5 | export const ValibotExceptionFilter: Middleware = async (next) => { 6 | try { 7 | return await next() 8 | } catch (error) { 9 | if (error instanceof ValiError) { 10 | const { issues, message } = error 11 | throw new GraphQLError(message, { extensions: { issues } }) 12 | } 13 | throw error 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/middlewares/src/middlewares/zod-exception-filter.ts: -------------------------------------------------------------------------------- 1 | import type { Middleware } from "@gqloom/core" 2 | import { GraphQLError } from "graphql" 3 | import { ZodError } from "zod" 4 | 5 | export const ZodExceptionFilter: Middleware = async (next) => { 6 | try { 7 | return await next() 8 | } catch (error) { 9 | if (error instanceof ZodError) { 10 | throw new GraphQLError(error.format()._errors.join(", "), { 11 | extensions: { issues: error.issues }, 12 | }) 13 | } 14 | throw error 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/middlewares/src/valibot-posts.ts: -------------------------------------------------------------------------------- 1 | import { mutation, resolver } from "@gqloom/core" 2 | import * as v from "valibot" 3 | import { useUser } from "./context" 4 | 5 | const Post = v.object({ 6 | __typename: v.nullish(v.literal("Post")), 7 | id: v.number(), 8 | title: v.string(), 9 | content: v.string(), 10 | authorId: v.number(), 11 | }) 12 | 13 | interface IPost extends v.InferOutput {} 14 | 15 | const posts: IPost[] = [] 16 | 17 | export const postsResolver = resolver({ 18 | createPost: mutation(Post) 19 | .input( 20 | v.object({ 21 | title: v.string(), 22 | content: v.string(), 23 | authorId: v.number(), 24 | }) 25 | ) 26 | .use(async ({ next, parseInput }) => { 27 | const inputResult = await parseInput.getResult() 28 | inputResult.authorId = (await useUser()).id 29 | parseInput.result = { value: inputResult } 30 | return next() 31 | }) 32 | .resolve(({ title, content, authorId }) => { 33 | const post = { 34 | id: Math.random(), 35 | title, 36 | content, 37 | authorId, 38 | } 39 | posts.push(post) 40 | return post 41 | }), 42 | }) 43 | -------------------------------------------------------------------------------- /examples/middlewares/src/valibot.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { ValibotWeaver, query, resolver, weave } from "@gqloom/valibot" 3 | import { createYoga } from "graphql-yoga" 4 | import * as v from "valibot" 5 | import { ValibotExceptionFilter, outputValidator } from "./middlewares" 6 | 7 | const helloResolver = resolver({ 8 | hello: query(v.pipe(v.string(), v.minLength(10)), { 9 | input: { name: v.string() }, 10 | resolve: ({ name }) => `Hello, ${name}`, 11 | middlewares: [outputValidator], 12 | }), 13 | }) 14 | 15 | export const schema = weave( 16 | ValibotWeaver, 17 | helloResolver, 18 | ValibotExceptionFilter 19 | ) 20 | 21 | const yoga = createYoga({ schema }) 22 | createServer(yoga).listen(4000, () => { 23 | console.info("Server is running on http://localhost:4000/graphql") 24 | }) 25 | -------------------------------------------------------------------------------- /examples/middlewares/src/zod-posts.ts: -------------------------------------------------------------------------------- 1 | import { mutation, resolver } from "@gqloom/core" 2 | import { z } from "zod" 3 | import { useUser } from "./context" 4 | 5 | const Post = z.object({ 6 | __typename: z.literal("Post").nullish(), 7 | id: z.number(), 8 | title: z.string(), 9 | content: z.string(), 10 | authorId: z.number(), 11 | }) 12 | 13 | interface IPost extends z.output {} 14 | 15 | const posts: IPost[] = [] 16 | 17 | export const postsResolver = resolver({ 18 | createPost: mutation(Post) 19 | .input( 20 | z.object({ 21 | title: z.string(), 22 | content: z.string(), 23 | authorId: z.number(), 24 | }) 25 | ) 26 | .use(async ({ next, parseInput }) => { 27 | const inputResult = await parseInput.getResult() 28 | inputResult.authorId = (await useUser()).id 29 | parseInput.result = { value: inputResult } 30 | return next() 31 | }) 32 | .resolve(({ title, content, authorId }) => { 33 | const post = { 34 | id: Math.random(), 35 | title, 36 | content, 37 | authorId, 38 | } 39 | posts.push(post) 40 | return post 41 | }), 42 | }) 43 | -------------------------------------------------------------------------------- /examples/middlewares/src/zod.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { ZodWeaver, query, resolver, weave } from "@gqloom/zod" 3 | import { createYoga } from "graphql-yoga" 4 | import * as z from "zod" 5 | import { ZodExceptionFilter, outputValidator } from "./middlewares" 6 | 7 | const helloResolver = resolver({ 8 | hello: query(z.string().min(10), { 9 | input: { name: z.string() }, 10 | resolve: ({ name }) => `Hello, ${name}`, 11 | middlewares: [outputValidator], 12 | }), 13 | }) 14 | 15 | export const schema = weave(ZodWeaver, helloResolver, ZodExceptionFilter) 16 | 17 | const yoga = createYoga({ schema }) 18 | createServer(yoga).listen(4000, () => { 19 | console.info("Server is running on http://localhost:4000/graphql") 20 | }) 21 | -------------------------------------------------------------------------------- /examples/mikro-orm/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | prisma/migrations 3 | 4 | src/generated -------------------------------------------------------------------------------- /examples/mikro-orm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-mikro-orm", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "tsx watch --env-file .env src/index.ts" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@types/node": "^22.13.1", 14 | "tsx": "^4.7.2", 15 | "typescript": "^5.7.3" 16 | }, 17 | "dependencies": { 18 | "@gqloom/core": "^0.7.0", 19 | "@gqloom/mikro-orm": "^0.7.0", 20 | "@mikro-orm/core": "^6.4.6", 21 | "@mikro-orm/postgresql": "^6.4.6", 22 | "graphql-yoga": "^5.6.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/mikro-orm/src/entities.ts: -------------------------------------------------------------------------------- 1 | import { type EntitySchemaSilk, mikroSilk } from "@gqloom/mikro-orm" 2 | import { type Collection, EntitySchema, type Ref } from "@mikro-orm/core" 3 | 4 | export interface IUser { 5 | id: number 6 | createdAt: Date 7 | email: string 8 | name: string 9 | role: "admin" | "user" 10 | posts: Collection 11 | } 12 | 13 | export interface IPost { 14 | id: number 15 | createdAt: Date 16 | updatedAt: Date 17 | published: boolean 18 | title: string 19 | author: Ref 20 | } 21 | 22 | export const User: EntitySchemaSilk> = mikroSilk( 23 | new EntitySchema({ 24 | name: "User", 25 | properties: { 26 | id: { type: "number", primary: true }, 27 | createdAt: { type: Date, defaultRaw: "now", onCreate: () => new Date() }, 28 | email: { type: "string", unique: true }, 29 | name: { type: "string" }, 30 | role: { type: "string", default: "user" }, 31 | posts: { 32 | entity: () => Post, 33 | mappedBy: (post) => post.author, 34 | kind: "1:m", 35 | }, 36 | }, 37 | }) 38 | ) 39 | 40 | export const Post: EntitySchemaSilk> = mikroSilk( 41 | new EntitySchema({ 42 | name: "Post", 43 | properties: { 44 | id: { type: "number", primary: true }, 45 | createdAt: { 46 | type: Date, 47 | defaultRaw: "now", 48 | onCreate: () => new Date(), 49 | }, 50 | updatedAt: { 51 | type: Date, 52 | defaultRaw: "now", 53 | onCreate: () => new Date(), 54 | onUpdate: () => new Date(), 55 | }, 56 | published: { type: "boolean" }, 57 | title: { type: "string" }, 58 | author: { 59 | entity: () => User, 60 | ref: true, 61 | nullable: true, 62 | kind: "m:1", 63 | }, 64 | }, 65 | }) 66 | ) 67 | -------------------------------------------------------------------------------- /examples/mikro-orm/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { resolver, weave } from "@gqloom/core" 3 | import { MikroResolverFactory } from "@gqloom/mikro-orm" 4 | import { MikroORM } from "@mikro-orm/postgresql" 5 | import { createYoga } from "graphql-yoga" 6 | import { Post, User } from "./entities" 7 | 8 | const ormPromise = MikroORM.init({ 9 | dbName: "gqloom", 10 | entities: [User, Post], 11 | clientUrl: process.env.DATABASE_URL!, 12 | }) 13 | 14 | const userResolverFactory = new MikroResolverFactory(User, () => 15 | ormPromise.then((orm) => orm.em.fork()) 16 | ) 17 | 18 | const userResolver = resolver.of(User, { 19 | user: userResolverFactory.findOneQuery(), 20 | users: userResolverFactory.findManyQuery(), 21 | createUser: userResolverFactory.createMutation(), 22 | updateUser: userResolverFactory.updateMutation(), 23 | deleteUser: userResolverFactory.deleteOneMutation(), 24 | }) 25 | 26 | const postResolverFactory = new MikroResolverFactory(Post, () => 27 | ormPromise.then((orm) => orm.em.fork()) 28 | ) 29 | 30 | const postResolver = resolver.of(Post, { 31 | post: postResolverFactory.findOneQuery(), 32 | posts: postResolverFactory.findManyQuery(), 33 | createPost: postResolverFactory.createMutation(), 34 | updatePost: postResolverFactory.updateMutation(), 35 | deletePost: postResolverFactory.deleteOneMutation(), 36 | }) 37 | 38 | const schema = weave(userResolver, postResolver) 39 | 40 | // fs.writeFileSync(path.join(__dirname, "../schema.graphql"), printSchema(schema)) 41 | 42 | const yoga = createYoga({ schema }) 43 | const server = createServer(yoga) 44 | server.listen(4000, () => { 45 | console.info("Server is running on http://localhost:4000/graphql") 46 | }) 47 | -------------------------------------------------------------------------------- /examples/prisma/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | prisma/migrations 3 | 4 | src/generated -------------------------------------------------------------------------------- /examples/prisma/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-prisma", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "tsx watch src/index.ts", 8 | "generate": "prisma generate", 9 | "push": "prisma generate" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "prisma": "^5.20.0" 16 | }, 17 | "dependencies": { 18 | "@gqloom/core": "workspace:*", 19 | "@gqloom/prisma": "workspace:*", 20 | "@gqloom/valibot": "workspace:*", 21 | "@prisma/client": "5.20.0", 22 | "graphql": "^16.8.1", 23 | "graphql-yoga": "^5.6.0", 24 | "valibot": "1.0.0-beta.7" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/prisma/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "postgresql" 3 | url = env("DATABASE_URL") 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | output = "../src/generated/client" 9 | } 10 | 11 | generator gqloom { 12 | provider = "prisma-gqloom" 13 | output = "../src/generated/gqloom" 14 | } 15 | 16 | model User { 17 | id Int @id @default(autoincrement()) 18 | createdAt DateTime @default(now()) 19 | email String @unique 20 | name String? 21 | role Role @default(USER) 22 | posts Post[] 23 | } 24 | 25 | model Post { 26 | id Int @id @default(autoincrement()) 27 | createdAt DateTime @default(now()) 28 | updatedAt DateTime @updatedAt 29 | published Boolean @default(false) 30 | title String @db.VarChar(255) 31 | content String 32 | author User? @relation(fields: [authorId], references: [id]) 33 | authorId Int? 34 | } 35 | 36 | enum Role { 37 | USER 38 | ADMIN 39 | } 40 | -------------------------------------------------------------------------------- /examples/prisma/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "node:fs" 2 | import { createServer } from "node:http" 3 | import * as path from "node:path" 4 | import { weave } from "@gqloom/core" 5 | import { PrismaResolverFactory } from "@gqloom/prisma" 6 | import { printSchema } from "graphql" 7 | import { createYoga } from "graphql-yoga" 8 | import { PrismaClient } from "./generated/client" 9 | import { Post, User } from "./generated/gqloom" 10 | 11 | const db = new PrismaClient() 12 | 13 | const userResolver = new PrismaResolverFactory(User, db).resolver() 14 | const postResolver = new PrismaResolverFactory(Post, db).resolver() 15 | 16 | const schema = weave(userResolver, postResolver) 17 | 18 | fs.writeFileSync(path.join(__dirname, "schema.graphql"), printSchema(schema)) 19 | 20 | const yoga = createYoga({ schema }) 21 | const server = createServer(yoga) 22 | server.listen(4000, () => { 23 | console.info("Server is running on http://localhost:4000/graphql") 24 | }) 25 | -------------------------------------------------------------------------------- /examples/prisma/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "bundler" 5 | }, 6 | "include": ["src/**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /examples/subscriptions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "subscriptions", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev:valibot": "tsx watch src/valibot.ts", 8 | "dev:zod": "tsx watch src/zod.ts" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@gqloom/core": "workspace:*", 15 | "@gqloom/valibot": "workspace:*", 16 | "@gqloom/zod": "workspace:*", 17 | "graphql": "^16.8.1", 18 | "graphql-yoga": "^5.6.0", 19 | "valibot": "1.0.0-beta.7", 20 | "zod": "4.0.0-beta.20250415T232143" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/subscriptions/src/valibot.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { 3 | ValibotWeaver, 4 | query, 5 | resolver, 6 | subscription, 7 | weave, 8 | } from "@gqloom/valibot" 9 | import { createPubSub, createYoga } from "graphql-yoga" 10 | import * as v from "valibot" 11 | 12 | const CountdownResolver = resolver({ 13 | countdown1: subscription(v.number()) 14 | .input({ seconds: v.pipe(v.number(), v.integer()) }) 15 | .subscribe(async function* (data) { 16 | for (let i = data.seconds; i >= 0; i--) { 17 | await new Promise((resolve) => setTimeout(resolve, 1000)) 18 | yield i // There's no problem. The return value matches the expected type. 19 | } 20 | }), 21 | 22 | // countdown2: subscription(v.number()) 23 | // .input({ seconds: v.pipe(v.number(), v.integer()) }) 24 | // .subscribe(async function* (data) { 25 | // for (let i = data.seconds; i >= 0; i--) { 26 | // await new Promise((resolve) => setTimeout(resolve, 1000)) 27 | // yield String(i) // TypeScript will remind us that the return value here does not match the expected return type, and there is no resolve function. 28 | // } 29 | // }), 30 | 31 | countdown3: subscription(v.number()) 32 | .input({ seconds: v.pipe(v.number(), v.integer()) }) 33 | .subscribe(async function* (data) { 34 | for (let i = data.seconds; i >= 0; i--) { 35 | await new Promise((resolve) => setTimeout(resolve, 1000)) 36 | yield String(i) // There's no problem, because we've added the correct resolve function. 37 | } 38 | }) 39 | .resolve((i) => Number(i)), 40 | }) 41 | 42 | const pubSub = createPubSub<{ greeting: [string] }>() 43 | 44 | const HelloResolver = resolver({ 45 | hello: query(v.string()) 46 | .input({ name: v.string() }) 47 | .resolve(({ name }) => { 48 | const hello = `Hello, ${name}` 49 | pubSub.publish("greeting", hello) 50 | return hello 51 | }), 52 | 53 | listenGreeting: subscription(v.string()) 54 | .subscribe(() => pubSub.subscribe("greeting")) 55 | .resolve((payload) => payload), 56 | }) 57 | 58 | const schema = weave(ValibotWeaver, CountdownResolver, HelloResolver) 59 | const yoga = createYoga({ schema }) 60 | const server = createServer(yoga) 61 | 62 | server.listen(4000, () => { 63 | console.info("Server is running on http://localhost:4000/graphql") 64 | }) 65 | -------------------------------------------------------------------------------- /examples/subscriptions/src/zod.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from "node:http" 2 | import { query, resolver, subscription, weave } from "@gqloom/zod" 3 | import { createPubSub, createYoga } from "graphql-yoga" 4 | import * as z from "zod" 5 | 6 | const CountdownResolver = resolver({ 7 | countdown: subscription(z.number()) 8 | .input({ seconds: z.number().int() }) 9 | .subscribe(async function* (data) { 10 | for (let i = data.seconds; i >= 0; i--) { 11 | await new Promise((resolve) => setTimeout(resolve, 1000)) 12 | yield i 13 | } 14 | }), 15 | }) 16 | 17 | const pubSub = createPubSub<{ greeting: [string] }>() 18 | 19 | const HelloResolver = resolver({ 20 | hello: query(z.string()) 21 | .input({ name: z.string() }) 22 | .resolve(({ name }) => { 23 | const hello = `Hello, ${name}` 24 | pubSub.publish("greeting", hello) 25 | return hello 26 | }), 27 | 28 | listenGreeting: subscription(z.string()) 29 | .subscribe(() => pubSub.subscribe("greeting")) 30 | .resolve((payload) => payload), 31 | }) 32 | 33 | const schema = weave(CountdownResolver, HelloResolver) 34 | const yoga = createYoga({ schema }) 35 | const server = createServer(yoga) 36 | 37 | server.listen(4000, () => { 38 | console.info("Server is running on http://localhost:4000/graphql") 39 | }) 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gqloom", 3 | "version": "0.0.0", 4 | "description": "Create GraphQL schema and resolvers with TypeScript, using Zod, Valibot or Yup!", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "vitest run --typecheck", 8 | "coverage": "vitest run --coverage", 9 | "format": "biome format --write .", 10 | "lint": "biome lint --write .", 11 | "fix": "biome check --write .", 12 | "check": "biome check --write . && pnpm run check:type", 13 | "check:type": "pnpm -r check:type", 14 | "build": "pnpm -F '@gqloom/core' run build && pnpm -F '@gqloom/drizzle' run build && pnpm -F '@gqloom/federation' run build && pnpm -F '@gqloom/mikro-orm' run build && pnpm -F '@gqloom/prisma' run build && pnpm -F '@gqloom/valibot' -F '@gqloom/yup' -F '@gqloom/zod' run build", 15 | "prepare": "husky", 16 | "compose-up": "docker compose up -d", 17 | "push": "pnpm -F @gqloom/drizzle -F @gqloom/prisma -F example-prisma push", 18 | "ci:publish": "pnpm -F @gqloom/* publish" 19 | }, 20 | "lint-staged": { 21 | "*.{ts,tsx,js,jsx,json}": ["biome check --no-errors-on-unmatched --write"], 22 | "*.{json,toml,graphql,gql,css}": "biome format --no-errors-on-unmatched --write" 23 | }, 24 | "keywords": [], 25 | "author": "xcfox", 26 | "license": "MIT", 27 | "devDependencies": { 28 | "@biomejs/biome": "1.9.4", 29 | "@graphql-tools/utils": "^10.1.3", 30 | "@types/node": "^20.11.30", 31 | "@vitest/coverage-v8": "^2.1.8", 32 | "graphql": "^16.8.1", 33 | "husky": "^9.0.11", 34 | "lint-staged": "^15.2.7", 35 | "tsup": "^8.1.0", 36 | "tsx": "^4.7.2", 37 | "typescript": "^5.4.3", 38 | "vitest": "^2.1.8" 39 | }, 40 | "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39", 41 | "pnpm": { 42 | "patchedDependencies": { 43 | "graphql@16.8.1": "patches/graphql@16.8.1.patch" 44 | }, 45 | "overrides": { 46 | "pg": "^8.13.2" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## next (YYYY-MM-DD) 6 | 7 | - Refactor: `field.load()` now accept `input` and `payload` parameters 8 | - Refactor: move `useContext` to `@gqloom/core/context` 9 | - Feat: introduce InjectableContext and createContext for enhanced dependency management 10 | - Feat: Updated resolver functions to accept a `payload` parameter 11 | - Fix: handle null and undefined inputs in GraphQLSchemaLoom.optionsFrom method 12 | - Refactor(resolver): update input type handling to use 'void' instead of 'undefined' 13 | - Feat: add `useResolvingFields` to get the fields that are being resolved 14 | 15 | ## 0.8.4 (2025-04-08) 16 | 17 | - Feat: add `screamingSnakeCase` to convert strings to screaming snake case 18 | 19 | ## 0.8.3 (2025-03-31) 20 | 21 | - Fix: ensure context work in subscription 22 | 23 | ## 0.8.2 (2025-03-26) 24 | 25 | - Fix: improve type inference for `subscription` 26 | 27 | ## 0.8.1 (2025-03-23) 28 | 29 | - Feat: export Resolver types 30 | 31 | ## 0.8.0 (2025-03-11) 32 | 33 | - Feat: `field.load()` to easier load data using data loader 34 | - Feat: chaining resolver factory 35 | - Feat: enhance type inference for `silk` 36 | - **Break change** refactor: rename `MiddlewareOptions.type` to `MiddlewareOptions.operation` 37 | - **Break change** refactor: remove `createLoom` in `@gqloom/core` 38 | 39 | ## 0.7.2 (2025-02-16) 40 | 41 | - Fix: update document link in README 42 | 43 | ## 0.7.1 (2025-02-16) 44 | 45 | - Fix: `query().resolve()` return type 46 | - Fix: update CallableInputParser interface documentation 47 | 48 | ## 0.7.0 (2025-01-25) 49 | 50 | - Fix: `silk.list` to correctly handle non-nullable list types 51 | - Refactor: move `EasyDataLoader` to `@gqloom/core` 52 | 53 | ## 0.6.0 (2024-12-13) 54 | 55 | - Feature: auto assign names to objects and inputs 56 | - Refactor: rename `SchemaWeaver` to `GraphQLSchemaLoom` 57 | 58 | ## 0.5.0 (2024-12-03) 59 | 60 | - Fix: add all objects in `resolver.of` to `context.types` 61 | - Chore: update `@standard-schema/spec` to 1.0.0-beta.4 62 | - Refactor: update `CallableMiddlewareOptions` for middleware 63 | 64 | ## 0.4.0 (2024-11-12) 65 | 66 | - Refactor: follow standard-schema ([#7](https://github.com/modevol-com/gqloom/pull/7)) 67 | 68 | ## 0.3.0 (2024-10-19) 69 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gqloom/core", 3 | "version": "0.9.4", 4 | "description": "Create GraphQL schema and resolvers with TypeScript.", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | }, 18 | "source": { 19 | "types": "./src/index.ts", 20 | "default": "./src/index.ts" 21 | } 22 | }, 23 | "./context": { 24 | "import": { 25 | "types": "./dist/context.d.ts", 26 | "default": "./dist/context.js" 27 | }, 28 | "require": { 29 | "types": "./dist/context.d.cts", 30 | "default": "./dist/context.cjs" 31 | }, 32 | "source": { 33 | "types": "./src/context/index.ts", 34 | "default": "./src/context/index.ts" 35 | } 36 | } 37 | }, 38 | "scripts": { 39 | "build": "tsup --dts-resolve", 40 | "check:type": "tsc --noEmit" 41 | }, 42 | "files": ["dist"], 43 | "peerDependencies": { 44 | "graphql": ">= 16.8.0" 45 | }, 46 | "keywords": [ 47 | "gqloom", 48 | "graphql", 49 | "schema", 50 | "typescript", 51 | "valibot", 52 | "zod", 53 | "yup" 54 | ], 55 | "author": "xcfox", 56 | "license": "MIT", 57 | "homepage": "https://gqloom.dev/", 58 | "repository": { 59 | "type": "git", 60 | "url": "https://github.com/modevol-com/gqloom.git", 61 | "directory": "packages/core" 62 | }, 63 | "publishConfig": { 64 | "access": "public" 65 | }, 66 | "devDependencies": { 67 | "@standard-schema/spec": "1.0.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/core/src/context/async-iterator.ts: -------------------------------------------------------------------------------- 1 | import type { AsyncLocalStorage } from "node:async_hooks" 2 | 3 | export function bindAsyncIterator< 4 | TAsyncLocalStorage extends AsyncLocalStorage, 5 | TAsyncIterator extends AsyncIterator, 6 | >(storage: TAsyncLocalStorage, generator: TAsyncIterator): TAsyncIterator { 7 | const store = storage.getStore() 8 | const next = generator.next 9 | Object.defineProperty(generator, "next", { 10 | value: (...args: [unknown]) => 11 | storage.run(store, () => next.apply(generator, args)), 12 | writable: false, 13 | }) 14 | return generator as TAsyncIterator 15 | } 16 | 17 | export function isAsyncIterator( 18 | value: unknown 19 | ): value is AsyncIterator { 20 | return ( 21 | value !== null && 22 | typeof value === "object" && 23 | "next" in value && 24 | typeof value.next === "function" 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/context/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./use-resolving-fields" 2 | export * from "./async-iterator" 3 | export * from "./context" 4 | -------------------------------------------------------------------------------- /packages/core/src/context/use-resolving-fields.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type ResolvingFields, 3 | getResolvingFields, 4 | } from "../utils/parse-resolving-fields" 5 | import { createContext, useResolverPayload } from "./context" 6 | 7 | /** 8 | * A hook that analyzes and processes field resolution in a GraphQL query. 9 | * 10 | * @returns An object containing sets of different field types, 11 | * or undefined if no resolver payload is available 12 | */ 13 | export const useResolvingFields = createContext( 14 | () => { 15 | const payload = useResolverPayload() 16 | if (!payload) return 17 | 18 | return getResolvingFields(payload) 19 | } 20 | ) 21 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./utils" 2 | export * from "./resolver" 3 | export * from "./schema" 4 | export * from "@standard-schema/spec" 5 | -------------------------------------------------------------------------------- /packages/core/src/resolver/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./silk" 2 | export * from "./resolver" 3 | export * from "./types" 4 | export * from "./input" 5 | -------------------------------------------------------------------------------- /packages/core/src/schema/extensions.ts: -------------------------------------------------------------------------------- 1 | export interface GQLoomExtensions { 2 | defaultValue?: any 3 | gqloom?: GQLoomExtensionAttribute 4 | directives?: DirectiveItem[] | DirectiveRecord 5 | } 6 | 7 | export interface DirectiveItem { 8 | name: string 9 | args?: Record 10 | } 11 | 12 | export type DirectiveRecord = Record> 13 | 14 | export interface GQLoomExtensionAttribute { 15 | directives?: string[] 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./object" 2 | export * from "./schema-weaver" 3 | export * from "./schema-loom" 4 | export * from "./weaver-context" 5 | export * from "./input" 6 | export * from "./interface" 7 | export * from "./types" 8 | export * from "./extensions" 9 | -------------------------------------------------------------------------------- /packages/core/src/schema/interface.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLInterfaceType, 3 | GraphQLObjectType, 4 | GraphQLString, 5 | printType, 6 | } from "graphql" 7 | import { describe, expect, it } from "vitest" 8 | import { ensureInterfaceType } from "./interface" 9 | import { initWeaverContext, provideWeaverContext } from "./weaver-context" 10 | 11 | describe("ensureInterfaceType", () => { 12 | it("should handle interface type", () => { 13 | const interfaceType = new GraphQLInterfaceType({ 14 | name: "Dog", 15 | fields: { name: { type: GraphQLString } }, 16 | }) 17 | const result = ensureInterfaceType(interfaceType) 18 | expect(result).toBe(interfaceType) 19 | expect(printType(result)).toMatchInlineSnapshot(` 20 | "interface Dog { 21 | name: String 22 | }" 23 | `) 24 | }) 25 | 26 | it("should handle object type", () => { 27 | const objectType = new GraphQLObjectType({ 28 | name: "Dog", 29 | fields: { name: { type: GraphQLString } }, 30 | }) 31 | const result = ensureInterfaceType(objectType) 32 | expect(result).toBeInstanceOf(GraphQLInterfaceType) 33 | expect(printType(result)).toMatchInlineSnapshot(` 34 | "interface Dog { 35 | name: String 36 | }" 37 | `) 38 | }) 39 | 40 | it("should return same object for same input ", () => { 41 | const objectType = new GraphQLObjectType({ 42 | name: "Dog", 43 | fields: { name: { type: GraphQLString } }, 44 | }) 45 | provideWeaverContext(() => { 46 | const result = ensureInterfaceType(objectType) 47 | expect(result).toBe(ensureInterfaceType(objectType)) 48 | }, initWeaverContext()) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /packages/core/src/schema/interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLInterfaceType, 3 | type GraphQLInterfaceTypeConfig, 4 | type GraphQLOutputType, 5 | isInterfaceType, 6 | isObjectType, 7 | } from "graphql" 8 | import { mapValue } from "../utils" 9 | import { getCacheType } from "./object" 10 | import { weaverContext } from "./weaver-context" 11 | 12 | export function ensureInterfaceType( 13 | gqlType: GraphQLOutputType, 14 | interfaceConfig?: Partial> 15 | ): GraphQLInterfaceType { 16 | if (isInterfaceType(gqlType)) return gqlType 17 | 18 | if (!isObjectType(gqlType)) 19 | throw new Error(`${gqlType.toString()} is not an object`) 20 | 21 | const key = gqlType 22 | 23 | const existing = weaverContext.interfaceMap?.get(key) 24 | if (existing != null) return existing 25 | 26 | const { 27 | astNode: _, 28 | extensionASTNodes: _1, 29 | fields, 30 | ...config 31 | } = gqlType.toConfig() 32 | const interfaceType = new GraphQLInterfaceType({ 33 | ...config, 34 | ...interfaceConfig, 35 | fields: mapValue(fields, (field) => { 36 | return { ...field, type: getCacheType(field.type) } 37 | }), 38 | }) 39 | 40 | weaverContext.interfaceMap?.set(key, interfaceType) 41 | return interfaceType 42 | } 43 | -------------------------------------------------------------------------------- /packages/core/src/schema/schema-weaver.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest" 2 | import { isSchemaVendorWeaver } from "./schema-weaver" 3 | 4 | describe("isSchemaVendorWeaver", () => { 5 | it("should return true for a valid SchemaWeaver", () => { 6 | const validSchemaWeaver = { 7 | vendor: "testVendor", 8 | getGraphQLType: (schema: any) => schema, 9 | } 10 | 11 | expect(isSchemaVendorWeaver(validSchemaWeaver)).toBe(true) 12 | }) 13 | 14 | it("should return false for an object missing the 'vendor' property", () => { 15 | const invalidSchemaWeaver = { 16 | getGraphQLType: (schema: any) => schema, 17 | } 18 | 19 | expect(isSchemaVendorWeaver(invalidSchemaWeaver)).toBe(false) 20 | }) 21 | 22 | it("should return false for an object missing the 'getGraphQLType' property", () => { 23 | const invalidSchemaWeaver = { 24 | vendor: "testVendor", 25 | } 26 | 27 | expect(isSchemaVendorWeaver(invalidSchemaWeaver)).toBe(false) 28 | }) 29 | 30 | it("should return false for an object with 'vendor' not being a string", () => { 31 | const invalidSchemaWeaver = { 32 | vendor: 123, 33 | getGraphQLType: (schema: any) => schema, 34 | } 35 | 36 | expect(isSchemaVendorWeaver(invalidSchemaWeaver)).toBe(false) 37 | }) 38 | 39 | it("should return false for an object with 'getGraphQLType' not being a function", () => { 40 | const invalidSchemaWeaver = { 41 | vendor: "testVendor", 42 | getGraphQLType: "not a function", 43 | } 44 | 45 | expect(isSchemaVendorWeaver(invalidSchemaWeaver)).toBe(false) 46 | }) 47 | 48 | it("should return false for a non-object and non-function input", () => { 49 | expect(isSchemaVendorWeaver(null)).toBe(false) 50 | expect(isSchemaVendorWeaver(undefined)).toBe(false) 51 | expect(isSchemaVendorWeaver(123)).toBe(false) 52 | expect(isSchemaVendorWeaver("string")).toBe(false) 53 | expect(isSchemaVendorWeaver([])).toBe(false) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /packages/core/src/schema/schema-weaver.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLOutputType } from "graphql" 2 | 3 | export interface SchemaWeaver { 4 | vendor: string 5 | getGraphQLType: (schema: any) => GraphQLOutputType 6 | } 7 | 8 | export function isSchemaVendorWeaver(some: any): some is SchemaWeaver { 9 | if (some == null) return false 10 | if (typeof some !== "object" && typeof some !== "function") return false 11 | if (!("getGraphQLType" in some) || typeof some.getGraphQLType !== "function") 12 | return false 13 | if (!("vendor" in some) || typeof some.vendor !== "string") return false 14 | 15 | return true 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/schema/types.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLSchemaConfig } from "graphql" 2 | import type { WEAVER_CONFIG } from "../utils/symbols" 3 | import type { WeaverConfig, WeaverContext } from "./weaver-context" 4 | 5 | export interface CoreSchemaWeaverConfigOptions extends GraphQLSchemaConfig { 6 | getInputObjectName?: (name: string) => string 7 | weaverContext?: WeaverContext 8 | } 9 | 10 | export interface CoreSchemaWeaverConfig 11 | extends WeaverConfig, 12 | CoreSchemaWeaverConfigOptions { 13 | [WEAVER_CONFIG]: "gqloom.core.schema" 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/src/utils/args.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLFieldExtensions } from "graphql" 2 | import type { 3 | FieldOptions, 4 | GraphQLFieldOptions, 5 | MutationOptions, 6 | QueryOptions, 7 | ResolverPayload, 8 | SubscriptionOptions, 9 | } from "../resolver/types" 10 | 11 | export function getOperationOptions( 12 | resolveOrOptions: 13 | | ((...args: any) => any) 14 | | FieldOptions 15 | | QueryOptions 16 | | MutationOptions 17 | ) { 18 | if (typeof resolveOrOptions === "function") { 19 | return { resolve: resolveOrOptions } 20 | } 21 | return resolveOrOptions as any 22 | } 23 | 24 | export function getSubscriptionOptions( 25 | subscribeOrOptions: 26 | | ((payload: ResolverPayload | undefined) => any) 27 | | SubscriptionOptions 28 | ): SubscriptionOptions { 29 | if (typeof subscribeOrOptions === "function") { 30 | return { subscribe: subscribeOrOptions } 31 | } 32 | return subscribeOrOptions 33 | } 34 | 35 | export function getFieldOptions( 36 | { description, deprecationReason, extensions }: GraphQLFieldOptions, 37 | extraExtensions?: GraphQLFieldExtensions 38 | ): GraphQLFieldOptions { 39 | return { 40 | description, 41 | deprecationReason, 42 | extensions: extraExtensions 43 | ? { ...extensions, ...extraExtensions } 44 | : extensions, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/core/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const DERIVED_DEPENDENCIES = "loom.derived-from-dependencies" 2 | -------------------------------------------------------------------------------- /packages/core/src/utils/context.ts: -------------------------------------------------------------------------------- 1 | import type { ResolverPayload } from "../resolver" 2 | import { CONTEXT_MAP_KEY } from "./symbols" 3 | 4 | /** 5 | * Empty Resolver Arguments that only store the memoization 6 | */ 7 | export interface OnlyMemoizationPayload { 8 | memoization: WeakMap 9 | isMemoization: true 10 | } 11 | 12 | /** 13 | * Create an empty memoization payload for the resolver 14 | * @returns the empty memoization payload 15 | */ 16 | export function onlyMemoization(): OnlyMemoizationPayload { 17 | return { memoization: new WeakMap(), isMemoization: true } 18 | } 19 | 20 | export function isOnlyMemoryPayload( 21 | payload: OnlyMemoizationPayload | Pick 22 | ): payload is OnlyMemoizationPayload { 23 | return (payload as OnlyMemoizationPayload).isMemoization === true 24 | } 25 | 26 | export function getMemoizationMap( 27 | payload: OnlyMemoizationPayload | Pick 28 | ) { 29 | if (isOnlyMemoryPayload(payload)) return payload.memoization 30 | if (typeof payload.context === "undefined") { 31 | Object.defineProperty(payload, "context", { value: {} }) 32 | } 33 | return assignContextMap(payload.context) 34 | } 35 | 36 | interface ContextMemoryContainer { 37 | [CONTEXT_MAP_KEY]?: WeakMap 38 | } 39 | 40 | export function assignContextMap( 41 | target: ContextMemoryContainer 42 | ): WeakMap { 43 | target[CONTEXT_MAP_KEY] ??= new WeakMap() 44 | return target[CONTEXT_MAP_KEY] 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/utils/error.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest" 2 | import { markLocation, tryIn } from "./error" 3 | 4 | describe("markLocation", () => { 5 | it("should mark location", () => { 6 | expect(markLocation("error", "banana")).toEqual("[banana] error") 7 | }) 8 | 9 | it("should not effect message when location is empty", () => { 10 | expect(markLocation("error")).toEqual("error") 11 | }) 12 | 13 | it("should mark location with multiple locations", () => { 14 | expect(markLocation("error", "banana", "apple")).toEqual( 15 | "[banana.apple] error" 16 | ) 17 | }) 18 | 19 | it("should mark location for message with location", () => { 20 | expect(markLocation("[banana] error", "apple")).toEqual( 21 | "[apple.banana] error" 22 | ) 23 | expect(markLocation("[banana] error", "apple", "orange")).toEqual( 24 | "[apple.orange.banana] error" 25 | ) 26 | expect(markLocation("[apple.banana] error", "orange")).toEqual( 27 | "[orange.apple.banana] error" 28 | ) 29 | expect(markLocation("[apple.banana] error", "orange", "peach")).toEqual( 30 | "[orange.peach.apple.banana] error" 31 | ) 32 | }) 33 | }) 34 | 35 | describe("tryIn", () => { 36 | it("should return value when no error", () => { 37 | expect(tryIn(() => 1, "banana")).toEqual(1) 38 | }) 39 | it("should throw error when error", () => { 40 | expect(() => 41 | tryIn(() => { 42 | throw new Error("error") 43 | }, "banana") 44 | ).toThrow("[banana] error") 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /packages/core/src/utils/error.ts: -------------------------------------------------------------------------------- 1 | export function markErrorLocation( 2 | error: TError, 3 | ...locations: string[] 4 | ): TError { 5 | if (error instanceof Error) { 6 | error.message = markLocation(error.message, ...locations) 7 | } 8 | return error 9 | } 10 | 11 | export function tryIn(func: () => T, ...locations: string[]): T { 12 | try { 13 | return func() 14 | } catch (error) { 15 | throw markErrorLocation(error, ...locations) 16 | } 17 | } 18 | 19 | /** 20 | * mark message with location 21 | * @param message origin message 22 | * @param locations where error happened 23 | * @returns message with location 24 | * @example markLocation("error", "banana") // "[banana] hello" 25 | * @example markLocation("error", fruit, banana) // "[fruit.banana] error" 26 | * @example markLocation("[banana] error", "fruit") // "[fruit.banana] error" 27 | * @example markLocation("[fruit.banana] error", "favorite") // "[favorite.fruit.banana] error" 28 | */ 29 | export function markLocation(message: string, ...locations: string[]): string { 30 | // If there's no valid location provided, return the original message 31 | if (locations.length === 0) { 32 | return message 33 | } 34 | 35 | // Check if the message already has a location prefix and extract it if present 36 | const [existingPrefix, newMessage] = (() => { 37 | const existingPrefixPattern = /^\[(.*?)\]/ 38 | const match = existingPrefixPattern.exec(message) 39 | 40 | if (match) return [match[1], message.slice(match[0].length).trim()] 41 | return [undefined, message] 42 | })() 43 | 44 | // Concatenate the new locations with the existing one (if any) 45 | const combinedLocation = locations 46 | .concat(existingPrefix ? [existingPrefix] : []) 47 | .join(".") 48 | 49 | // Add or update the location prefix in the message 50 | return `[${combinedLocation}] ${newMessage}` 51 | } 52 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types" 2 | export * from "./args" 3 | export * from "./middleware" 4 | export * from "./object" 5 | export * from "./parse-resolving-fields" 6 | export * from "./string" 7 | export * from "./error" 8 | export * from "./loader" 9 | export * from "./context" 10 | export * as SYMBOLS from "../utils/symbols" 11 | -------------------------------------------------------------------------------- /packages/core/src/utils/string.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest" 2 | import { capitalize, pascalCase, screamingSnakeCase } from "./string" 3 | 4 | describe("toPascalCase", () => { 5 | it("should convert kebab-case to PascalCase", () => { 6 | expect(pascalCase("hello-world")).toBe("HelloWorld") 7 | }) 8 | 9 | it("should convert camelCase to PascalCase", () => { 10 | expect(pascalCase("helloWorld")).toBe("HelloWorld") 11 | }) 12 | 13 | it("should convert snake_case to PascalCase", () => { 14 | expect(pascalCase("hello_world")).toBe("HelloWorld") 15 | }) 16 | 17 | it("should convert space separated words to PascalCase", () => { 18 | expect(pascalCase("hello world")).toBe("HelloWorld") 19 | }) 20 | 21 | it("should return empty string for empty input", () => { 22 | expect(pascalCase("")).toBe("") 23 | }) 24 | 25 | it("should capitalize the first letter of each word", () => { 26 | expect(pascalCase("multiple-words-here")).toBe("MultipleWordsHere") 27 | }) 28 | }) 29 | 30 | describe("capitalize", () => { 31 | it("should capitalize the first letter of a string", () => { 32 | expect(capitalize("hello")).toBe("Hello") 33 | }) 34 | }) 35 | 36 | describe("screamingSnakeCase", () => { 37 | it("should convert kebab-case to SCREAMING_SNAKE_CASE", () => { 38 | expect(screamingSnakeCase("hello-world")).toBe("HELLO_WORLD") 39 | }) 40 | 41 | it("should convert camelCase to SCREAMING_SNAKE_CASE", () => { 42 | expect(screamingSnakeCase("helloWorld")).toBe("HELLO_WORLD") 43 | }) 44 | 45 | it("should convert snake_case to SCREAMING_SNAKE_CASE", () => { 46 | expect(screamingSnakeCase("hello_world")).toBe("HELLO_WORLD") 47 | }) 48 | 49 | it("should convert space separated words to SCREAMING_SNAKE_CASE", () => { 50 | expect(screamingSnakeCase("hello world")).toBe("HELLO_WORLD") 51 | }) 52 | 53 | it("should return empty string for empty input", () => { 54 | expect(screamingSnakeCase("")).toBe("") 55 | }) 56 | 57 | it("should handle multiple words with mixed separators", () => { 58 | expect(screamingSnakeCase("hello-world_here")).toBe("HELLO_WORLD_HERE") 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /packages/core/src/utils/string.ts: -------------------------------------------------------------------------------- 1 | export function pascalCase(str: string): string { 2 | return str 3 | .split(/[\s-_]+/) 4 | .map((word, index) => 5 | index === 0 6 | ? word.charAt(0).toUpperCase() + word.slice(1) 7 | : word.charAt(0).toUpperCase() + word.slice(1) 8 | ) 9 | .join("") 10 | } 11 | 12 | export function capitalize(str: T): Capitalize { 13 | return (str.slice(0, 1).toUpperCase() + str.slice(1)) as Capitalize 14 | } 15 | 16 | export function screamingSnakeCase(str: string): string { 17 | return str 18 | .replace(/([a-z])([A-Z])/g, "$1_$2") 19 | .split(/[\s-_]+/) 20 | .map((word) => word.toUpperCase()) 21 | .join("_") 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/utils/symbols.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The symbol to get GraphQL type for schema 3 | */ 4 | export const GET_GRAPHQL_TYPE = Symbol.for("gqloom.get_graphql_type") 5 | 6 | /** 7 | * The symbol to get and store weaver config 8 | */ 9 | export const WEAVER_CONFIG = Symbol.for("gqloom.weaver_config") 10 | 11 | /** 12 | * The symbol to get resolver options 13 | */ 14 | export const RESOLVER_OPTIONS_KEY = Symbol.for("gqloom.resolver-options") 15 | 16 | /** 17 | * The symbol to check if an object is a resolver 18 | */ 19 | export const IS_RESOLVER = Symbol.for("gqloom.is-resolver") 20 | 21 | /** 22 | * The symbol to assign a WeakMap to an object 23 | */ 24 | export const CONTEXT_MAP_KEY = Symbol.for("gqloom.context-map") 25 | 26 | /** 27 | * Set fields to be hidden 28 | */ 29 | export const FIELD_HIDDEN = false 30 | -------------------------------------------------------------------------------- /packages/core/src/utils/type.spec.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLList, GraphQLNonNull } from "graphql" 2 | import { GraphQLString } from "graphql" 3 | import { describe, expect, it } from "vitest" 4 | import { unwrapType } from "./type" 5 | 6 | describe("unwrapType", () => { 7 | it("should unwrap a unwrapped type", () => { 8 | const type = unwrapType(GraphQLString) 9 | expect(type).toBe(GraphQLString) 10 | }) 11 | 12 | it("should unwrap a non-null type", () => { 13 | const type = unwrapType(new GraphQLNonNull(GraphQLString)) 14 | expect(type).toBe(GraphQLString) 15 | }) 16 | 17 | it("should unwrap a list type", () => { 18 | const type = unwrapType(new GraphQLList(GraphQLString)) 19 | expect(type).toBe(GraphQLString) 20 | }) 21 | 22 | it("should unwrap a non-null list type", () => { 23 | const type = unwrapType( 24 | new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))) 25 | ) 26 | expect(type).toBe(GraphQLString) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/core/src/utils/type.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLNonNull } from "graphql" 2 | import type { GraphQLOutputType } from "graphql" 3 | import { isListType } from "graphql" 4 | import type { GraphQLList } from "graphql" 5 | import { isNonNullType } from "graphql" 6 | 7 | export function unwrapType( 8 | gqlType: GraphQLOutputType 9 | ): Exclude< 10 | GraphQLOutputType, 11 | GraphQLList | GraphQLNonNull 12 | > { 13 | if (isNonNullType(gqlType)) { 14 | return unwrapType(gqlType.ofType) 15 | } 16 | if (isListType(gqlType)) { 17 | return unwrapType(gqlType.ofType) 18 | } 19 | return gqlType 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export type MayPromise = T | Promise 2 | 3 | export type IsAny = 0 extends 1 & T ? true : false 4 | 5 | export type ValueOf = T[keyof T] 6 | 7 | export type OmitInUnion = TUnion extends infer T 8 | ? T extends TOmit 9 | ? never 10 | : T 11 | : never 12 | 13 | export type RequireKeys = { 14 | [P in keyof T as P extends TKey ? P : never]-?: T[P] 15 | } & { 16 | [P in keyof T as P extends TKey ? never : P]: T[P] 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/test/middleware.spec.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLString, execute, parse } from "graphql" 2 | import { beforeAll, describe, expect, it } from "vitest" 3 | import { GraphQLSchemaLoom, type Middleware, loom, silk } from "../src" 4 | 5 | const { query, resolver } = loom 6 | 7 | describe("middleware integration", () => { 8 | const logs: string[] = [] 9 | const operationMiddleware: Middleware = async (next) => { 10 | logs.push("operation Start") 11 | const result = await next() 12 | logs.push("operation End") 13 | return result 14 | } 15 | const resolverMiddleware: Middleware = async (next) => { 16 | logs.push("resolver Start") 17 | const result = await next() 18 | logs.push("resolver End") 19 | return result 20 | } 21 | const globalMiddleware: Middleware = async (next) => { 22 | logs.push("global Start") 23 | const result = await next() 24 | logs.push("global End") 25 | return result 26 | } 27 | const simpleResolver = resolver({ 28 | hello: query(silk(GraphQLString), { 29 | resolve: () => "hello GQLoom", 30 | middlewares: [operationMiddleware], 31 | }), 32 | }) 33 | simpleResolver.use(resolverMiddleware) 34 | 35 | const schema = new GraphQLSchemaLoom() 36 | .use(globalMiddleware) 37 | .add(simpleResolver) 38 | .weaveGraphQLSchema() 39 | 40 | beforeAll(async () => { 41 | await execute({ 42 | schema, 43 | document: parse(/* GraphQL */ ` 44 | query { 45 | hello 46 | } 47 | `), 48 | }) 49 | }) 50 | 51 | it("should work in operation", async () => { 52 | expect(logs).toContain("operation Start") 53 | expect(logs).toContain("operation End") 54 | }) 55 | it("should work in resolver", async () => { 56 | expect(logs).toContain("resolver Start") 57 | expect(logs).toContain("resolver End") 58 | }) 59 | it("should work in global", async () => { 60 | expect(logs).toContain("global Start") 61 | expect(logs).toContain("global End") 62 | }) 63 | it("should work in order", async () => { 64 | expect(logs).toEqual([ 65 | "global Start", 66 | "resolver Start", 67 | "operation Start", 68 | "operation End", 69 | "resolver End", 70 | "global End", 71 | ]) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/tsup.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://cdn.jsdelivr.net/npm/tsup/schema.json", 3 | "entry": { 4 | "index": "./src/index.ts", 5 | "context": "./src/context/index.ts" 6 | }, 7 | "format": ["esm", "cjs"], 8 | "minify": false, 9 | "dts": true, 10 | "outDir": "./dist", 11 | "clean": true, 12 | "external": ["node:async_hooks"], 13 | "tsconfig": "../../tsconfig.tsup.json" 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { projectConfig } from "../../vitest.config" 2 | 3 | export default projectConfig 4 | -------------------------------------------------------------------------------- /packages/drizzle/.gitignore: -------------------------------------------------------------------------------- 1 | test/**/*.db* 2 | .env -------------------------------------------------------------------------------- /packages/drizzle/README.md: -------------------------------------------------------------------------------- 1 | ![GQLoom Logo](https://github.com/modevol-com/gqloom/blob/main/gqloom.svg?raw=true) 2 | 3 | # GQLoom 4 | 5 | GQLoom is a **Code-First** GraphQL Schema Loom used to weave **runtime types** in the **TypeScript/JavaScript** ecosystem into GraphQL Schema, helping you build GraphQL server enjoyably and efficiently. 6 | 7 | Runtime validation libraries such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), and [Yup](https://github.com/jquense/yup) have been widely used in backend application development. Meanwhile, when using ORM libraries like [Prisma](https://www.prisma.io/), [MikroORM](https://mikro-orm.io/), and [Drizzle](https://orm.drizzle.team/), we also pre-define database table structures or entity models that contain runtime types. 8 | The responsibility of GQLoom is to weave these runtime types into a GraphQL Schema. 9 | 10 | When developing backend applications with GQLoom, you only need to write types using the Schema libraries you're familiar with. Modern Schema libraries will infer TypeScript types for you, and GQLoom will weave GraphQL types for you. 11 | In addition, the **resolver factory** of GQLoom can create CRUD interfaces for `Prisma`, `MikroORM`, and `Drizzle`, and supports custom input and adding middleware. 12 | 13 | ## Drizzle 14 | 15 | [Drizzle](https://orm.drizzle.team/) is a modern, type-safe TypeScript ORM designed for Node.js. It offers a concise and easy-to-use API, supports databases such as PostgreSQL, MySQL, and SQLite, and has powerful query builders, transaction processing, and database migration capabilities. At the same time, it remains lightweight and has no external dependencies, making it very suitable for database operation scenarios that require high performance and type safety. 16 | 17 | ## @gqloom/drizzle 18 | 19 | This package provides GQLoom integration with [Drizzle](https://orm.drizzle.team/): 20 | 21 | - Weave database tables defined by Drizzle into a GraphQL Schema; 22 | - Support quickly creating CRUD interfaces from Drizzle using the resolver factory; 23 | 24 | ### Installation 25 | 26 | ```bash 27 | # use npm 28 | npm install @gqloom/core @gqloom/drizzle 29 | 30 | # use pnpm 31 | pnpm add @gqloom/core @gqloom/drizzle 32 | 33 | # use yarn 34 | yarn add @gqloom/core @gqloom/drizzle 35 | ``` 36 | 37 | Read more at [GQLoom Document](https://gqloom.dev/docs/schema/drizzle). 38 | -------------------------------------------------------------------------------- /packages/drizzle/drizzle-mysql.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit" 2 | import { config } from "./env.config" 3 | 4 | export default defineConfig({ 5 | dialect: "mysql", 6 | schema: "./test/schema/mysql.ts", 7 | dbCredentials: { 8 | url: config.mysqlUrl, 9 | }, 10 | tablesFilter: ["drizzle_user", "drizzle_post"], 11 | }) 12 | -------------------------------------------------------------------------------- /packages/drizzle/drizzle-postgres.config.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { defineConfig } from "drizzle-kit" 3 | import { config } from "./env.config" 4 | 5 | export default defineConfig({ 6 | dialect: "postgresql", 7 | schema: "./test/schema/postgres.ts", 8 | dbCredentials: { 9 | url: config.postgresUrl, 10 | }, 11 | tablesFilter: ["drizzle_user", "drizzle_post"], 12 | }) 13 | -------------------------------------------------------------------------------- /packages/drizzle/drizzle-sqlite-1.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit" 2 | 3 | export default defineConfig({ 4 | dialect: "sqlite", 5 | schema: "./test/schema/sqlite.ts", 6 | dbCredentials: { 7 | url: "file:./test/schema/sqlite-1.db", 8 | }, 9 | tablesFilter: [ 10 | "user", 11 | "post", 12 | "course", 13 | "studentToCourse", 14 | "studentCourseGrade", 15 | "studentCourseGrade", 16 | ], 17 | }) 18 | -------------------------------------------------------------------------------- /packages/drizzle/drizzle-sqlite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit" 2 | 3 | export default defineConfig({ 4 | dialect: "sqlite", 5 | schema: "./test/schema/sqlite.ts", 6 | dbCredentials: { 7 | url: "file:./test/schema/sqlite.db", 8 | }, 9 | tablesFilter: [ 10 | "user", 11 | "post", 12 | "course", 13 | "studentToCourse", 14 | "studentCourseGrade", 15 | ], 16 | }) 17 | -------------------------------------------------------------------------------- /packages/drizzle/env.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv" 2 | 3 | dotenv.config({ path: new URL("./.env", import.meta.url) }) 4 | 5 | export const config = { 6 | mysqlUrl: process.env.MYSQL_URL ?? "mysql://root@localhost:3306/mysql", 7 | postgresUrl: 8 | process.env.POSTGRESQL_URL ?? "postgres://postgres@localhost:5432/postgres", 9 | } 10 | -------------------------------------------------------------------------------- /packages/drizzle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gqloom/drizzle", 3 | "version": "0.9.4", 4 | "description": "GQLoom integration with Drizzle ORM", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | } 18 | }, 19 | "./context": { 20 | "import": { 21 | "types": "./dist/context.d.ts", 22 | "default": "./dist/context.js" 23 | }, 24 | "require": { 25 | "types": "./dist/context.d.cts", 26 | "default": "./dist/context.cjs" 27 | } 28 | } 29 | }, 30 | "scripts": { 31 | "build": "tsup", 32 | "check:type": "tsc --noEmit", 33 | "push": "drizzle-kit push --config=drizzle-mysql.config.ts && drizzle-kit push --config=drizzle-postgres.config.ts && drizzle-kit push --config=drizzle-sqlite.config.ts && drizzle-kit push --config=drizzle-sqlite-1.config.ts" 34 | }, 35 | "files": ["dist"], 36 | "keywords": [ 37 | "gqloom", 38 | "graphql", 39 | "schema", 40 | "typescript", 41 | "drizzle", 42 | "drizzle-orm" 43 | ], 44 | "author": "xcfox", 45 | "license": "MIT", 46 | "peerDependencies": { 47 | "@gqloom/core": ">= 0.9.0", 48 | "drizzle-orm": ">= 0.38.0", 49 | "graphql": ">= 16.8.0" 50 | }, 51 | "devDependencies": { 52 | "@gqloom/core": "workspace:*", 53 | "@gqloom/valibot": "workspace:*", 54 | "@libsql/client": "^0.14.0", 55 | "@types/pg": "^8.11.10", 56 | "dotenv": "^16.4.7", 57 | "drizzle-kit": "^0.30.1", 58 | "drizzle-orm": "^0.39.3", 59 | "graphql": "^16.8.1", 60 | "graphql-yoga": "^5.6.0", 61 | "mysql2": "^3.12.0", 62 | "pg": "^8.13.1", 63 | "tsx": "^4.7.2", 64 | "valibot": "1.0.0-beta.12" 65 | }, 66 | "homepage": "https://gqloom.dev/", 67 | "repository": { 68 | "type": "git", 69 | "url": "https://github.com/modevol-com/gqloom.git", 70 | "directory": "packages/drizzle" 71 | }, 72 | "publishConfig": { 73 | "access": "public" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/drizzle/src/context.ts: -------------------------------------------------------------------------------- 1 | import { useResolverPayload } from "@gqloom/core/context" 2 | import type { Table } from "drizzle-orm" 3 | import { getSelectedColumns } from "./helper" 4 | 5 | /** 6 | * use the selected columns from the resolver payload 7 | * @param table - The table to get the selected columns from 8 | * @returns The selected columns 9 | */ 10 | export function useSelectedColumns(table: TTable) { 11 | const payload = useResolverPayload() 12 | return getSelectedColumns(table, payload) 13 | } 14 | -------------------------------------------------------------------------------- /packages/drizzle/test/schema/mysql.ts: -------------------------------------------------------------------------------- 1 | import { relations } from "drizzle-orm" 2 | import * as t from "drizzle-orm/mysql-core" 3 | import { drizzleSilk } from "../../src" 4 | 5 | export const user = drizzleSilk( 6 | t.mysqlTable("drizzle_user", { 7 | id: t.int().primaryKey().autoincrement(), 8 | name: t.text().notNull(), 9 | age: t.int(), 10 | email: t.text(), 11 | }), 12 | { 13 | name: "User", 14 | description: "A user", 15 | fields: { 16 | name: { description: "The name of the user" }, 17 | }, 18 | } 19 | ) 20 | 21 | export const usersRelations = relations(user, ({ many }) => ({ 22 | posts: many(post), 23 | })) 24 | 25 | export const post = drizzleSilk( 26 | t.mysqlTable("drizzle_post", { 27 | id: t.int().primaryKey().autoincrement(), 28 | title: t.text().notNull(), 29 | content: t.text(), 30 | authorId: t.int().references(() => user.id, { onDelete: "cascade" }), 31 | }), 32 | { 33 | name: "Post", 34 | description: "A post", 35 | fields: { 36 | title: { description: "The title of the post" }, 37 | }, 38 | } 39 | ) 40 | 41 | export const postsRelations = relations(post, ({ one }) => ({ 42 | author: one(user, { 43 | fields: [post.authorId], 44 | references: [user.id], 45 | }), 46 | })) 47 | -------------------------------------------------------------------------------- /packages/drizzle/test/schema/postgres.ts: -------------------------------------------------------------------------------- 1 | import { relations } from "drizzle-orm" 2 | import * as t from "drizzle-orm/pg-core" 3 | import { drizzleSilk } from "../../src" 4 | 5 | export const user = drizzleSilk( 6 | t.pgTable("drizzle_user", { 7 | id: t.serial().primaryKey(), 8 | name: t.text().notNull(), 9 | age: t.integer(), 10 | email: t.text(), 11 | }), 12 | { 13 | name: "User", 14 | description: "A user", 15 | fields: { 16 | name: { description: "The name of the user" }, 17 | }, 18 | } 19 | ) 20 | export const usersRelations = relations(user, ({ many }) => ({ 21 | posts: many(post), 22 | })) 23 | 24 | export const post = drizzleSilk( 25 | t.pgTable("drizzle_post", { 26 | id: t.serial().primaryKey(), 27 | title: t.text().notNull(), 28 | content: t.text(), 29 | authorId: t.integer().references(() => user.id, { onDelete: "cascade" }), 30 | }), 31 | { 32 | name: "Post", 33 | description: "A post", 34 | fields: { 35 | title: { description: "The title of the post" }, 36 | }, 37 | } 38 | ) 39 | export const postsRelations = relations(post, ({ one }) => ({ 40 | author: one(user, { 41 | fields: [post.authorId], 42 | references: [user.id], 43 | }), 44 | })) 45 | -------------------------------------------------------------------------------- /packages/drizzle/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "bundler" 5 | }, 6 | "include": ["src", "test"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/drizzle/tsup.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": { 3 | "index": "./src/index.ts", 4 | "context": "./src/context.ts" 5 | }, 6 | "format": ["esm", "cjs"], 7 | "minify": false, 8 | "dts": true, 9 | "outDir": "./dist", 10 | "clean": true, 11 | "tsconfig": "../../tsconfig.tsup.json" 12 | } 13 | -------------------------------------------------------------------------------- /packages/drizzle/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { projectConfig } from "../../vitest.config" 2 | 3 | export default projectConfig 4 | -------------------------------------------------------------------------------- /packages/federation/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## next (YYYY-MM-DD) 6 | 7 | ## 0.8.0 (2025-03-11) 8 | 9 | - Feat: chaining resolver factory 10 | 11 | ## 0.7.1 (2025-02-16) 12 | 13 | - Fix: update document link in README 14 | 15 | ## 0.7.0 16 | 17 | - Refactor: rename `FederatedSchemaWeaver` to `FederatedSchemaLoom` 18 | 19 | ## 0.4.0 (2024-11-12) 20 | 21 | - Refactor: follow standard-schema ([#7](https://github.com/modevol-com/gqloom/pull/7)) 22 | -------------------------------------------------------------------------------- /packages/federation/README.md: -------------------------------------------------------------------------------- 1 | ![GQLoom Logo](https://github.com/modevol-com/gqloom/blob/main/gqloom.svg?raw=true) 2 | 3 | # GQLoom 4 | 5 | GQLoom is a **Code-First** GraphQL Schema Loom used to weave **runtime types** in the **TypeScript/JavaScript** ecosystem into GraphQL Schema, helping you build GraphQL server enjoyably and efficiently. 6 | 7 | Runtime validation libraries such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), and [Yup](https://github.com/jquense/yup) have been widely used in backend application development. Meanwhile, when using ORM libraries like [Prisma](https://www.prisma.io/), [MikroORM](https://mikro-orm.io/), and [Drizzle](https://orm.drizzle.team/), we also pre-define database table structures or entity models that contain runtime types. 8 | The responsibility of GQLoom is to weave these runtime types into a GraphQL Schema. 9 | 10 | When developing backend applications with GQLoom, you only need to write types using the Schema libraries you're familiar with. Modern Schema libraries will infer TypeScript types for you, and GQLoom will weave GraphQL types for you. 11 | In addition, the **resolver factory** of GQLoom can create CRUD interfaces for `Prisma`, `MikroORM`, and `Drizzle`, and supports custom input and adding middleware. 12 | 13 | # @gqloom/federation 14 | 15 | This package provides GQLoom support for [Apollo Federation](https://www.apollographql.com/docs/federation). 16 | 17 | ## Installation 18 | 19 | ```bash 20 | # use npm 21 | npm i graphql @gqloom/core @apollo/subgraph @gqloom/federation 22 | 23 | # use pnpm 24 | pnpm add graphql @gqloom/core @apollo/subgraph @gqloom/federation 25 | 26 | # use yarn 27 | yarn add graphql @gqloom/core @apollo/subgraph @gqloom/federation 28 | ``` 29 | 30 | Read more at [GQLoom Document](https://gqloom.dev/docs/advanced/federation). 31 | -------------------------------------------------------------------------------- /packages/federation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gqloom/federation", 3 | "version": "0.9.2", 4 | "description": "Building subGraphs with GQLoom", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | } 18 | } 19 | }, 20 | "scripts": { 21 | "build": "tsup", 22 | "check:type": "tsc --noEmit" 23 | }, 24 | "files": ["dist"], 25 | "peerDependencies": { 26 | "@apollo/subgraph": ">= 2.0.0", 27 | "@gqloom/core": ">= 0.9.1", 28 | "graphql": ">= 16.8.0" 29 | }, 30 | "devDependencies": { 31 | "@apollo/server": "^4.10.4", 32 | "@apollo/subgraph": "^2.8.2", 33 | "@gqloom/core": "workspace:*", 34 | "@mercuriusjs/federation": "^3.0.0", 35 | "fastify": "^4.28.1", 36 | "graphql": "^16.8.1", 37 | "graphql-tag": "^2.12.6", 38 | "graphql-yoga": "^5.6.0", 39 | "mercurius": "^14.1.0", 40 | "mercurius-integration-testing": "^9.0.0" 41 | }, 42 | "keywords": [ 43 | "gqloom", 44 | "graphql", 45 | "schema", 46 | "typescript", 47 | "apollo", 48 | "federation", 49 | "subgraph" 50 | ], 51 | "author": "xcfox", 52 | "license": "MIT", 53 | "homepage": "https://gqloom.dev/", 54 | "repository": { 55 | "type": "git", 56 | "url": "https://github.com/modevol-com/gqloom.git", 57 | "directory": "packages/federation" 58 | }, 59 | "publishConfig": { 60 | "access": "public" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/federation/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { MayPromise, ResolverPayload } from "@gqloom/core" 2 | import type { GraphQLResolveInfo } from "graphql" 3 | 4 | export interface ResolveReferenceExtension< 5 | TEntity extends object, 6 | TRequiredKey extends keyof TEntity, 7 | > { 8 | apollo: { 9 | subgraph: { 10 | resolveReference: ResolveReference 11 | } 12 | } 13 | } 14 | 15 | type ResolveReference< 16 | TEntity extends object, 17 | TRequiredKey extends keyof TEntity, 18 | > = ( 19 | parent: Pick, 20 | context: object, 21 | info: GraphQLResolveInfo 22 | ) => MayPromise 23 | 24 | export function resolveReference< 25 | TEntity extends object, 26 | TRequiredKey extends keyof TEntity, 27 | >( 28 | resolve: ( 29 | source: Pick, 30 | payload: Pick 31 | ) => MayPromise 32 | ): ResolveReferenceExtension { 33 | return { 34 | apollo: { 35 | subgraph: { 36 | resolveReference: (root, context, info) => 37 | resolve(root, { root, context, info }), 38 | }, 39 | }, 40 | } 41 | } 42 | 43 | export * from "./resolver" 44 | export * from "./schema-loom" 45 | -------------------------------------------------------------------------------- /packages/federation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/federation/tsup.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": ["./src/index.ts"], 3 | "format": ["esm", "cjs"], 4 | "minify": false, 5 | "dts": true, 6 | "outDir": "./dist", 7 | "clean": true, 8 | "tsconfig": "../../tsconfig.tsup.json" 9 | } 10 | -------------------------------------------------------------------------------- /packages/federation/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path" 2 | import { defineProject, mergeConfig } from "vitest/config" 3 | import { projectConfig } from "../../vitest.config" 4 | 5 | export default mergeConfig( 6 | projectConfig, 7 | defineProject({ 8 | resolve: { 9 | alias: { 10 | graphql: path.resolve(__dirname, "./node_modules/graphql"), 11 | }, 12 | }, 13 | }) 14 | ) 15 | -------------------------------------------------------------------------------- /packages/mikro-orm/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## next (YYYY-MM-DD) 6 | 7 | ## 0.8.0 (2025-03-11) 8 | 9 | ## 0.7.1 (2025-02-16) 10 | 11 | * Fix: update document link in README 12 | 13 | ## 0.7.0 (2025-02-05) 14 | 15 | * Refactor: rename `MikroOperationBobbin` to `MikroResolverFactory` and split input type generation into `MikroInputFactory` 16 | 17 | ## 0.5.0 (2024-12-03) 18 | 19 | * Chore: update `@standard-schema/spec` to 1.0.0-beta.4 20 | 21 | ## 0.3.0 (2024-10-19) 22 | 23 | * Refactor: follow standard-schema ([#7](https://github.com/modevol-com/gqloom/pull/7)) 24 | -------------------------------------------------------------------------------- /packages/mikro-orm/README.md: -------------------------------------------------------------------------------- 1 | ![GQLoom Logo](https://github.com/modevol-com/gqloom/blob/main/gqloom.svg?raw=true) 2 | 3 | # GQLoom 4 | 5 | GQLoom is a **Code-First** GraphQL Schema Loom used to weave **runtime types** in the **TypeScript/JavaScript** ecosystem into GraphQL Schema, helping you build GraphQL server enjoyably and efficiently. 6 | 7 | Runtime validation libraries such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), and [Yup](https://github.com/jquense/yup) have been widely used in backend application development. Meanwhile, when using ORM libraries like [Prisma](https://www.prisma.io/), [MikroORM](https://mikro-orm.io/), and [Drizzle](https://orm.drizzle.team/), we also pre-define database table structures or entity models that contain runtime types. 8 | The responsibility of GQLoom is to weave these runtime types into a GraphQL Schema. 9 | 10 | When developing backend applications with GQLoom, you only need to write types using the Schema libraries you're familiar with. Modern Schema libraries will infer TypeScript types for you, and GQLoom will weave GraphQL types for you. 11 | In addition, the **resolver factory** of GQLoom can create CRUD interfaces for `Prisma`, `MikroORM`, and `Drizzle`, and supports custom input and adding middleware. 12 | 13 | # @gqloom/mikro-orm 14 | 15 | This package provides GQLoom integration with [Mikro ORM](https://mikro-orm.io/): 16 | 17 | - Use MikroORM's Entity Schema as the silk; 18 | - Weaving the silk into MikroORM's Entity Schema; 19 | - Generating GraphQL operations from MikroORM's Entity Schema; 20 | 21 | ## Installation 22 | 23 | ```bash 24 | # use npm 25 | npm i @gqloom/core @gqloom/mikro-orm @mikro-orm/core 26 | 27 | # use pnpm 28 | pnpm add @gqloom/core @gqloom/mikro-orm @mikro-orm/core 29 | 30 | # use yarn 31 | yarn add @gqloom/core @gqloom/mikro-orm @mikro-orm/core 32 | ``` 33 | 34 | Read more at [GQLoom Document](https://gqloom.dev/docs/schema/mikro-orm). 35 | -------------------------------------------------------------------------------- /packages/mikro-orm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gqloom/mikro-orm", 3 | "version": "0.9.0", 4 | "description": "GQLoom integration with Mikro ORM", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | } 18 | } 19 | }, 20 | "scripts": { 21 | "build": "tsup", 22 | "check:type": "tsc --noEmit" 23 | }, 24 | "files": ["dist"], 25 | "keywords": ["gqloom", "graphql", "schema", "typescript", "mikro-orm"], 26 | "author": "xcfox", 27 | "license": "MIT", 28 | "peerDependencies": { 29 | "@gqloom/core": ">= 0.9.0", 30 | "@mikro-orm/core": ">= 6.0.0", 31 | "graphql": ">= 16.8.0" 32 | }, 33 | "devDependencies": { 34 | "@gqloom/core": "workspace:*", 35 | "@mikro-orm/core": "^6.4.6", 36 | "@mikro-orm/libsql": "^6.4.6" 37 | }, 38 | "homepage": "https://gqloom.dev/", 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/modevol-com/gqloom.git", 42 | "directory": "packages/mikro-orm" 43 | }, 44 | "publishConfig": { 45 | "access": "public" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/mikro-orm/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { WeaverConfig } from "@gqloom/core" 2 | // biome-ignore lint/correctness/noUnusedImports: SYMBOLS used in type 3 | import type { SYMBOLS } from "@gqloom/core" 4 | import type { 5 | EntityProperty, 6 | EntitySchema, 7 | PropertyOptions, 8 | } from "@mikro-orm/core" 9 | import type { GraphQLOutputType } from "graphql" 10 | 11 | export interface GQLoomMikroFieldExtensions { 12 | mikroProperty?: PropertyOptions 13 | } 14 | 15 | export type InferEntity> = 16 | TSchema extends EntitySchema ? TEntity : never 17 | 18 | export interface MikroWeaverConfigOptions { 19 | presetGraphQLType?: ( 20 | property: EntityProperty 21 | ) => GraphQLOutputType | undefined 22 | } 23 | 24 | export interface MikroWeaverConfig 25 | extends WeaverConfig, 26 | MikroWeaverConfigOptions { 27 | [SYMBOLS.WEAVER_CONFIG]: "gqloom.mikro-orm" 28 | } 29 | -------------------------------------------------------------------------------- /packages/mikro-orm/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { EntitySchema } from "@mikro-orm/core" 2 | import { 3 | GraphQLList, 4 | GraphQLNonNull, 5 | type GraphQLObjectType, 6 | type GraphQLOutputType, 7 | } from "graphql" 8 | 9 | /** 10 | * Store origin GraphQLType for EntitySchema 11 | */ 12 | export const EntityGraphQLTypes = new WeakMap() 13 | 14 | export type CapitalizeFirstLetter = 15 | TString extends `${infer TFirst}${infer TRest}` 16 | ? `${Uppercase}${TRest}` 17 | : TString 18 | 19 | export type LowercaseFirstLetter = 20 | TString extends `${infer TFirst}${infer TRest}` 21 | ? `${Lowercase}${TRest}` 22 | : TString 23 | 24 | export function unwrapGraphQLType( 25 | gqlType: GraphQLOutputType 26 | ): Exclude< 27 | GraphQLOutputType, 28 | GraphQLList | GraphQLNonNull 29 | > { 30 | if (gqlType instanceof GraphQLNonNull) { 31 | return unwrapGraphQLType(gqlType.ofType) 32 | } 33 | if (gqlType instanceof GraphQLList) { 34 | return unwrapGraphQLType(gqlType.ofType) 35 | } 36 | return gqlType 37 | } 38 | -------------------------------------------------------------------------------- /packages/mikro-orm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/mikro-orm/tsup.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": ["./src/index.ts"], 3 | "format": ["esm", "cjs"], 4 | "minify": false, 5 | "dts": true, 6 | "outDir": "./dist", 7 | "clean": true, 8 | "tsconfig": "../../tsconfig.tsup.json" 9 | } 10 | -------------------------------------------------------------------------------- /packages/mikro-orm/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { projectConfig } from "../../vitest.config" 2 | 3 | export default projectConfig 4 | -------------------------------------------------------------------------------- /packages/prisma/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | 5 | ./dev.db 6 | /prisma/dev.db* 7 | /prisma/migrations 8 | /generated 9 | /test/client 10 | /test/generated -------------------------------------------------------------------------------- /packages/prisma/.graphqlrc.yml: -------------------------------------------------------------------------------- 1 | schema: "./test/bobbin-resolver.spec.gql" 2 | documents: "test/**/*.{graphql,js,ts,jsx,tsx}" 3 | -------------------------------------------------------------------------------- /packages/prisma/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## next (YYYY-MM-DD) 6 | 7 | ## 0.9.1 (2025-05-13) 8 | 9 | - Feat: add `useSelectedFields` to get the selected fields from the resolver payload 10 | 11 | ## 0.9.0 (2025-05-11) 12 | 13 | - Refactor: use `prisma.delegate.findMany` in `PrismaResolverFactory.relationField` for better performance 14 | - Feat: add `getSelectedFields` to get the selected fields from the resolver payload 15 | - Feat: add `PrismaResolverFactory.queriesResolver` to create a read-only resolver 16 | 17 | ## 0.8.0 (2025-03-11) 18 | 19 | - Feat: Use the chain method to add custom inputs and middleware to `PrismaResolverFactory` 20 | 21 | ## 0.7.2 (2025-02-16) 22 | 23 | - Fix: update document link in README 24 | 25 | ## 0.7.1 (2025-02-12) 26 | 27 | - Fix: `package.json` remove `postinstall` 28 | 29 | ## 0.7.0 (2025-01-28) 30 | 31 | - Refactor: rename `PrismaModelBobbin` to `PrismaResolverFactory` 32 | 33 | ## 0.5.0 (2024-12-03) 34 | 35 | - Chore: update `@standard-schema/spec` to 1.0.0-beta.4 36 | 37 | ## 0.2.0 (2024-11-20) 38 | 39 | - Refactor: follow standard-schema ([#7](https://github.com/modevol-com/gqloom/pull/7)) 40 | 41 | ## [0.1.3](https://github.com/modevol-com/gqloom/compare/@gqloom/prisma@0.1.2...@gqloom/prisma@0.1.3) (2024-10-19) 42 | 43 | ### Features 44 | 45 | - Add `PrismaWeaver.config` to define config 46 | 47 | ## [0.1.2](https://github.com/modevol-com/gqloom/compare/@gqloom/prisma@0.1.1...@gqloom/prisma@0.1.2) (2024-10-19) 48 | 49 | ## [0.1.1](https://github.com/modevol-com/gqloom/compare/@gqloom/prisma@0.1.0...@gqloom/prisma@0.1.1) (2024-10-19) 50 | 51 | ## 0.1.0 (2024-10-19) 52 | -------------------------------------------------------------------------------- /packages/prisma/README.md: -------------------------------------------------------------------------------- 1 | ![GQLoom Logo](https://github.com/modevol-com/gqloom/blob/main/gqloom.svg?raw=true) 2 | 3 | # GQLoom 4 | 5 | GQLoom is a **Code-First** GraphQL Schema Loom used to weave **runtime types** in the **TypeScript/JavaScript** ecosystem into GraphQL Schema, helping you build GraphQL server enjoyably and efficiently. 6 | 7 | Runtime validation libraries such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), and [Yup](https://github.com/jquense/yup) have been widely used in backend application development. Meanwhile, when using ORM libraries like [Prisma](https://www.prisma.io/), [MikroORM](https://mikro-orm.io/), and [Drizzle](https://orm.drizzle.team/), we also pre-define database table structures or entity models that contain runtime types. 8 | The responsibility of GQLoom is to weave these runtime types into a GraphQL Schema. 9 | 10 | When developing backend applications with GQLoom, you only need to write types using the Schema libraries you're familiar with. Modern Schema libraries will infer TypeScript types for you, and GQLoom will weave GraphQL types for you. 11 | In addition, the **resolver factory** of GQLoom can create CRUD interfaces for `Prisma`, `MikroORM`, and `Drizzle`, and supports custom input and adding middleware. 12 | 13 | ## Prisma 14 | 15 | [Prisma](https://www.prisma.io/) is a next-generation Node.js and TypeScript ORM that unlocks a new level of developer experience when working with databases thanks to its intuitive data model, automated migrations, type-safety & auto-completion. 16 | 17 | ## @gqloom/prisma 18 | 19 | This package provides GQLoom integration with [Prisma](https://www.prisma.io/): 20 | 21 | - Weave Prisma model into a GraphQL Schema; 22 | - Support quickly creating CRUD interfaces from Prisma using the resolver factory; 23 | 24 | ## Installation 25 | 26 | ```bash 27 | # use npm 28 | npm i @gqloom/core @gqloom/prisma 29 | 30 | # use pnpm 31 | pnpm add @gqloom/core @gqloom/prisma 32 | 33 | # use yarn 34 | yarn add @gqloom/core @gqloom/prisma 35 | ``` 36 | Read more at [GQLoom Document](https://gqloom.dev/docs/schema/prisma). 37 | -------------------------------------------------------------------------------- /packages/prisma/bin/generator.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable @typescript-eslint/no-require-imports */ 3 | require("../dist/generator.cjs") 4 | -------------------------------------------------------------------------------- /packages/prisma/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gqloom/prisma", 3 | "version": "0.9.1", 4 | "description": "GQLoom integration with Prisma", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | } 18 | }, 19 | "./context": { 20 | "import": { 21 | "types": "./dist/context.d.ts", 22 | "default": "./dist/context.js" 23 | }, 24 | "require": { 25 | "types": "./dist/context.d.cts", 26 | "default": "./dist/context.cjs" 27 | } 28 | }, 29 | "./generated": { 30 | "import": { 31 | "types": "./generated/index.d.ts", 32 | "default": "./generated/index.js" 33 | }, 34 | "require": { 35 | "types": "./generated/index.d.ts", 36 | "default": "./generated/index.cjs" 37 | } 38 | } 39 | }, 40 | "scripts": { 41 | "build": "tsup", 42 | "check:type": "tsc --noEmit", 43 | "init": "exec prisma migrate dev --name init", 44 | "generate": "pnpm build && prisma generate", 45 | "push": "pnpm build && pnpm generate && pnpm run init" 46 | }, 47 | "bin": { 48 | "prisma-gqloom": "./bin/generator.cjs" 49 | }, 50 | "files": ["dist"], 51 | "keywords": ["gqloom", "graphql", "schema", "typescript", "prisma"], 52 | "author": "xcfox", 53 | "license": "MIT", 54 | "peerDependencies": { 55 | "@gqloom/core": ">= 0.9.0", 56 | "graphql": ">= 16.8.0", 57 | "prisma": ">= 5.0.0" 58 | }, 59 | "devDependencies": { 60 | "@gqloom/core": "workspace:*", 61 | "@gqloom/zod": "workspace:*", 62 | "@prisma/client": "^6.1.0", 63 | "graphql-yoga": "^5.6.0", 64 | "prisma": "^6.1.0", 65 | "zod": "4.0.0-beta.20250415T232143" 66 | }, 67 | "homepage": "https://gqloom.dev/", 68 | "repository": { 69 | "type": "git", 70 | "url": "https://github.com/modevol-com/gqloom.git", 71 | "directory": "packages/prisma" 72 | }, 73 | "publishConfig": { 74 | "access": "public" 75 | }, 76 | "dependencies": { 77 | "@prisma/generator-helper": "^6.1.0", 78 | "ts-morph": "^25.0.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/prisma/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | output = "../test/client" 7 | } 8 | 9 | generator gqloom { 10 | provider = "node ./bin/generator.cjs" 11 | output = "../test/generated" 12 | commonjsFile = "" 13 | moduleFile = "index.js" 14 | typesFiles = ["index.d.ts"] 15 | gqloomPath = "../../dist" 16 | } 17 | 18 | datasource db { 19 | provider = "sqlite" 20 | url = "file:./dev.db" 21 | } 22 | 23 | model User { 24 | id Int @id @default(autoincrement()) 25 | email String @unique 26 | name String? 27 | posts Post[] 28 | publishedPosts Post[] @relation("UserToPublishedPost") 29 | profile Profile? 30 | } 31 | 32 | model Profile { 33 | id Int @id @default(autoincrement()) 34 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 35 | userId Int @unique // relation scalar field (used in the `@relation` attribute above) 36 | introduction String 37 | } 38 | 39 | model Post { 40 | id Int @id @default(autoincrement()) 41 | title String 42 | content String? 43 | published Boolean @default(false) 44 | 45 | author User @relation(fields: authorId, references: id, onDelete: Cascade) 46 | authorId Int 47 | 48 | publisher User? @relation("UserToPublishedPost", fields: publisherId, references: id) 49 | publisherId Int? @map("publishedById") 50 | 51 | categories Category[] 52 | } 53 | 54 | model Category { 55 | id Int @id @default(autoincrement()) 56 | posts Post[] 57 | } 58 | 59 | model Dog { 60 | firstName String 61 | lastName String 62 | 63 | height Float 64 | weight Int 65 | 66 | birthDate DateTime 67 | 68 | sheeps Sheep[] 69 | 70 | @@id(name: "fullName", fields: [firstName, lastName]) 71 | } 72 | 73 | model Sheep { 74 | firstCode String 75 | lastCode String 76 | 77 | shepherdFirstName String 78 | shepherdLastName String 79 | shepherd Dog @relation(fields: [shepherdFirstName, shepherdLastName], references: [firstName, lastName]) 80 | 81 | @@id([firstCode, lastCode]) 82 | } 83 | 84 | model KeyValue { 85 | id String @id 86 | value String 87 | } 88 | -------------------------------------------------------------------------------- /packages/prisma/src/context.ts: -------------------------------------------------------------------------------- 1 | import { useResolverPayload } from "@gqloom/core/context" 2 | import type { DMMF } from "@prisma/generator-helper" 3 | import type { PrismaModelSilk, SelectedModelFields } from "./types" 4 | import { getSelectedFields } from "./utils" 5 | 6 | /** 7 | * Get the selected columns from the resolver payload 8 | * @param silk - The silk to get the selected columns from 9 | * @returns The selected columns 10 | */ 11 | export function useSelectedFields< 12 | TSilk extends PrismaModelSilk>, 13 | >(silk: TSilk): SelectedModelFields /** 14 | * Get the selected columns from the resolver payload 15 | * @param silk - The silk to get the selected columns from 16 | * @param payload - The resolver payload 17 | * @returns The selected columns 18 | */ 19 | export function useSelectedFields(model: DMMF.Model): Record 20 | /** 21 | * Get the selected columns from the resolver payload 22 | * @param silk - The silk to get the selected columns from 23 | * @param payload - The resolver payload 24 | * @returns The selected columns 25 | */ 26 | export function useSelectedFields( 27 | silkOrModel: 28 | | PrismaModelSilk> 29 | | DMMF.Model 30 | ): Record { 31 | return getSelectedFields(silkOrModel as any, useResolverPayload()) 32 | } 33 | -------------------------------------------------------------------------------- /packages/prisma/src/generator/js.ts: -------------------------------------------------------------------------------- 1 | import type { DMMF } from "@prisma/generator-helper" 2 | import type { GQLoomGeneratorConfig } from "." 3 | 4 | export function genJSFile( 5 | dmmf: DMMF.Document, 6 | config: { outputFile: string; esm?: boolean } & GQLoomGeneratorConfig 7 | ): string { 8 | const lines: string[] = [] 9 | 10 | if (config.esm) { 11 | lines.push( 12 | `import { PrismaWeaver } from "${config.gqloomPath ?? "@gqloom/prisma"}"` 13 | ) 14 | } else { 15 | lines.push( 16 | `const { PrismaWeaver } = require("${config.gqloomPath ?? "@gqloom/prisma"}")` 17 | ) 18 | } 19 | 20 | if (config.esm) { 21 | lines.push(`import mm from "./model-meta.json"`) 22 | } else { 23 | lines.push(`const mm = require("./model-meta.json")`) 24 | } 25 | lines.push("") 26 | for (const model of dmmf.datamodel.models) { 27 | lines.push( 28 | `const ${model.name} = PrismaWeaver.unravel(mm.models.${model.name}, mm)` 29 | ) 30 | } 31 | 32 | lines.push("") 33 | 34 | for (const enumType of dmmf.datamodel.enums) { 35 | lines.push( 36 | `const ${enumType.name} = PrismaWeaver.unravelEnum(mm.enums.${enumType.name})` 37 | ) 38 | } 39 | 40 | // export silks 41 | lines.push("") 42 | if (config.esm) lines.push("export {") 43 | else lines.push("module.exports = {") 44 | 45 | for (const model of dmmf.datamodel.models) { 46 | lines.push(` ${model.name},`) 47 | } 48 | for (const enumType of dmmf.datamodel.enums) { 49 | lines.push(` ${enumType.name},`) 50 | } 51 | lines.push("}") 52 | 53 | return lines.join("\n") 54 | } 55 | -------------------------------------------------------------------------------- /packages/prisma/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/prisma/tsup.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": { 3 | "index": "./src/index.ts", 4 | "generator": "./src/generator/index.ts", 5 | "context": "./src/context.ts" 6 | }, 7 | "format": ["esm", "cjs"], 8 | "minify": false, 9 | "dts": true, 10 | "outDir": "./dist", 11 | "clean": true, 12 | "tsconfig": "../../tsconfig.tsup.json" 13 | } 14 | -------------------------------------------------------------------------------- /packages/prisma/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { projectConfig } from "../../vitest.config" 2 | 3 | export default projectConfig 4 | -------------------------------------------------------------------------------- /packages/valibot/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## next (YYYY-MM-DD) 6 | 7 | ## 0.8.1 (2025-03-23) 8 | 9 | - Feat: export all types 10 | 11 | ## 0.8.0 (2025-03-11) 12 | 13 | ## 0.7.1 (2025-02-16) 14 | 15 | * Fix: update document link in README 16 | 17 | ## 0.7.0 (2025-02-05) 18 | 19 | * Feature: auto assign names to objects and inputs 20 | * Fix: get config in nested field 21 | 22 | ## 0.5.0 (2024-12-03) 23 | 24 | * Chore: update `@standard-schema/spec` to 1.0.0-beta.4 25 | * Chore: re-export `@gqloom/core` 26 | 27 | ## \[0.4.0] (2024-11-20) 28 | 29 | * Refactor: follow standard-schema ([#7](https://github.com/modevol-com/gqloom/pull/7)) 30 | 31 | ## [0.3.2](https://github.com/modevol-com/gqloom/compare/@gqloom/valibot@0.3.1...@gqloom/valibot@0.3.2) (2024-10-19) 32 | 33 | ### Bug Fixes 34 | 35 | * fix type of `valibotSilk.input` 36 | 37 | ## [0.3.1](https://github.com/modevol-com/gqloom/compare/@gqloom/valibot@0.3.0...@gqloom/valibot@0.3.1) (2024-10-19) 38 | 39 | ### Features 40 | 41 | * valibotSilk.input 42 | 43 | ## 0.3.0 (2024-10-19) 44 | -------------------------------------------------------------------------------- /packages/valibot/README.md: -------------------------------------------------------------------------------- 1 | ![GQLoom Logo](https://github.com/modevol-com/gqloom/blob/main/gqloom.svg?raw=true) 2 | 3 | # GQLoom 4 | 5 | GQLoom is a **Code-First** GraphQL Schema Loom used to weave **runtime types** in the **TypeScript/JavaScript** ecosystem into GraphQL Schema, helping you build GraphQL server enjoyably and efficiently. 6 | 7 | Runtime validation libraries such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), and [Yup](https://github.com/jquense/yup) have been widely used in backend application development. Meanwhile, when using ORM libraries like [Prisma](https://www.prisma.io/), [MikroORM](https://mikro-orm.io/), and [Drizzle](https://orm.drizzle.team/), we also pre-define database table structures or entity models that contain runtime types. 8 | The responsibility of GQLoom is to weave these runtime types into a GraphQL Schema. 9 | 10 | When developing backend applications with GQLoom, you only need to write types using the Schema libraries you're familiar with. Modern Schema libraries will infer TypeScript types for you, and GQLoom will weave GraphQL types for you. 11 | In addition, the **resolver factory** of GQLoom can create CRUD interfaces for `Prisma`, `MikroORM`, and `Drizzle`, and supports custom input and adding middleware. 12 | 13 | # @gqloom/valibot 14 | 15 | This package provides GQLoom integration with [Valibot](https://valibot.dev/) to weave Valibot Schema to GraphQL Schema. 16 | 17 | ## Installation 18 | 19 | ```bash 20 | # use npm 21 | npm i @gqloom/core valibot @gqloom/valibot 22 | 23 | # use pnpm 24 | pnpm add @gqloom/core valibot @gqloom/valibot 25 | 26 | # use yarn 27 | yarn add @gqloom/core valibot @gqloom/valibot 28 | ``` 29 | 30 | 31 | ## Hello World 32 | 33 | ```ts 34 | import { resolver, query, weave } from "@gqloom/core" 35 | import { ValibotWeaver } from "@gqloom/valibot" 36 | import * as v from "valibot" 37 | 38 | const helloResolver = resolver({ 39 | hello: query(v.string()) 40 | .input({ name: v.nullish(v.string(), "World") }) 41 | .resolve(({ name }) => `Hello, ${name}!`), 42 | }) 43 | 44 | export const schema = weave(ValibotWeaver, helloResolver) 45 | ``` 46 | 47 | Read more at [GQLoom Document](https://gqloom.dev/docs/schema/valibot). 48 | -------------------------------------------------------------------------------- /packages/valibot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gqloom/valibot", 3 | "version": "0.9.1", 4 | "description": "Create GraphQL schema and resolvers easily using Valibot!", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | } 18 | } 19 | }, 20 | "scripts": { 21 | "build": "tsup", 22 | "check:type": "tsc --noEmit" 23 | }, 24 | "files": ["dist"], 25 | "keywords": [ 26 | "gqloom", 27 | "graphql", 28 | "schema", 29 | "typescript", 30 | "valibot", 31 | "validation", 32 | "validate" 33 | ], 34 | "author": "xcfox", 35 | "license": "MIT", 36 | "peerDependencies": { 37 | "@gqloom/core": ">= 0.9.0", 38 | "graphql": ">= 16.8.0", 39 | "valibot": ">= 1.0.0" 40 | }, 41 | "devDependencies": { 42 | "@gqloom/core": "workspace:*", 43 | "valibot": "1.0.0-beta.14" 44 | }, 45 | "homepage": "https://gqloom.dev/", 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/modevol-com/gqloom.git", 49 | "directory": "packages/valibot" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/valibot/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | NullableSchema, 3 | NullishSchema, 4 | OptionalSchema, 5 | VariantOptions, 6 | VariantOptionsAsync, 7 | VariantSchema, 8 | VariantSchemaAsync, 9 | } from "valibot" 10 | import type { PipedSchema } from "./types" 11 | 12 | export const nullishTypes: Set = new Set< 13 | ( 14 | | NullableSchema 15 | | NullishSchema 16 | | OptionalSchema 17 | )["type"] 18 | >(["nullable", "nullish", "optional"]) 19 | 20 | export function isNullish( 21 | schema: PipedSchema 22 | ): schema is 23 | | NullableSchema 24 | | NullishSchema 25 | | OptionalSchema { 26 | return nullishTypes.has(schema.type) 27 | } 28 | 29 | type FlatVariantSchema = Exclude< 30 | VariantOptionsAsync[number], 31 | { type: "variant" } 32 | > 33 | 34 | export function flatVariant( 35 | schema: 36 | | VariantSchema, any> 37 | | VariantSchemaAsync, any>, 38 | flatten: FlatVariantSchema[] = [] 39 | ): FlatVariantSchema[] { 40 | for (const item of schema.options) { 41 | if (item.type === "variant") { 42 | flatVariant( 43 | item as 44 | | VariantSchema, any> 45 | | VariantSchemaAsync, any>, 46 | flatten 47 | ) 48 | } else { 49 | flatten.push(item) 50 | } 51 | } 52 | return flatten 53 | } 54 | -------------------------------------------------------------------------------- /packages/valibot/test/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import * as v from "valibot" 2 | import { describe, expect, it } from "vitest" 3 | import { flatVariant } from "../src/utils" 4 | 5 | describe("flatVariant", () => { 6 | const VariantSchema = v.variant("type", [ 7 | v.object({ 8 | type: v.literal("email"), 9 | email: v.pipe(v.string(), v.email()), 10 | }), 11 | v.object({ 12 | type: v.literal("url"), 13 | url: v.pipe(v.string(), v.url()), 14 | }), 15 | v.object({ 16 | type: v.literal("date"), 17 | date: v.pipe(v.string(), v.isoDate()), 18 | }), 19 | ]) 20 | const NestedVariantSchema = v.variant("type", [ 21 | VariantSchema, 22 | v.object({ 23 | type: v.literal("color"), 24 | date: v.pipe(v.string(), v.hexColor()), 25 | }), 26 | ]) 27 | 28 | it("should flat Variant schema", () => { 29 | const schemas = flatVariant(VariantSchema) 30 | const variantList: string[] = [] 31 | for (const schema of schemas) { 32 | expect(schema.type).toEqual("object") 33 | expect(schema.entries["type"].type).toEqual("literal") 34 | variantList.push( 35 | (schema.entries["type"] as v.LiteralSchema).literal 36 | ) 37 | } 38 | 39 | expect(variantList).toEqual(["email", "url", "date"]) 40 | }) 41 | 42 | it("should flat Nested Variant schema", () => { 43 | const schemas = flatVariant(NestedVariantSchema) 44 | const variantList: string[] = [] 45 | for (const schema of schemas) { 46 | expect(schema.type).toEqual("object") 47 | expect(schema.entries["type"].type).toEqual("literal") 48 | variantList.push( 49 | (schema.entries["type"] as v.LiteralSchema).literal 50 | ) 51 | } 52 | 53 | expect(variantList).toEqual(["email", "url", "date", "color"]) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /packages/valibot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/valibot/tsup.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": ["./src/index.ts"], 3 | "format": ["esm", "cjs"], 4 | "minify": false, 5 | "dts": true, 6 | "outDir": "./dist", 7 | "clean": true, 8 | "tsconfig": "../../tsconfig.tsup.json" 9 | } 10 | -------------------------------------------------------------------------------- /packages/valibot/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { projectConfig } from "../../vitest.config" 2 | 3 | export default projectConfig 4 | -------------------------------------------------------------------------------- /packages/yup/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## next (YYYY-MM-DD) 6 | 7 | ## 0.8.1 (2025-03-23) 8 | 9 | - Feat: export all types 10 | 11 | ## 0.8.0 (2025-03-11) 12 | 13 | * **Break change** refactor: remove `query`, `mutation`, `field`, `resolver`, `subscription` in `@gqloom/schema` 14 | 15 | ## 0.7.2 (2025-02-25) 16 | 17 | * Fix: update document link in README 18 | 19 | ## 0.7.1 (2025-02-08) 20 | 21 | * fix: remove `isTypeOf` when weave a yup object into a GraphQL object 22 | 23 | ## 0.7.0 (2025-02-04) 24 | 25 | * Feature: auto assign names to objects and inputs 26 | 27 | ## 0.5.0 (2024-12-03) 28 | 29 | * Chore: update `@standard-schema/spec` to 1.0.0-beta.4 30 | 31 | ## 0.4.0 (2024-11-120) 32 | 33 | * Refactor: follow standard-schema ([#7](https://github.com/modevol-com/gqloom/pull/7)) 34 | -------------------------------------------------------------------------------- /packages/yup/README.md: -------------------------------------------------------------------------------- 1 | ![GQLoom Logo](https://github.com/modevol-com/gqloom/blob/main/gqloom.svg?raw=true) 2 | 3 | # GQLoom 4 | 5 | GQLoom is a **Code-First** GraphQL Schema Loom used to weave **runtime types** in the **TypeScript/JavaScript** ecosystem into GraphQL Schema, helping you build GraphQL server enjoyably and efficiently. 6 | 7 | Runtime validation libraries such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), and [Yup](https://github.com/jquense/yup) have been widely used in backend application development. Meanwhile, when using ORM libraries like [Prisma](https://www.prisma.io/), [MikroORM](https://mikro-orm.io/), and [Drizzle](https://orm.drizzle.team/), we also pre-define database table structures or entity models that contain runtime types. 8 | The responsibility of GQLoom is to weave these runtime types into a GraphQL Schema. 9 | 10 | When developing backend applications with GQLoom, you only need to write types using the Schema libraries you're familiar with. Modern Schema libraries will infer TypeScript types for you, and GQLoom will weave GraphQL types for you. 11 | In addition, the **resolver factory** of GQLoom can create CRUD interfaces for `Prisma`, `MikroORM`, and `Drizzle`, and supports custom input and adding middleware. 12 | 13 | # @gqloom/yup 14 | 15 | This package provides GQLoom integration with [Yup](https://github.com/jquense/yup) to weave Yup Schema to GraphQL Schema. 16 | 17 | ## Installation 18 | 19 | ```bash 20 | # use npm 21 | npm i @gqloom/core yup @gqloom/yup 22 | 23 | # use pnpm 24 | pnpm add @gqloom/core yup @gqloom/yup 25 | 26 | # use yarn 27 | yarn add @gqloom/core yup @gqloom/yup 28 | ``` 29 | 30 | 31 | ## Hello World 32 | 33 | ```ts 34 | import { query, resolver, weave } from "@gqloom/core" 35 | import { yupSilk } from "@gqloom/yup" 36 | import { string } from "yup" 37 | 38 | const helloResolver = resolver({ 39 | hello: query(yupSilk(string())) 40 | .input({ name: yupSilk(string().default("World")) }) 41 | .resolve(({ name }) => `Hello, ${name}!`), 42 | }) 43 | 44 | export const schema = weave(helloResolver) 45 | ``` 46 | 47 | Read more at [GQLoom Document](https://gqloom.dev/docs/schema/yup). 48 | -------------------------------------------------------------------------------- /packages/yup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gqloom/yup", 3 | "version": "0.9.1", 4 | "description": "Create GraphQL schema and resolvers easily using using Yup!", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | } 18 | } 19 | }, 20 | "scripts": { 21 | "build": "tsup", 22 | "check:type": "tsc --noEmit" 23 | }, 24 | "files": ["dist"], 25 | "keywords": [ 26 | "gqloom", 27 | "graphql", 28 | "schema", 29 | "typescript", 30 | "valibot", 31 | "validation", 32 | "validate" 33 | ], 34 | "author": "xcfox", 35 | "license": "MIT", 36 | "peerDependencies": { 37 | "graphql": ">= 16.8.0", 38 | "@gqloom/core": ">= 0.9.0", 39 | "yup": ">= 1.0.0" 40 | }, 41 | "devDependencies": { 42 | "yup": "^1.4.0", 43 | "@gqloom/core": "workspace:*" 44 | }, 45 | "homepage": "https://gqloom.dev/", 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/modevol-com/gqloom.git", 49 | "directory": "packages/yup" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/yup/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { WeaverConfig } from "@gqloom/core" 2 | import type { SYMBOLS } from "@gqloom/core" 3 | import type { 4 | GraphQLEnumTypeConfig, 5 | GraphQLEnumValueConfig, 6 | GraphQLFieldConfig, 7 | GraphQLInterfaceTypeConfig, 8 | GraphQLObjectTypeConfig, 9 | GraphQLOutputType, 10 | GraphQLUnionTypeConfig, 11 | } from "graphql" 12 | import type { Schema, SchemaDescription } from "yup" 13 | 14 | export interface GQLoomMetadata { 15 | description?: string 16 | 17 | asField?: FieldConfig 18 | 19 | asObjectType?: ObjectTypeConfig 20 | 21 | asInterfaceType?: Partial> 22 | 23 | asEnumType?: EnumTypeConfig 24 | 25 | asUnionType?: Partial> 26 | } 27 | 28 | export interface FieldConfig 29 | extends Partial, "type">> { 30 | type?: 31 | | GraphQLOutputType 32 | | (() => GraphQLOutputType) 33 | | undefined 34 | | null 35 | | typeof SYMBOLS.FIELD_HIDDEN 36 | } 37 | 38 | export interface ObjectTypeConfig 39 | extends Partial< 40 | Omit, "fields" | "interfaces"> 41 | > { 42 | interfaces?: Schema[] 43 | } 44 | 45 | export interface EnumTypeConfig extends Partial { 46 | enum?: Record 47 | valuesConfig?: Record 48 | } 49 | 50 | export interface GQLoomMetadataOptions {} 51 | 52 | export interface YupWeaverOptions { 53 | yupPresetGraphQLType?: ( 54 | description: SchemaDescription 55 | ) => GraphQLOutputType | undefined 56 | } 57 | 58 | export interface YupWeaverConfigOptions { 59 | presetGraphQLType?: ( 60 | description: SchemaDescription 61 | ) => GraphQLOutputType | undefined 62 | } 63 | 64 | export interface YupWeaverConfig extends WeaverConfig, YupWeaverConfigOptions { 65 | [SYMBOLS.WEAVER_CONFIG]: "gqloom.yup" 66 | } 67 | -------------------------------------------------------------------------------- /packages/yup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/yup/tsup.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": ["./src/index.ts"], 3 | "format": ["esm", "cjs"], 4 | "minify": false, 5 | "dts": true, 6 | "outDir": "./dist", 7 | "clean": true, 8 | "tsconfig": "../../tsconfig.tsup.json" 9 | } 10 | -------------------------------------------------------------------------------- /packages/yup/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { projectConfig } from "../../vitest.config" 2 | 3 | export default projectConfig 4 | -------------------------------------------------------------------------------- /packages/zod/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## next (YYYY-MM-DD) 6 | 7 | - Feat: Support zod v4 8 | 9 | ## 0.8.1 (2025-03-23) 10 | 11 | - Feat: export all types 12 | 13 | ## 0.8.0 (2025-03-11) 14 | 15 | ## 0.7.3 (2025-02-21) 16 | 17 | * Fix: handle `z.coerce` nullable correctly 18 | 19 | ## 0.7.2 (2025-02-15) 20 | 21 | * Fix: update document link in README 22 | 23 | ## 0.7.1 (2025-02-08) 24 | 25 | * fix: remove `isTypeOf` when weave a zod object into a GraphQL object, fixed [#15](https://github.com/modevol-com/gqloom/issues/15) 26 | 27 | ## 0.7.0 (2025-02-04) 28 | 29 | * update zod version to 3.24.0 and remove unnecessary functions 30 | * Feature: auto assign names to objects and inputs 31 | 32 | ## 0.5.0 (2024-12-03) 33 | 34 | * Chore: update `@standard-schema/spec` to 1.0.0-beta.4 35 | 36 | ## 0.3.0 (2024-11-120) 37 | 38 | * Refactor: follow standard-schema ([#7](https://github.com/modevol-com/gqloom/pull/7)) 39 | -------------------------------------------------------------------------------- /packages/zod/README.md: -------------------------------------------------------------------------------- 1 | ![GQLoom Logo](https://github.com/modevol-com/gqloom/blob/main/gqloom.svg?raw=true) 2 | 3 | # GQLoom 4 | 5 | GQLoom is a **Code-First** GraphQL Schema Loom used to weave **runtime types** in the **TypeScript/JavaScript** ecosystem into GraphQL Schema, helping you build GraphQL server enjoyably and efficiently. 6 | 7 | Runtime validation libraries such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/), and [Yup](https://github.com/jquense/yup) have been widely used in backend application development. Meanwhile, when using ORM libraries like [Prisma](https://www.prisma.io/), [MikroORM](https://mikro-orm.io/), and [Drizzle](https://orm.drizzle.team/), we also pre-define database table structures or entity models that contain runtime types. 8 | The responsibility of GQLoom is to weave these runtime types into a GraphQL Schema. 9 | 10 | When developing backend applications with GQLoom, you only need to write types using the Schema libraries you're familiar with. Modern Schema libraries will infer TypeScript types for you, and GQLoom will weave GraphQL types for you. 11 | In addition, the **resolver factory** of GQLoom can create CRUD interfaces for `Prisma`, `MikroORM`, and `Drizzle`, and supports custom input and adding middleware. 12 | 13 | # @gqloom/zod 14 | 15 | This package provides GQLoom integration with [Zod](https://zod.dev/) to weave Zod Schema to GraphQL Schema. 16 | 17 | ## Hello World 18 | 19 | ```ts 20 | import { resolver, query, weave } from "@gqloom/core" 21 | import { ZodWeaver } from "@gqloom/zod" 22 | import { zod } from "zod" 23 | 24 | const helloResolver = resolver({ 25 | hello: query(z.string()) 26 | .input({ name: z.string().nullish() }) 27 | .resolve(({ name }) => `Hello, ${name ?? "World"}!`), 28 | }) 29 | 30 | export const schema = weave(ZodWeaver, helloResolver) 31 | ``` 32 | 33 | Read more at [GQLoom Document](https://gqloom.dev/docs/schema/zod). 34 | -------------------------------------------------------------------------------- /packages/zod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gqloom/zod", 3 | "version": "0.9.2", 4 | "description": "Create GraphQL schema and resolvers easily using using Zod!", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": { 11 | "types": "./dist/index.d.ts", 12 | "default": "./dist/index.js" 13 | }, 14 | "require": { 15 | "types": "./dist/index.d.cts", 16 | "default": "./dist/index.cjs" 17 | } 18 | }, 19 | "./v3": { 20 | "import": { 21 | "types": "./dist/v3.d.ts", 22 | "default": "./dist/v3.js" 23 | }, 24 | "require": { 25 | "types": "./dist/v3.d.cts", 26 | "default": "./dist/v3.cjs" 27 | } 28 | } 29 | }, 30 | "scripts": { 31 | "build": "tsup", 32 | "check:type": "tsc --noEmit" 33 | }, 34 | "keywords": [ 35 | "gqloom", 36 | "graphql", 37 | "schema", 38 | "typescript", 39 | "zod", 40 | "validation", 41 | "validate" 42 | ], 43 | "author": "xcfox", 44 | "license": "MIT", 45 | "peerDependencies": { 46 | "@gqloom/core": ">= 0.9.0", 47 | "graphql": ">= 16.8.0", 48 | "zod": ">= 3.25.0" 49 | }, 50 | "files": ["dist"], 51 | "devDependencies": { 52 | "@gqloom/core": "workspace:*", 53 | "zod": "3.25.17" 54 | }, 55 | "homepage": "https://gqloom.dev/", 56 | "repository": { 57 | "type": "git", 58 | "url": "https://github.com/modevol-com/gqloom.git", 59 | "directory": "packages/zod" 60 | }, 61 | "publishConfig": { 62 | "access": "public" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/zod/src/metadata.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod/v4/core" 2 | import type { 3 | EnumConfig, 4 | FieldConfig, 5 | ObjectConfig, 6 | UnionConfig, 7 | } from "./types" 8 | 9 | /** 10 | * A registry to configure GraphQL object types. 11 | */ 12 | export const asObjectType = z.registry() 13 | 14 | /** 15 | * A registry to configure GraphQL fields. 16 | */ 17 | export const asField = z.registry() 18 | 19 | /** 20 | * A registry to configure GraphQL enum types. 21 | */ 22 | export const asEnumType = z.registry() 23 | 24 | /** 25 | * A registry to configure GraphQL union types. 26 | */ 27 | export const asUnionType = z.registry() 28 | -------------------------------------------------------------------------------- /packages/zod/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { WeaverConfig } from "@gqloom/core" 2 | import type { SYMBOLS } from "@gqloom/core" 3 | import type { 4 | GraphQLEnumTypeConfig, 5 | GraphQLEnumValueConfig, 6 | GraphQLFieldConfig, 7 | GraphQLInterfaceType, 8 | GraphQLObjectTypeConfig, 9 | GraphQLOutputType, 10 | GraphQLUnionTypeConfig, 11 | } from "graphql" 12 | import type { $ZodObject, $ZodShape, $ZodType } from "zod/v4/core" 13 | 14 | export interface ObjectConfig 15 | extends Omit< 16 | GraphQLObjectTypeConfig, 17 | "fields" | "name" | "interfaces" 18 | >, 19 | Partial, "fields" | "name">> { 20 | interfaces?: ($ZodObject<$ZodShape> | GraphQLInterfaceType)[] 21 | [k: string]: unknown 22 | } 23 | 24 | export interface FieldConfig 25 | extends Partial, "type">> { 26 | type?: GraphQLOutputType | undefined | null | typeof SYMBOLS.FIELD_HIDDEN 27 | 28 | [k: string]: unknown 29 | } 30 | 31 | export interface EnumConfig 32 | extends Partial { 33 | valuesConfig?: TKey extends string 34 | ? Partial> 35 | : Partial> 36 | [k: string]: unknown 37 | } 38 | 39 | export interface UnionConfig 40 | extends Omit, "types">, 41 | Partial, "types">> { 42 | [k: string]: unknown 43 | } 44 | 45 | export interface ZodWeaverConfigOptions { 46 | presetGraphQLType?: (schema: $ZodType) => GraphQLOutputType | undefined 47 | } 48 | 49 | export interface ZodWeaverConfig extends WeaverConfig, ZodWeaverConfigOptions { 50 | [SYMBOLS.WEAVER_CONFIG]: "gqloom.zod" 51 | } 52 | -------------------------------------------------------------------------------- /packages/zod/src/v3/types.ts: -------------------------------------------------------------------------------- 1 | import type { WeaverConfig } from "@gqloom/core" 2 | // biome-ignore lint/correctness/noUnusedImports: SYMBOLS used in type 3 | import type { SYMBOLS } from "@gqloom/core" 4 | import type { 5 | GraphQLEnumTypeConfig, 6 | GraphQLEnumValueConfig, 7 | GraphQLFieldConfig, 8 | GraphQLInterfaceType, 9 | GraphQLObjectTypeConfig, 10 | GraphQLOutputType, 11 | GraphQLUnionTypeConfig, 12 | } from "graphql" 13 | import type { Schema, ZodObject, ZodRawShape } from "zod/v3" 14 | 15 | export interface ObjectConfig 16 | extends Omit< 17 | GraphQLObjectTypeConfig, 18 | "fields" | "name" | "interfaces" 19 | >, 20 | Partial, "fields" | "name">> { 21 | interfaces?: (ZodObject | GraphQLInterfaceType)[] 22 | } 23 | 24 | export interface FieldConfig 25 | extends Partial, "type">> { 26 | type?: GraphQLOutputType | undefined | null 27 | } 28 | 29 | export interface EnumConfig 30 | extends Partial { 31 | valuesConfig?: TKey extends string 32 | ? Partial> 33 | : Partial> 34 | } 35 | 36 | export interface UnionConfig 37 | extends Omit, "types">, 38 | Partial, "types">> {} 39 | 40 | export type TypeOrFieldConfig = 41 | | ObjectConfig 42 | | FieldConfig 43 | | EnumConfig 44 | | UnionConfig 45 | 46 | export interface ZodWeaverConfigOptions { 47 | presetGraphQLType?: (schema: Schema) => GraphQLOutputType | undefined 48 | } 49 | 50 | export interface ZodWeaverConfig extends WeaverConfig, ZodWeaverConfigOptions { 51 | [SYMBOLS.WEAVER_CONFIG]: "gqloom.zod" 52 | } 53 | -------------------------------------------------------------------------------- /packages/zod/src/v3/utils.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | GraphQLFieldConfig, 3 | GraphQLObjectTypeConfig, 4 | GraphQLTypeResolver, 5 | } from "graphql" 6 | import { 7 | type ZodDiscriminatedUnion, 8 | ZodEffects, 9 | type ZodObject, 10 | type ZodStringCheck, 11 | } from "zod/v3" 12 | import { ZodWeaver } from "." 13 | 14 | export function parseObjectConfig( 15 | input: string 16 | ): Pick< 17 | GraphQLObjectTypeConfig, 18 | "name" | "description" | "extensions" 19 | > { 20 | const [name, ...maybeDescription] = input.split(":") 21 | 22 | return { 23 | name: name.trim(), 24 | description: 25 | maybeDescription.length > 0 26 | ? maybeDescription.join(":").trim() 27 | : undefined, 28 | } 29 | } 30 | 31 | export function parseFieldConfig( 32 | input: string | undefined 33 | ): 34 | | Pick, "description" | "extensions"> 35 | | undefined { 36 | if (!input) return undefined 37 | 38 | return { 39 | description: input.trim(), 40 | } 41 | } 42 | 43 | export function resolveTypeByDiscriminatedUnion( 44 | schemaOrEffect: 45 | | ZodDiscriminatedUnion[]> 46 | | ZodEffects[]>> 47 | ): GraphQLTypeResolver { 48 | const schema = 49 | schemaOrEffect instanceof ZodEffects 50 | ? schemaOrEffect.innerType() 51 | : schemaOrEffect 52 | return (data) => { 53 | const discriminatorValue: string = data[schema.discriminator] 54 | const option = schema.optionsMap.get(discriminatorValue) 55 | return ZodWeaver.getDiscriminatedUnionOptionName(option) 56 | } 57 | } 58 | 59 | export const ZodIDKinds: Set = new Set([ 60 | "cuid", 61 | "cuid2", 62 | "ulid", 63 | "uuid", 64 | ]) 65 | -------------------------------------------------------------------------------- /packages/zod/test/v3/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest" 2 | import { parseFieldConfig, parseObjectConfig } from "../../src/v3/utils" 3 | 4 | describe("parseObjectConfig", () => { 5 | it("should parse name", () => { 6 | expect(parseObjectConfig("name")).toMatchObject({ 7 | name: "name", 8 | description: undefined, 9 | }) 10 | 11 | expect(parseObjectConfig("abc")).toMatchObject({ 12 | name: "abc", 13 | description: undefined, 14 | }) 15 | }) 16 | 17 | it("should parse description", () => { 18 | expect(parseObjectConfig("name: description")).toMatchObject({ 19 | name: "name", 20 | description: "description", 21 | }) 22 | 23 | expect( 24 | parseObjectConfig("name: description maybe very long") 25 | ).toMatchObject({ 26 | name: "name", 27 | description: "description maybe very long", 28 | }) 29 | }) 30 | }) 31 | 32 | describe("parseFieldConfig", () => { 33 | it("should parse description", () => { 34 | expect(parseFieldConfig("description maybe:: very long")).toMatchObject({ 35 | description: "description maybe:: very long", 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/zod/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "test"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/zod/tsup.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": { 3 | "index": "./src/index.ts", 4 | "v3": "./src/v3/index.ts" 5 | }, 6 | "format": ["esm", "cjs"], 7 | "minify": false, 8 | "dts": true, 9 | "outDir": "./dist", 10 | "clean": true, 11 | "tsconfig": "../../tsconfig.tsup.json" 12 | } 13 | -------------------------------------------------------------------------------- /packages/zod/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { projectConfig } from "../../vitest.config" 2 | 3 | export default projectConfig 4 | -------------------------------------------------------------------------------- /patches/graphql@16.8.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/package.json b/package.json 2 | index 67818190393e01c3175ae1cf9231f5e51b77c4e4..14935c26711b1291935f2ae38c578fcc103d25d4 100644 3 | --- a/package.json 4 | +++ b/package.json 5 | @@ -3,7 +3,6 @@ 6 | "version": "16.8.1", 7 | "description": "A Query Language and Runtime which can target any service.", 8 | "license": "MIT", 9 | - "main": "index", 10 | "module": "index.mjs", 11 | "typesVersions": { 12 | ">=4.1.0": { 13 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | - "examples/*" 4 | - "website" 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowImportingTsExtensions": true, 8 | "skipLibCheck": true, 9 | "noEmit": true, 10 | "paths": { 11 | "@gqloom/core/context": ["./packages/core/src/context/index.ts"], 12 | "@gqloom/*": ["./packages/*/src/index.ts"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.tsup.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "bundler" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path" 2 | import { defineConfig, defineProject } from "vitest/config" 3 | 4 | const alias = (name: string) => { 5 | const find = `@gqloom/${name}` 6 | const replacement = path.join(__dirname, "packages", name, "src") 7 | return { find, replacement } 8 | } 9 | 10 | export const projectConfig = defineProject({ 11 | test: { 12 | alias: [ 13 | alias("core"), 14 | alias("federation"), 15 | alias("mikro-orm"), 16 | alias("prisma"), 17 | alias("valibot"), 18 | alias("yup"), 19 | alias("zod"), 20 | ], 21 | }, 22 | }) 23 | 24 | export default defineConfig({ 25 | test: { 26 | coverage: { 27 | exclude: [ 28 | ".vercel/", 29 | "coverage/", 30 | "**/test/**", 31 | "**/dist/**", 32 | "**/examples/**/*", 33 | "**/*.spec-d.ts", 34 | "**/*.spec.ts", 35 | "**/*.config.ts", 36 | "**/draft/**", 37 | "website/**", 38 | "vitest.workspace.ts", 39 | "**/generated/*.ts", 40 | "**/bin/*", 41 | "packages/prisma/src/generator/index.ts", 42 | ], 43 | }, 44 | }, 45 | }) 46 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from "vitest/config" 2 | 3 | export default defineWorkspace([ 4 | "packages/*", 5 | { 6 | extends: "./vitest.config.ts", 7 | test: { 8 | include: ["test/**/*.{spec,spec-d}.ts", "src/**/*.{spec,spec-d}.ts"], 9 | }, 10 | }, 11 | ]) 12 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # deps 2 | /node_modules 3 | 4 | # generated content 5 | .contentlayer 6 | .content-collections 7 | .source 8 | 9 | # test & build 10 | /coverage 11 | /.next/ 12 | /out/ 13 | /build 14 | *.tsbuildinfo 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | /.pnp 20 | .pnp.js 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # others 26 | .env*.local 27 | .vercel 28 | next-env.d.ts -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # website 2 | 3 | This is a Next.js application generated with 4 | [Create Fumadocs](https://github.com/fuma-nama/fumadocs). 5 | 6 | Run development server: 7 | 8 | ```bash 9 | npm run dev 10 | # or 11 | pnpm dev 12 | # or 13 | yarn dev 14 | ``` 15 | 16 | Open http://localhost:3000 with your browser to see the result. 17 | 18 | ## Learn More 19 | 20 | To learn more about Next.js and Fumadocs, take a look at the following 21 | resources: 22 | 23 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js 24 | features and API. 25 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 26 | - [Fumadocs](https://fumadocs.vercel.app) - learn about Fumadocs 27 | -------------------------------------------------------------------------------- /website/app/[lang]/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { baseOptions } from "@/app/layout.config" 2 | import { HomeLayout } from "fumadocs-ui/layouts/home" 3 | import type { ReactNode } from "react" 4 | 5 | export default function Layout({ children }: { children: ReactNode }) { 6 | return {children} 7 | } 8 | -------------------------------------------------------------------------------- /website/app/[lang]/(home)/utils.tsx: -------------------------------------------------------------------------------- 1 | import { Popup, PopupContent, PopupTrigger } from "fumadocs-twoslash/ui" 2 | import { CodeBlock, Pre } from "fumadocs-ui/components/codeblock" 3 | import { Tab, Tabs } from "fumadocs-ui/components/tabs" 4 | import defaultMdxComponents from "fumadocs-ui/mdx" 5 | 6 | export const mdxComponents = { 7 | ...defaultMdxComponents, 8 | pre: ({ children, ...props }: React.ComponentProps<"pre">) => ( 9 | 10 |
{children}
11 |
12 | ), 13 | Tab, 14 | Tabs, 15 | Popup, 16 | PopupContent, 17 | PopupTrigger, 18 | } 19 | -------------------------------------------------------------------------------- /website/app/[lang]/docs/[[...slug]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { source } from "@/lib/source" 2 | import { Popup, PopupContent, PopupTrigger } from "fumadocs-twoslash/ui" 3 | import { Tab, Tabs } from "fumadocs-ui/components/tabs" 4 | import defaultMdxComponents from "fumadocs-ui/mdx" 5 | import { 6 | DocsBody, 7 | DocsDescription, 8 | DocsPage, 9 | DocsTitle, 10 | } from "fumadocs-ui/page" 11 | import { notFound } from "next/navigation" 12 | 13 | export default async function Page(props: { 14 | params: Promise<{ slug?: string[]; lang: string }> 15 | }) { 16 | const params = await props.params 17 | const page = source.getPage(params.slug, params.lang) 18 | if (!page) notFound() 19 | 20 | const MDX = page.data.body 21 | 22 | return ( 23 | 24 | {page.data.title} 25 | {page.data.description} 26 | 27 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export async function generateStaticParams() { 43 | return source.generateParams() 44 | } 45 | 46 | export async function generateMetadata(props: { 47 | params: Promise<{ slug?: string[]; lang: string }> 48 | }) { 49 | const params = await props.params 50 | const page = source.getPage(params.slug, params.lang) 51 | if (!page) notFound() 52 | 53 | return { 54 | title: page.data.title, 55 | description: page.data.description, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /website/app/[lang]/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { baseOptions } from "@/app/layout.config" 2 | import { source } from "@/lib/source" 3 | import { DocsLayout } from "fumadocs-ui/layouts/docs" 4 | import type { ReactNode } from "react" 5 | 6 | export default async function Layout({ 7 | children, 8 | params, 9 | }: { children: ReactNode; params: Promise<{ lang: string }> }) { 10 | return ( 11 | 12 | {children} 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /website/app/[lang]/layout.tsx: -------------------------------------------------------------------------------- 1 | import "../global.css" 2 | import { I18nProvider, type Translations } from "fumadocs-ui/i18n" 3 | import { RootProvider } from "fumadocs-ui/provider" 4 | import type { Metadata } from "next" 5 | import { Inter } from "next/font/google" 6 | import type { ReactNode } from "react" 7 | 8 | export const metadata: Metadata = { 9 | title: "GQLoom", 10 | description: 11 | "GQLoom is a Code First GraphQL Schema Loom, used to weave runtime types in the TypeScript/JavaScript ecosystem into a GraphQL Schema.", 12 | icons: { 13 | icon: { url: "/gqloom.svg", type: "image/svg+xml" }, 14 | }, 15 | } 16 | 17 | const inter = Inter({ 18 | subsets: ["latin"], 19 | }) 20 | 21 | const zh: Partial = { 22 | search: "搜索", 23 | searchNoResult: "没有结果", 24 | chooseLanguage: "选择语言", 25 | nextPage: "下一页", 26 | previousPage: "上一页", 27 | editOnGithub: "在 GitHub 上编辑此页面", 28 | toc: "目录", 29 | tocNoHeadings: "没有标题", 30 | lastUpdate: "最后更新于", 31 | } 32 | 33 | const translations: Record> = { zh } 34 | 35 | // available languages that will be displayed on UI 36 | // make sure `locale` is consistent with your i18n config 37 | const locales = [ 38 | { 39 | name: "English", 40 | locale: "en", 41 | }, 42 | { 43 | name: "简体中文", 44 | locale: "zh", 45 | }, 46 | ] 47 | 48 | export default async function Layout({ 49 | params, 50 | children, 51 | }: { params: Promise<{ lang: string }>; children: ReactNode }) { 52 | const lang = (await params).lang 53 | return ( 54 | 55 | 56 | 61 | {children} 62 | 63 | 64 | 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /website/app/[lang]/llms.txt/route.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "node:fs/promises" 2 | import fg from "fast-glob" 3 | import { remarkInclude } from "fumadocs-mdx/config" 4 | import matter from "gray-matter" 5 | import { remark } from "remark" 6 | import remarkGfm from "remark-gfm" 7 | import remarkMdx from "remark-mdx" 8 | import remarkStringify from "remark-stringify" 9 | import * as YAML from "yaml" 10 | 11 | export const revalidate = false 12 | 13 | const processor = remark() 14 | .use(remarkMdx) 15 | // https://fumadocs.dev/docs/mdx/include 16 | .use(remarkInclude) 17 | // gfm styles 18 | .use(remarkGfm) 19 | // .use(your remark plugins) 20 | .use(remarkStringify) // to string 21 | 22 | // 缓存处理结果 23 | const cache = new Map() 24 | const CACHE_TTL = 60 * 60 * 1000 // 1 hour 25 | 26 | export async function GET( 27 | _: Request, 28 | { params }: { params: Promise<{ lang: "en" | "zh" }> } 29 | ) { 30 | const { lang } = await params 31 | const cacheKey = `llms_${lang}` 32 | 33 | // 检查缓存 34 | const cached = cache.get(cacheKey) 35 | if (cached && Date.now() - cached.timestamp < CACHE_TTL) { 36 | return new Response(cached.content) 37 | } 38 | 39 | const files = await (() => { 40 | if (lang === "zh") { 41 | return fg("./content/docs/*.zh.{mdx,md}") 42 | } 43 | return fg("./content/docs/*.{mdx,md}", { 44 | ignore: ["**/*.zh.{mdx,md}"], 45 | }) 46 | })() 47 | 48 | const scan = files.map(async (file) => { 49 | const fileContent = await fs.readFile(file) 50 | const { content, data } = matter(fileContent.toString()) 51 | 52 | const processed = await processor.process({ 53 | path: file, 54 | value: content, 55 | }) 56 | const meta = { ...data, file } 57 | const text = processed 58 | .toString() 59 | .replace(/```ts twoslash[\s\S]*?\/\/ ---cut---\n/g, "```ts twoslash\n") 60 | 61 | const header = [`---`, `${YAML.stringify(meta).trim()}`, `---`].join("\n") 62 | return `${header}\n\n${text}` 63 | }) 64 | 65 | const scanned = await Promise.all(scan) 66 | const result = scanned.join("\n\n") 67 | 68 | // 更新缓存 69 | cache.set(cacheKey, { 70 | content: result, 71 | timestamp: Date.now(), 72 | }) 73 | 74 | return new Response(result) 75 | } 76 | -------------------------------------------------------------------------------- /website/app/global.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "fumadocs-ui/css/catppuccin.css"; 3 | @import "fumadocs-ui/css/preset.css"; 4 | @import "fumadocs-twoslash/twoslash.css"; 5 | 6 | @source '../node_modules/fumadocs-ui/dist/**/*.js'; 7 | 8 | :root { 9 | --color-fd-primary: hsl(336, 95%, 43%); 10 | --color-fd-ring: hsl(336, 84%, 81%); 11 | } 12 | 13 | .dark { 14 | --color-fd-primary: hsl(336, 100%, 70%); 15 | --color-fd-ring: hsl(336, 84%, 81%); 16 | } 17 | 18 | button { 19 | @apply cursor-pointer; 20 | } 21 | 22 | @keyframes float { 23 | 0%, 24 | 100% { 25 | transform: translateY(0) scale(1); 26 | } 27 | 50% { 28 | transform: translateY(-20px) scale(0.9); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /website/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import DocText from "@/components/doc-text" 2 | import { GQLoomLogo } from "@/components/gqloom-logo" 3 | import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared" 4 | import { memo } from "react" 5 | 6 | const Title = memo(() => { 7 | return ( 8 | <> 9 | 10 | GQLoom 11 | 12 | ) 13 | }) 14 | 15 | /** 16 | * Shared layout configurations 17 | * 18 | * you can configure layouts individually from: 19 | * Home Layout: app/(home)/layout.tsx 20 | * Docs Layout: app/docs/layout.tsx 21 | */ 22 | export const baseOptions: BaseLayoutProps = { 23 | nav: { 24 | // can be JSX too! 25 | title: , 26 | }, 27 | githubUrl: "https://github.com/modevol-com/gqloom", 28 | i18n: true, 29 | links: [ 30 | { 31 | type: "custom", 32 | children: <DocText />, 33 | on: "nav", 34 | }, 35 | ], 36 | } 37 | -------------------------------------------------------------------------------- /website/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next" 2 | 3 | export default function RootLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode 7 | }) { 8 | return children 9 | } 10 | 11 | export const metadata: Metadata = { 12 | title: "GQLoom", 13 | description: 14 | "GQLoom is a Code First GraphQL Schema Loom, used to weave runtime types in the TypeScript/JavaScript ecosystem into a GraphQL Schema.", 15 | icons: { 16 | icon: { url: "/gqloom.svg", type: "image/svg+xml" }, 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /website/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import "./global.css" 2 | import Link from "next/link" 3 | 4 | export default function NotFound() { 5 | return ( 6 | <html lang="en"> 7 | <body> 8 | <main className="flex flex-col items-center justify-center h-screen bg-slate-950 text-neutral-100 space-y-6 px-4"> 9 | <div className="text-center space-y-4 relative"> 10 | <h1 className="text-9xl font-bold bg-gradient-to-r from-amber-400 to-rose-600 bg-clip-text text-transparent"> 11 | 404 12 | </h1> 13 | <h2 className="text-2xl font-semibold text-orange-100"> 14 | Page Not Found 15 | </h2> 16 | <p className="text-amber-100/60 max-w-md"> 17 | The page you're looking for doesn't exist or has been moved. 18 | </p> 19 | </div> 20 | <Link 21 | href="/" 22 | className="bg-gradient-to-r from-orange-500 to-rose-600 px-6 py-3 rounded-full 23 | font-medium hover:scale-105 transition-transform duration-200 24 | flex items-center gap-2 group shadow-lg hover:shadow-orange-500/20" 25 | > 26 | <span>Back to Home</span> 27 | <span className="group-hover:translate-x-1 transition-transform"> 28 | → 29 | </span> 30 | </Link> 31 | <div className="absolute inset-0 overflow-hidden opacity-60 pointer-events-none"> 32 | {[...Array(20)].map((_, i) => ( 33 | <div 34 | key={i} 35 | className="absolute size-1 bg-rose-300/60 rounded-full" 36 | style={{ 37 | top: `${Math.random() * 100}%`, 38 | left: `${Math.random() * 100}%`, 39 | animation: `float ${Math.random() * 8 + 4}s infinite`, 40 | }} 41 | /> 42 | ))} 43 | </div> 44 | </main> 45 | </body> 46 | </html> 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /website/components/doc-text/index.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import Link from "next/link" 3 | import { useParams, usePathname } from "next/navigation" 4 | 5 | export default function DocText() { 6 | const params = useParams<{ lang: string }>() 7 | const pathname = usePathname() 8 | const pathDepth = pathname.split("/").filter(Boolean).length 9 | if (pathDepth > 1) { 10 | return null 11 | } 12 | 13 | const text = params.lang === "zh" ? "文档" : "Documentation" 14 | return ( 15 | <Link 16 | href={`/${params.lang}/docs`} 17 | className="font-semibold flex flex-row items-center gap-2 rounded-md px-3 py-2.5 text-fd-muted-foreground transition-colors duration-100 [overflow-wrap:anywhere] hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none md:px-2 md:py-1.5 [&_svg]:size-4" 18 | > 19 | {text} 20 | </Link> 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /website/components/flowing-lines/index.module.css: -------------------------------------------------------------------------------- 1 | .background { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | opacity: 0.1; 8 | z-index: -1; 9 | } 10 | 11 | .backgroundContainer { 12 | position: relative; 13 | overflow: hidden; 14 | } 15 | 16 | .background { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | opacity: 0.1; 23 | z-index: -1; 24 | } 25 | 26 | /* flowing-lines.module.css */ 27 | /* 删除 composes 语句,改用标准 CSS 选择器 */ 28 | .path, 29 | .line1, 30 | .line2, 31 | .line3 { 32 | fill: none; 33 | stroke-width: 2; 34 | stroke-linecap: round; 35 | animation: flow 12s linear infinite; 36 | } 37 | 38 | /* 保持原有颜色定义 */ 39 | .line1 { 40 | stroke: #ff6b6b; 41 | } 42 | .line2 { 43 | stroke: #4ecdc4; 44 | } 45 | .line3 { 46 | stroke: #ffe66d; 47 | } 48 | 49 | @keyframes flow { 50 | 0% { 51 | stroke-dashoffset: 0; 52 | transform: translateX(-5%); 53 | } 54 | 100% { 55 | stroke-dashoffset: -1000; 56 | transform: translateX(5%); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /website/components/flowing-lines/index.tsx: -------------------------------------------------------------------------------- 1 | import { colord, extend } from "colord" 2 | import mixPlugin from "colord/plugins/mix" 3 | 4 | extend([mixPlugin]) 5 | 6 | const originColors = ["#FBBF24", "#F5775B", "#CA5BF2"] 7 | const colorTotal = 20 8 | const colorGroupSize = colorTotal / (originColors.length - 1) 9 | 10 | const colorList = new Array(colorTotal).fill(0).map((_, i) => { 11 | const currentOriginIndex = Math.floor(i / colorGroupSize) 12 | const nextOriginIndex = currentOriginIndex + 1 13 | 14 | return colord(originColors[currentOriginIndex]).mix( 15 | originColors[nextOriginIndex], 16 | (i % colorGroupSize) / colorGroupSize 17 | ) 18 | }) 19 | 20 | const toBottom = (bottom: number, index: number) => 21 | (index / colorTotal) * bottom + (360 - bottom) 22 | 23 | export function FlowingLines({ className }: { className?: string }) { 24 | return ( 25 | <svg preserveAspectRatio="none" viewBox="0 0 360 360" className={className}> 26 | {colorList.map((color, i) => { 27 | const mid = 0.7 28 | const x = i / (colorTotal - 1) 29 | const factor = 1 / Math.pow(mid, 2) 30 | return ( 31 | <path 32 | key={i} 33 | d={`M0 ${(i * 360) / colorTotal - 1} Q 120 ${toBottom(100, i)} 360 ${toBottom(70, i)}`} 34 | style={{ 35 | fill: "none", 36 | strokeWidth: 1, 37 | stroke: color.toHex(), 38 | opacity: 1 - Math.pow(x - mid, 2) * factor, 39 | strokeDasharray: 1000, 40 | }} 41 | /> 42 | ) 43 | })} 44 | </svg> 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/apollo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Apollo 3 | --- 4 | 5 | [Apollo Server](https://www.apollographql.com/docs/apollo-server/) is an open-source, spec-compliant GraphQL server that's compatible with any GraphQL client, including [Apollo Client](https://www.apollographql.com/docs/react). 6 | It's the best way to build a production-ready, self-documenting GraphQL API that can use data from any source. 7 | 8 | ## Installation 9 | 10 | ```sh tab="npm" 11 | npm i graphql @apollo/server @gqloom/core 12 | ``` 13 | ```sh tab="pnpm" 14 | pnpm add graphql @apollo/server @gqloom/core 15 | ``` 16 | ```sh tab="yarn" 17 | yarn add graphql @apollo/server @gqloom/core 18 | ``` 19 | ```sh tab="bun" 20 | bun add graphql @apollo/server @gqloom/core 21 | ``` 22 | 23 | ## Usage 24 | ```ts twoslash 25 | // @filename: resolvers.ts 26 | import { resolver, query, silk, weave } from "@gqloom/core" 27 | import { GraphQLNonNull, GraphQLString } from "graphql" 28 | import { createServer } from "node:http" 29 | import { createYoga } from "graphql-yoga" 30 | 31 | export const helloResolver = resolver({ 32 | hello: query( 33 | silk<string>(new GraphQLNonNull(GraphQLString)), 34 | () => "Hello, World" 35 | ), 36 | }) 37 | // @filename: index.ts 38 | // ---cut--- 39 | import { weave } from "@gqloom/core" 40 | import { ApolloServer } from "@apollo/server" 41 | import { startStandaloneServer } from "@apollo/server/standalone" 42 | import { helloResolver } from "./resolvers" 43 | 44 | const schema = weave(helloResolver) 45 | const server = new ApolloServer({ schema }) 46 | 47 | startStandaloneServer(server, { 48 | listen: { port: 4000 }, 49 | }).then(({ url }) => { 50 | console.info(`🚀 Server ready at: ${url}`) 51 | }) 52 | ``` 53 | 54 | ## Context 55 | 56 | The default context for `Apollo Server` is an empty object, you need to pass the context to the resolvers manually. 57 | See the [Apollo Server documentation](https://www.apollographql.com/docs/apollo-server/data/context) for more information. -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/apollo.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Apollo 3 | --- 4 | 5 | [Apollo Server](https://www.apollographql.com/docs/apollo-server/) 是一款开源、符合规范的 GraphQL 服务器,与包括 [Apollo Client](https://www.apollographql.com/docs/react) 在内的任何 GraphQL 客户端兼容。 6 | 它是构建生产就绪、自文档化 GraphQL API 的最佳方式,可使用来自任何来源的数据。 7 | 8 | ## 安装 9 | 10 | ```sh tab="npm" 11 | npm i graphql @apollo/server @gqloom/core 12 | ``` 13 | ```sh tab="pnpm" 14 | pnpm add graphql @apollo/server @gqloom/core 15 | ``` 16 | ```sh tab="yarn" 17 | yarn add graphql @apollo/server @gqloom/core 18 | ``` 19 | ```sh tab="bun" 20 | bun add graphql @apollo/server @gqloom/core 21 | ``` 22 | 23 | ## 使用 24 | ```ts twoslash 25 | // @filename: resolvers.ts 26 | import { resolver, query, silk, weave } from "@gqloom/core" 27 | import { GraphQLNonNull, GraphQLString } from "graphql" 28 | import { createServer } from "node:http" 29 | import { createYoga } from "graphql-yoga" 30 | 31 | export const helloResolver = resolver({ 32 | hello: query( 33 | silk<string>(new GraphQLNonNull(GraphQLString)), 34 | () => "Hello, World" 35 | ), 36 | }) 37 | // @filename: index.ts 38 | // ---cut--- 39 | import { weave } from "@gqloom/core" 40 | import { ApolloServer } from "@apollo/server" 41 | import { startStandaloneServer } from "@apollo/server/standalone" 42 | import { helloResolver } from "./resolvers" 43 | 44 | const schema = weave(helloResolver) 45 | const server = new ApolloServer({ schema }) 46 | 47 | startStandaloneServer(server, { 48 | listen: { port: 4000 }, 49 | }).then(({ url }) => { 50 | console.info(`🚀 Server ready at: ${url}`) 51 | }) 52 | ``` 53 | 54 | ## 上下文 55 | 56 | `Apollo Server` 的默认上下文为空对象,你需要手动传递上下文到解析器中。 57 | 更多信息请查看 [Apollo Server 文档](https://www.apollographql.com/docs/apollo-server/data/context)。 -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/elysia.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Elysia 3 | --- 4 | 5 | [Elysia](https://elysiajs.com/) is an ergonomic web framework for building backend servers with Bun. 6 | 7 | Designed with simplicity and type-safety in mind, Elysia has a familiar API with extensive support for TypeScript, optimized for Bun. 8 | 9 | ## Installation 10 | 11 | ```package-install 12 | elysia @elysiajs/graphql-yoga graphql @gqloom/core 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```ts 18 | import { Elysia } from 'elysia' 19 | import { query, resolver, weave } from '@gqloom/core' 20 | import { yoga } from '@elysiajs/graphql-yoga' 21 | import * as z from 'zod' 22 | import { ZodWeaver } from '@gqloom/zod' 23 | import { helloResolver } from "./resolvers" 24 | 25 | const schema = weave(helloResolver) 26 | 27 | const app = new Elysia().use(yoga({ schema })).listen(8001) 28 | 29 | console.log( 30 | `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 31 | ) 32 | ``` 33 | 34 | ## Contexts 35 | 36 | When using GQLoom together with `@elysiajs/graphql-yoga`, you can use `YogaInitialContext` to label the type of context: 37 | 38 | ```ts 39 | import { useContext } from '@gqloom/core' 40 | import type { YogaInitialContext } from 'graphql-yoga' 41 | 42 | export function useAuthorization() { 43 | return useContext<YogaInitialContext>().request.headers.get('Authorization') 44 | } 45 | ``` 46 | 47 | You can also learn more about contexts in the [Elysia documentation](https://elysiajs.com/plugins/graphql-yoga.html). -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/elysia.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Elysia 3 | --- 4 | 5 | [Elysia](https://elysiajs.com/) 是一个符合人体工程学的 Web 框架,用于使用 Bun 构建后端服务器。 6 | 7 | Elysia 在设计时考虑到了简洁性和类型安全性,它拥有大家熟悉的 API,对 TypeScript 提供了广泛支持,并且针对 Bun 进行了优化。 8 | 9 | ## 安装 10 | 11 | ```package-install 12 | elysia @elysiajs/graphql-yoga graphql @gqloom/core 13 | ``` 14 | 15 | ## 使用方法 16 | 17 | ```ts 18 | import { Elysia } from 'elysia' 19 | import { query, resolver, weave } from '@gqloom/core' 20 | import { yoga } from '@elysiajs/graphql-yoga' 21 | import * as z from 'zod' 22 | import { ZodWeaver } from '@gqloom/zod' 23 | import { helloResolver } from "./resolvers" 24 | 25 | const schema = weave(helloResolver) 26 | 27 | const app = new Elysia().use(yoga({ schema })).listen(8001) 28 | 29 | console.log( 30 | `🦊 Elysia 正在 ${app.server?.hostname}:${app.server?.port} 运行` 31 | ) 32 | ``` 33 | 34 | ## 上下文 35 | 36 | 当将 GQLoom 与 `@elysiajs/graphql-yoga` 一起使用时,你可以使用 `YogaInitialContext` 来标注上下文的类型: 37 | 38 | ```ts 39 | import { useContext } from '@gqloom/core' 40 | import type { YogaInitialContext } from 'graphql-yoga' 41 | 42 | export function useAuthorization() { 43 | return useContext<YogaInitialContext>().request.headers.get('Authorization') 44 | } 45 | ``` 46 | 47 | 你也可以在 [Elysia 文档](https://elysiajs.com/plugins/graphql-yoga.html) 中了解更多关于上下文的信息。 -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/hono.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hono 3 | --- 4 | 5 | [Hono](https://hono.dev/) is a small, simple, and extremely fast web framework built based on web standards and capable of running in various JavaScript runtime environments. It has the characteristics of zero dependencies and being lightweight, and provides a concise API and first-class TypeScript support. It is suitable for building various application scenarios such as web APIs and edge applications. 6 | 7 | ## Installation 8 | 9 | ```package-install 10 | hono @hono/graphql-server graphql @gqloom/core 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```ts 16 | import { weave } from "@gqloom/core" 17 | import { graphqlServer } from "@hono/graphql-server" 18 | import { serve } from "@hono/node-server" 19 | import { Hono } from "hono" 20 | import { helloResolver } from "./resolvers" 21 | 22 | export const app = new Hono() 23 | 24 | const schema = weave(helloResolver) 25 | 26 | app.use("/graphql", graphqlServer({ schema, graphiql: true })) 27 | 28 | serve(app, (info) => { 29 | console.info( 30 | `GraphQL server is running on http://localhost:${info.port}/graphql` 31 | ) 32 | }) 33 | ``` 34 | 35 | ## Context 36 | 37 | When using GQLoom together with Hono, you can use Hono's `Context` to annotate the type of the context: 38 | 39 | ```ts 40 | import { useContext } from "@gqloom/core" 41 | import type { Context } from "hono" 42 | 43 | export function useAuthorization() { 44 | return useContext<Context>().req.header().authorization 45 | } 46 | ``` 47 | 48 | Learn more in the [Hono documentation](https://hono.dev/docs/api/context). -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/hono.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hono 3 | --- 4 | 5 | [Hono](https://hono.dev/) 是一个小巧、简单且超快速的 Web 框架,基于 Web 标准构建,能够在多种 JavaScript 运行时环境中运行。它具有零依赖、轻量级的特点,且提供简洁的 API 和一流的 TypeScript 支持,适合构建 Web API、边缘应用等多种应用场景。 6 | 7 | ## 安装 8 | 9 | ```package-install 10 | hono @hono/graphql-server graphql @gqloom/core 11 | ``` 12 | 13 | ## 使用 14 | 15 | ```ts 16 | import { weave } from "@gqloom/core" 17 | import { graphqlServer } from "@hono/graphql-server" 18 | import { serve } from "@hono/node-server" 19 | import { Hono } from "hono" 20 | import { helloResolver } from "./resolvers" 21 | 22 | export const app = new Hono() 23 | 24 | const schema = weave(helloResolver) 25 | 26 | app.use("/graphql", graphqlServer({ schema, graphiql: true })) 27 | 28 | serve(app, (info) => { 29 | console.info( 30 | `GraphQL server is running on http://localhost:${info.port}/graphql` 31 | ) 32 | }) 33 | ``` 34 | 35 | ## 上下文 36 | 37 | 在与 Hono 同时使用 GQLoom 时,你可以使用 hono 的 `Context` 来标注上下的类型: 38 | 39 | ```ts 40 | import { useContext } from "@gqloom/core" 41 | import type { Context } from "hono" 42 | 43 | export function useAuthorization() { 44 | return useContext<Context>().req.header().authorization 45 | } 46 | ``` 47 | 48 | 在 [Hono 文档](https://hono.dev/docs/api/context) 中了解更多信息。 -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Adapters 3 | --- 4 | import { Card, Cards } from 'fumadocs-ui/components/card' 5 | 6 | There are a number of GraphQL HTTP adapters in the Node.js ecosystem. 7 | 8 | Since the product of the GQLoom weave is a standard GraphQL Schema object, it works seamlessly with these adapters. 9 | 10 | Here are some popular adapters: 11 | 12 | <Cards> 13 | <Card 14 | href="./adapters/yoga" 15 | title="Yoga" 16 | description="Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance & great developer experience." 17 | /> 18 | <Card 19 | href="./adapters/apollo" 20 | title="Apollo Server" 21 | description="pec-compliant and production ready JavaScript GraphQL server that lets you develop in a schema-first way. Built for Express, Connect, Hapi, Koa, and more." 22 | /> 23 | <Card 24 | href="./adapters/mercurius" 25 | title="Mercurius" 26 | description="Implement GraphQL servers and gateways with Fastify." 27 | /> 28 | <Card 29 | href="./adapters/hono" 30 | title="Hono" 31 | description="Hono is a small, simple, and extremely fast web framework built based on web standards and capable of running in various JavaScript runtime environments." 32 | /> 33 | <Card 34 | href="./adapters/elysia" 35 | title="Elysia" 36 | description="Elysia is an ergonomic web framework for building backend servers with Bun" 37 | /> 38 | </Cards> -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/index.zh.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 适配器 3 | --- 4 | import { Card, Cards } from 'fumadocs-ui/components/card' 5 | 6 | 在 Node.js 生态中,有许多 GraphQL HTTP 适配器。 7 | 8 | 由于 GQLoom 编织的产物是标准的 GraphQL Schema 对象,因此可以无缝地与这些适配器工作。 9 | 10 | 以下是一些流行的适配器: 11 | 12 | <Cards> 13 | <Card 14 | href="./adapters/yoga" 15 | title="Yoga" 16 | description="重写了功能齐全的 GraphQL 服务器,重点关注简便的设置、性能和良好的开发人员体验。" 17 | /> 18 | <Card 19 | href="./adapters/apollo" 20 | title="Apollo Server" 21 | description="符合规范且可用于生产的 JavaScript GraphQL 服务器,可让您以模式优先的方式进行开发。专为 Express、Connect、Hapi、Koa 等而构建" 22 | /> 23 | <Card 24 | href="./adapters/mercurius" 25 | title="Mercurius" 26 | description="使用 Fastify 实现 GraphQL 服务器和网关。" 27 | /> 28 | <Card 29 | href="./adapters/hono" 30 | title="Hono" 31 | description="Hono 是一个小巧、简单且超快速的 Web 框架,基于 Web 标准构建,能够在多种 JavaScript 运行时环境中运行。" 32 | /> 33 | <Card 34 | href="./adapters/elysia" 35 | title="Elysia" 36 | description="Elysia 是一个符合人体工程学的 Web 框架,用于使用 Bun 构建后端服务器。" 37 | /> 38 | </Cards> -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/mercurius.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mercurius 3 | --- 4 | 5 | [Mercurius](https://mercurius.dev/) is a GraphQL adapter for [Fastify](https://www.fastify.io/) 6 | 7 | ## Installation 8 | 9 | ```sh tab="npm" 10 | npm i fastify mercurius graphql @gqloom/core 11 | ``` 12 | ```sh tab="pnpm" 13 | pnpm add fastify mercurius graphql @gqloom/core 14 | ``` 15 | ```sh tab="yarn" 16 | yarn add fastify mercurius graphql @gqloom/core 17 | ``` 18 | ```sh tab="bun" 19 | bun add fastify mercurius graphql @gqloom/core 20 | ``` 21 | 22 | ## Usage 23 | ```ts twoslash 24 | // @filename: resolvers.ts 25 | import { resolver, query, silk, weave } from "@gqloom/core" 26 | import { GraphQLNonNull, GraphQLString } from "graphql" 27 | import { createServer } from "node:http" 28 | import { createYoga } from "graphql-yoga" 29 | 30 | export const helloResolver = resolver({ 31 | hello: query( 32 | silk<string>(new GraphQLNonNull(GraphQLString)), 33 | () => "Hello, World" 34 | ), 35 | }) 36 | // @filename: index.ts 37 | // ---cut--- 38 | import { weave } from "@gqloom/core" 39 | import Fastify from "fastify" 40 | import mercurius from "mercurius" 41 | import { helloResolver } from "./resolvers" 42 | 43 | const schema = weave(helloResolver) 44 | 45 | const app = Fastify() 46 | app.register(mercurius, { schema }) 47 | app.listen({ port: 4000 }, () => { 48 | console.info("Mercurius server is running on http://localhost:4000") 49 | }) 50 | ``` 51 | 52 | ## Contexts 53 | 54 | When using GQLoom together with `Mercurius`, you can use `MercuriusContext` to label the type of context: 55 | 56 | ```ts twoslash 57 | import { useContext } from "@gqloom/core/context" 58 | import { type MercuriusContext } from "mercurius" 59 | 60 | export function useAuthorization() { 61 | return useContext<MercuriusContext>().reply.request.headers.authorization 62 | } 63 | ``` 64 | 65 | You can also learn more about contexts in the [Mercurius documentation](https://mercurius.dev/#/docs/context). -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/mercurius.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mercurius 3 | --- 4 | 5 | [Mercurius](https://mercurius.dev/) 是用于 [Fastify](https://www.fastify.io/) 的 GraphQL 适配器 6 | 7 | ## 安装 8 | 9 | ```sh tab="npm" 10 | npm i fastify mercurius graphql @gqloom/core 11 | ``` 12 | ```sh tab="pnpm" 13 | pnpm add fastify mercurius graphql @gqloom/core 14 | ``` 15 | ```sh tab="yarn" 16 | yarn add fastify mercurius graphql @gqloom/core 17 | ``` 18 | ```sh tab="bun" 19 | bun add fastify mercurius graphql @gqloom/core 20 | ``` 21 | 22 | ## 使用 23 | ```ts twoslash 24 | // @filename: resolvers.ts 25 | import { resolver, query, silk, weave } from "@gqloom/core" 26 | import { GraphQLNonNull, GraphQLString } from "graphql" 27 | import { createServer } from "node:http" 28 | import { createYoga } from "graphql-yoga" 29 | 30 | export const helloResolver = resolver({ 31 | hello: query( 32 | silk<string>(new GraphQLNonNull(GraphQLString)), 33 | () => "Hello, World" 34 | ), 35 | }) 36 | // @filename: index.ts 37 | // ---cut--- 38 | import { weave } from "@gqloom/core" 39 | import Fastify from "fastify" 40 | import mercurius from "mercurius" 41 | import { helloResolver } from "./resolvers" 42 | 43 | const schema = weave(helloResolver) 44 | 45 | const app = Fastify() 46 | app.register(mercurius, { schema }) 47 | app.listen({ port: 4000 }, () => { 48 | console.info("Mercurius server is running on http://localhost:4000") 49 | }) 50 | ``` 51 | 52 | ## 上下文 53 | 54 | 在与 `Mercurius` 同时使用 GQLoom 时,你可以使用 `MercuriusContext` 来标注上下的类型: 55 | 56 | ```ts twoslash 57 | import { useContext } from "@gqloom/core/context" 58 | import { type MercuriusContext } from "mercurius" 59 | 60 | export function useAuthorization() { 61 | return useContext<MercuriusContext>().reply.request.headers.authorization 62 | } 63 | ``` 64 | 65 | 你还可以在 [Mercurius 文档](https://mercurius.dev/#/docs/context)中了解更多关于上下文的信息。 -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/yoga.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Yoga 3 | --- 4 | 5 | GraphQL Yoga is a batteries-included cross-platform [GraphQL over HTTP spec-compliant](https://github.com/enisdenjo/graphql-http/tree/master/implementations/graphql-yoga) GraphQL server 6 | powered by [Envelop](https://envelop.dev) and [GraphQL Tools](https://graphql-tools.com) that runs anywhere; 7 | focused on easy setup, performance and great developer experience. 8 | 9 | ## Installation 10 | 11 | ```sh tab="npm" 12 | npm i graphql graphql-yoga @gqloom/core 13 | ``` 14 | ```sh tab="pnpm" 15 | pnpm add graphql graphql-yoga @gqloom/core 16 | ``` 17 | ```sh tab="yarn" 18 | yarn add graphql graphql-yoga @gqloom/core 19 | ``` 20 | ```sh tab="bun" 21 | bun add graphql graphql-yoga @gqloom/core 22 | ``` 23 | 24 | ## Usage 25 | 26 | ```ts twoslash 27 | // @filename: resolvers.ts 28 | import { resolver, query, silk, weave } from "@gqloom/core" 29 | import { GraphQLNonNull, GraphQLString } from "graphql" 30 | import { createServer } from "node:http" 31 | import { createYoga } from "graphql-yoga" 32 | 33 | export const helloResolver = resolver({ 34 | hello: query( 35 | silk<string>(new GraphQLNonNull(GraphQLString)), 36 | () => "Hello, World" 37 | ), 38 | }) 39 | // @filename: index.ts 40 | // ---cut--- 41 | import { weave } from "@gqloom/core" 42 | import { createServer } from "node:http" 43 | import { createYoga } from "graphql-yoga" 44 | import { helloResolver } from "./resolvers" 45 | 46 | const schema = weave(helloResolver) 47 | 48 | const yoga = createYoga({ schema }) 49 | 50 | createServer(yoga).listen(4000, () => { 51 | console.info("Server is running on http://localhost:4000/graphql") 52 | }) 53 | ``` 54 | 55 | ## Contexts 56 | 57 | When using GQLoom together with `Yoga`, you can use `YogaInitialContext` to label the type of context: 58 | 59 | ```ts twoslash 60 | import { useContext } from "@gqloom/core/context" 61 | import { type YogaInitialContext } from "graphql-yoga" 62 | 63 | export function useAuthorization() { 64 | return useContext<YogaInitialContext>().request.headers.get("Authorization") 65 | } 66 | ``` 67 | 68 | You can also learn more about contexts in the [Yoga documentation](https://the-guild.dev/graphql/yoga-server/docs/features/context). -------------------------------------------------------------------------------- /website/content/docs/advanced/adapters/yoga.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Yoga 3 | --- 4 | 5 | [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server) 是一款包含电池的跨平台 [GraphQL over HTTP 规范兼容](<(https://github.com/enisdenjo/graphql-http/tree/master/implementations/graphql-yoga)>)的 GraphQL 服务器, 6 | 由 [Envelop](https://envelop.dev/) 和 [GraphQL Tools](https://graphql-tools.com/) 提供支持,可在任何地方运行; 7 | 重点在于简易设置、性能和良好的开发人员体验。 8 | 9 | ## 安装 10 | 11 | ```sh tab="npm" 12 | npm i graphql graphql-yoga @gqloom/core 13 | ``` 14 | ```sh tab="pnpm" 15 | pnpm add graphql graphql-yoga @gqloom/core 16 | ``` 17 | ```sh tab="yarn" 18 | yarn add graphql graphql-yoga @gqloom/core 19 | ``` 20 | ```sh tab="bun" 21 | bun add graphql graphql-yoga @gqloom/core 22 | ``` 23 | 24 | ## 使用 25 | 26 | ```ts twoslash 27 | // @filename: resolvers.ts 28 | import { resolver, query, silk, weave } from "@gqloom/core" 29 | import { GraphQLNonNull, GraphQLString } from "graphql" 30 | import { createServer } from "node:http" 31 | import { createYoga } from "graphql-yoga" 32 | 33 | export const helloResolver = resolver({ 34 | hello: query( 35 | silk<string>(new GraphQLNonNull(GraphQLString)), 36 | () => "Hello, World" 37 | ), 38 | }) 39 | // @filename: index.ts 40 | // ---cut--- 41 | import { weave } from "@gqloom/core" 42 | import { createServer } from "node:http" 43 | import { createYoga } from "graphql-yoga" 44 | import { helloResolver } from "./resolvers" 45 | 46 | const schema = weave(helloResolver) 47 | 48 | const yoga = createYoga({ schema }) 49 | 50 | createServer(yoga).listen(4000, () => { 51 | console.info("Server is running on http://localhost:4000/graphql") 52 | }) 53 | ``` 54 | 55 | ## 上下文 56 | 57 | 在与 `Yoga` 同时使用 GQLoom 时,你可以使用 `YogaInitialContext` 来标注上下的类型: 58 | 59 | ```ts twoslash 60 | import { useContext } from "@gqloom/core/context" 61 | import { type YogaInitialContext } from "graphql-yoga" 62 | 63 | export function useAuthorization() { 64 | return useContext<YogaInitialContext>().request.headers.get("Authorization") 65 | } 66 | ``` 67 | 68 | 你还可以在 [Yoga 文档](https://the-guild.dev/graphql/yoga-server/docs/features/context)中了解更多关于上下文的信息。 -------------------------------------------------------------------------------- /website/content/docs/advanced/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "adapters", 4 | "printing-schema", 5 | "executor", 6 | "subscription", 7 | "federation" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /website/content/docs/advanced/printing-schema.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Printing Schema 3 | --- 4 | 5 | The GraphQL Schema file is the core document that defines the data structure and operations of the GraphQL API. It uses the GraphQL Schema Definition Language (SDL) to describe information such as data types, fields, queries, mutations, and subscriptions. It serves as the basis for server-side request processing and also provides an interface document for the client, helping developers understand the available data and operations. 6 | 7 | ## Generating files from Schema 8 | 9 | We can use the `printSchema` function from the `graphql` package to print out the Schema. 10 | 11 | ```ts twoslash 12 | // @filename: resolvers.ts 13 | import { query, resolver, weave } from "@gqloom/valibot" 14 | import * as v from "valibot" 15 | import { createServer } from "node:http" 16 | import { createYoga } from "graphql-yoga" 17 | 18 | export const helloResolver = resolver({ 19 | hello: query(v.string(), () => "Hello, World"), 20 | }) 21 | 22 | // @filename: main.ts 23 | // ---cut--- 24 | import { weave } from "@gqloom/core" 25 | import { printSchema, lexicographicSortSchema } from "graphql" 26 | import { helloResolver } from "./resolvers" 27 | import * as fs from "fs" 28 | 29 | const schema = weave(helloResolver) 30 | 31 | const schemaText = printSchema(lexicographicSortSchema(schema)) 32 | 33 | if (process.env.NODE_ENV === "development") { 34 | fs.writeFileSync("schema.graphql", schemaText) 35 | } 36 | ``` 37 | 38 | The above code generates a `schema.graphql` file that contains all the contents of the Schema. 39 | 40 | ## Using GraphQL Schema 41 | 42 | GraphQL Schema can be used for many purposes, common uses include: 43 | 44 | - Merging Schema from multiple microservices into a [supergraph](https://www.apollographql.com/docs/federation/building-supergraphs/subgraphs-overview) for unified cross-service querying on the client side. This architecture is called [federation](./federation). 45 | 46 | - Developed and type-checked on the client side using [code generation](https://the-guild.dev/graphql/codegen). 47 | 48 | - Integrate with TypeScript for client-side development for better type checking and auto-completion during development, see [gql.tada](https://gql-tada.0no.co/) for more information. 49 | -------------------------------------------------------------------------------- /website/content/docs/advanced/printing-schema.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 打印 Schema 3 | --- 4 | 5 | GraphQL Schema 文件是定义 GraphQL API 数据结构和操作的核心文件,它使用 GraphQL Schema Definition Language (SDL) 描述了数据类型、字段、查询(queries)、变更(mutations)和订阅(subscriptions)等信息,既作为服务器端处理请求的基础,也为客户端提供了接口文档,帮助开发者了解可用的数据和操作。 6 | 7 | ## 从 Schema 生成文件 8 | 9 | 我们可以使用来自 `graphql` 包的 `printSchema` 函数来打印出 Schema。 10 | 11 | ```ts twoslash 12 | // @filename: resolvers.ts 13 | import { query, resolver, weave } from "@gqloom/valibot" 14 | import * as v from "valibot" 15 | import { createServer } from "node:http" 16 | import { createYoga } from "graphql-yoga" 17 | 18 | export const helloResolver = resolver({ 19 | hello: query(v.string(), () => "Hello, World"), 20 | }) 21 | 22 | // @filename: main.ts 23 | // ---cut--- 24 | import { weave } from "@gqloom/core" 25 | import { printSchema, lexicographicSortSchema } from "graphql" 26 | import { helloResolver } from "./resolvers" 27 | import * as fs from "fs" 28 | 29 | const schema = weave(helloResolver) 30 | 31 | const schemaText = printSchema(lexicographicSortSchema(schema)) 32 | 33 | if (process.env.NODE_ENV === "development") { 34 | fs.writeFileSync("schema.graphql", schemaText) 35 | } 36 | ``` 37 | 38 | 上面的代码会生成一个 `schema.graphql` 文件,其中包含 Schema 的所有内容。 39 | 40 | ## 使用 GraphQL Schema 41 | 42 | GraphQL Schema 可以用于很多用途,常见的用途包括: 43 | 44 | - 将来自多个微服务的 Schema 合并成一个[超级图](https://www.apollographql.com/docs/federation/building-supergraphs/subgraphs-overview),以便在客户端进行跨服务的统一查询。这种架构被称为[联邦](./federation)。 45 | 46 | - 在客户端使用[代码生成](https://the-guild.dev/graphql/codegen)进行开发和类型检查。 47 | 48 | - 在客户端开发时与 TypeScript 集成,以便在开发过程中获得更好的类型检查和自动补全,更多信息请参阅 [gql.tada](https://gql-tada.0no.co/)。 49 | -------------------------------------------------------------------------------- /website/content/docs/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "index", 4 | "getting-started", 5 | "silk", 6 | "resolver", 7 | "weave", 8 | "context", 9 | "dataloader", 10 | "middleware", 11 | "---Schema Integration---", 12 | "...schema", 13 | "---Advanced Usages---", 14 | "...advanced", 15 | "---LLM---", 16 | "[llms.txt](/en/llms.txt)" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /website/content/docs/meta.zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "index", 4 | "getting-started", 5 | "silk", 6 | "resolver", 7 | "weave", 8 | "context", 9 | "dataloader", 10 | "middleware", 11 | "---Schema 集成---", 12 | "...schema", 13 | "---进阶功能---", 14 | "...advanced", 15 | "---LLM---", 16 | "[llms.txt](/zh/llms.txt)" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /website/content/docs/schema/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": ["valibot", "zod", "yup", "drizzle", "prisma", "mikro-orm"] 3 | } 4 | -------------------------------------------------------------------------------- /website/content/home/schema-graphql.md: -------------------------------------------------------------------------------- 1 | ```graphql title="schema.graphql" 2 | type Giraffe { 3 | """The giraffe's name""" 4 | name: String! 5 | birthday: String! 6 | age(currentDate: String): Int! 7 | } 8 | ``` -------------------------------------------------------------------------------- /website/lib/i18n.ts: -------------------------------------------------------------------------------- 1 | import type { I18nConfig } from "fumadocs-core/i18n" 2 | 3 | export const i18n: I18nConfig = { 4 | defaultLanguage: "en", 5 | languages: ["en", "zh"], 6 | } 7 | -------------------------------------------------------------------------------- /website/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { docs, home, meta } from "@/.source" 2 | import { loader } from "fumadocs-core/source" 3 | import { createMDXSource } from "fumadocs-mdx" 4 | import { icons } from "lucide-react" 5 | import { createElement } from "react" 6 | import { i18n } from "./i18n" 7 | 8 | const icon = (icon: string | undefined) => { 9 | if (!icon) return 10 | 11 | if (icon in icons) return createElement(icons[icon as keyof typeof icons]) 12 | } 13 | export const source = loader({ 14 | baseUrl: "/docs", 15 | source: createMDXSource(docs, meta), 16 | i18n, 17 | icon, 18 | }) 19 | 20 | export const homeSource = loader({ 21 | baseUrl: "/", 22 | source: createMDXSource(home, home), 23 | }) 24 | -------------------------------------------------------------------------------- /website/middleware.ts: -------------------------------------------------------------------------------- 1 | import { createI18nMiddleware } from "fumadocs-core/i18n" 2 | import { type NextMiddleware, NextResponse } from "next/server" 3 | import { i18n } from "./lib/i18n" 4 | 5 | const removeHtmlSuffix: NextMiddleware = (request) => { 6 | const url = new URL(request.url) 7 | let pathname = url.pathname 8 | 9 | // 移除 .html 后缀 10 | if (pathname.endsWith(".html")) { 11 | pathname = pathname.slice(0, -5) 12 | } 13 | 14 | // 移除 /index 后缀 15 | if (pathname.endsWith("/index")) { 16 | pathname = pathname.slice(0, -6) 17 | } 18 | 19 | // 如果路径发生了变化,重定向到新的路径 20 | if (pathname !== url.pathname) { 21 | url.pathname = pathname 22 | return NextResponse.redirect(url) 23 | } 24 | 25 | // 如果路径没有变化,继续下一个中间件 26 | return 27 | } 28 | 29 | export default composeMiddlewares(removeHtmlSuffix, createI18nMiddleware(i18n)) 30 | 31 | export const config = { 32 | // Matcher ignoring `/_next/` and `/api/` 33 | matcher: ["/((?!api|_next/static|_next/image|favicon.ico|gqloom.svg).*)"], 34 | } 35 | 36 | /** 37 | * - Registers as many middlewares as needed. 38 | * 39 | * - Middlewares are invoked in the order they were registered. 40 | * 41 | * - The first middleware to return the instance of NextResponse breaks the chain. 42 | * 43 | * - As in the next docs, middlewares are invoked for every request including next 44 | * requests to fetch static assets and the sorts. 45 | */ 46 | function composeMiddlewares(...middlewares: NextMiddleware[]): NextMiddleware { 47 | const validMiddlewares = middlewares.reduce((acc, _middleware) => { 48 | if (typeof _middleware === "function") { 49 | return [...acc, _middleware] 50 | } 51 | 52 | return acc 53 | }, [] as NextMiddleware[]) 54 | 55 | return async function middleware(request, evt) { 56 | for (const _middleware of validMiddlewares) { 57 | const result = await _middleware(request, evt) 58 | 59 | if (result instanceof NextResponse) { 60 | return result 61 | } 62 | } 63 | 64 | return NextResponse.next() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /website/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { createMDX } from "fumadocs-mdx/next" 2 | 3 | const withMDX = createMDX() 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const config = { 7 | reactStrictMode: true, 8 | redirects: async () => [ 9 | { 10 | source: "/guide/schema-integration/:slug", 11 | destination: "/en/docs/schema/:slug", 12 | permanent: true, 13 | }, 14 | { 15 | source: "/zh/guide/schema-integration/:slug", 16 | destination: "/zh/docs/schema/:slug", 17 | permanent: true, 18 | }, 19 | { 20 | source: "/guide/introduction.html", 21 | destination: "/docs", 22 | permanent: true, 23 | }, 24 | { 25 | source: "/guide/:slug", 26 | destination: "/en/docs/:slug", 27 | permanent: true, 28 | }, 29 | { 30 | source: "/zh/guide/:slug", 31 | destination: "/zh/docs/:slug", 32 | permanent: true, 33 | }, 34 | ], 35 | serverExternalPackages: ["@node-rs/jieba", "twoslash"], 36 | experimental: { 37 | webpackMemoryOptimizations: true, 38 | }, 39 | } 40 | 41 | export default withMDX(config) 42 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "pnpm -w run build && next build", 7 | "dev": "next dev", 8 | "start": "next start", 9 | "postinstall": "fumadocs-mdx" 10 | }, 11 | "dependencies": { 12 | "@node-rs/jieba": "^2.0.1", 13 | "@orama/orama": "^3.1.6", 14 | "clsx": "^2.1.1", 15 | "colord": "^2.9.3", 16 | "fast-glob": "^3.3.3", 17 | "fumadocs-core": "15.3.0", 18 | "fumadocs-docgen": "^2.0.0", 19 | "fumadocs-mdx": "11.6.3", 20 | "fumadocs-twoslash": "^3.1.2", 21 | "fumadocs-ui": "15.3.0", 22 | "gray-matter": "^4.0.3", 23 | "lucide-react": "^0.475.0", 24 | "next": "15.3.1", 25 | "react": "^19.0.0", 26 | "react-dom": "^19.0.0", 27 | "remark": "^15.0.1", 28 | "remark-gfm": "^4.0.1", 29 | "remark-mdx": "^3.1.0", 30 | "remark-stringify": "^11.0.0", 31 | "yaml": "^2.7.1" 32 | }, 33 | "devDependencies": { 34 | "@apollo/server": "^4.10.4", 35 | "@apollo/subgraph": "^2.8.2", 36 | "@fumadocs/cli": "^0.1.1", 37 | "@gqloom/core": "workspace:*", 38 | "@gqloom/drizzle": "workspace:*", 39 | "@gqloom/federation": "workspace:*", 40 | "@gqloom/mikro-orm": "workspace:*", 41 | "@gqloom/prisma": "workspace:*", 42 | "@gqloom/valibot": "workspace:*", 43 | "@gqloom/yup": "workspace:*", 44 | "@gqloom/zod": "workspace:*", 45 | "@mikro-orm/core": "^6.4.6", 46 | "@mikro-orm/postgresql": "^6.4.6", 47 | "@tailwindcss/postcss": "^4.0.3", 48 | "@types/mdx": "^2.0.13", 49 | "@types/node": "22.13.0", 50 | "@types/react": "^19.0.8", 51 | "@types/react-dom": "^19.0.3", 52 | "dotenv": "^16.4.7", 53 | "drizzle-kit": "^0.30.1", 54 | "drizzle-orm": "^0.39.3", 55 | "fastify": "^4.28.1", 56 | "graphql": "^16.8.1", 57 | "graphql-scalars": "^1.24.1", 58 | "graphql-yoga": "^5.6.0", 59 | "mercurius": "^14.1.0", 60 | "postcss": "^8.5.1", 61 | "tailwindcss": "^4.0.3", 62 | "typescript": "^5.7.3", 63 | "valibot": "1.0.0-beta.15", 64 | "yup": "^1.4.0", 65 | "zod": "3.25.17" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /website/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /website/public/gqloom.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 360 360"><defs><clipPath id="a"><rect width="360" height="360" rx="0"/></clipPath></defs><g clip-path="url(#a)"><path fill="#3068B7" fill-rule="evenodd" d="M178.44 179.75q61.1 20.12 120.73 30.18l1.66-9.86q-58.87-9.94-119.27-29.82-60.39-19.88-119.3-49.71l-4.52 8.92q59.59 30.17 120.7 50.29Z"/><path fill="#23C8D2" fill-rule="evenodd" d="M300 250H60v-10h240v10Z"/><path fill="#FBBF24" fill-rule="evenodd" d="M178.79 198.35q46.23 11.56 120.47 22.6l1.47-9.9q-73.75-10.96-119.52-22.4-45.77-11.44-119.55-37.37l-3.32 9.44q74.22 26.07 120.45 37.63Z"/><path fill="#CA5BF2" fill-rule="evenodd" d="M178.97 215.9q26.26 5.55 120.53 15.07l1-9.94q-93.73-9.48-119.47-14.92l-120.01-25-2.04 9.78 119.99 25Z"/><path fill="#F5775B" fill-rule="evenodd" d="M179.5 233.47Q204.11 236 299.73 241l.52-9.98q-95.37-5-119.75-7.48l-120-12.5-1.03 9.94 120.01 12.5Z"/><path fill="#C12E8C" fill-rule="evenodd" d="M312.5 110.43q0-.4-.02-.79l-.08-.78q-.05-.39-.13-.77-.07-.39-.18-.77-.1-.38-.22-.75-.13-.37-.28-.73-.15-.37-.33-.72-.17-.35-.37-.7l-.41-.66q-.22-.33-.46-.64l-.5-.6q-.25-.3-.53-.58-.28-.27-.58-.53l-.6-.5q-.31-.24-.64-.46-.33-.22-.67-.41L186 30.46q-.34-.2-.7-.37-.34-.17-.7-.32l-.74-.28-.75-.22q-.38-.1-.77-.18-.38-.08-.77-.13l-.78-.08-.79-.02q-.4 0-.78.02-.4.03-.79.08t-.77.13q-.39.07-.77.18-.38.1-.75.22-.37.13-.73.28-.37.15-.72.32l-.69.37-120.5 69.58-.67.41q-.33.22-.64.46l-.6.5q-.3.26-.58.53-.27.28-.53.58l-.5.6q-.24.31-.46.64-.22.33-.41.67-.2.34-.37.7-.18.34-.33.7l-.28.74q-.12.37-.22.75t-.18.77q-.08.38-.13.77l-.08.78-.02.79v139.14q0 .4.02.79l.08.78q.05.39.13.77.08.39.18.77.1.38.23.75.12.37.27.73.15.37.33.72.17.35.37.7l.41.66.46.64.5.6q.26.3.53.58.28.28.58.53l.6.5q.31.24.64.46.33.22.67.41L174 329.54q.34.2.7.37.34.17.7.32l.74.28.75.23q.38.1.77.17.38.08.77.13l.78.08.79.02q.4 0 .78-.02.4-.03.79-.08t.77-.13q.39-.07.77-.18.38-.1.75-.22.37-.13.73-.28.37-.15.72-.32l.69-.37 120.5-69.58.67-.41.64-.46.6-.5q.3-.25.58-.53.27-.28.53-.58l.5-.6q.24-.31.46-.64.22-.33.41-.67.2-.34.37-.7.18-.34.33-.7l.28-.74q.12-.37.22-.75t.18-.77q.08-.38.13-.77l.08-.78.02-.79V110.43Zm-16 2.3L180 45.49 63.5 112.74v134.52L180 314.52l116.5-67.26V112.74Z"/><circle cx="180" cy="44" r="24" fill="#C12E8C"/><circle cx="180" cy="314" r="24" fill="#C12E8C"/><circle cx="61" cy="250" r="24" fill="#C12E8C"/><circle cx="304" cy="250" r="24" fill="#C12E8C"/><circle cx="61" cy="108" r="24" fill="#C12E8C"/><circle cx="304" cy="108" r="24" fill="#C12E8C"/></g></svg> -------------------------------------------------------------------------------- /website/source.config.ts: -------------------------------------------------------------------------------- 1 | import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins" 2 | import { remarkInstall } from "fumadocs-docgen" 3 | import { 4 | defineCollections, 5 | defineConfig, 6 | defineDocs, 7 | } from "fumadocs-mdx/config" 8 | import { transformerTwoslash } from "fumadocs-twoslash" 9 | import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs" 10 | 11 | export const { docs, meta } = defineDocs({ 12 | dir: "content/docs", 13 | }) 14 | 15 | export const home = defineCollections({ 16 | type: "doc", 17 | dir: "content/home", 18 | }) 19 | 20 | export default defineConfig({ 21 | mdxOptions: { 22 | remarkPlugins: [remarkInstall], 23 | rehypeCodeOptions: { 24 | langs: ["ts", "js", "json", "bash", "graphql", "gql"], 25 | themes: { 26 | light: "github-light", 27 | dark: "github-dark", 28 | }, 29 | transformers: [ 30 | ...(rehypeCodeDefaultOptions.transformers ?? []), 31 | process.env.NODE_ENV === "production" && 32 | transformerTwoslash({ typesCache: createFileSystemTypesCache() }), 33 | ].filter(notFalse), 34 | }, 35 | }, 36 | }) 37 | 38 | function notFalse<T>(value: T | false): value is T { 39 | return value !== false 40 | } 41 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "paths": { 19 | "@/*": ["./*"] 20 | }, 21 | "plugins": [ 22 | { 23 | "name": "next" 24 | } 25 | ] 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules", "**/@orama/**/*"] 29 | } 30 | --------------------------------------------------------------------------------