Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(solid-query): add plugin #474

Merged
merged 5 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/plugins/typescript/solid-query/.changeset/README.md
qlaffont marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool
that works with multi-package repos, or single-package repos to help you version and publish your
code. You can find the full documentation for it
[in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
11 changes: 11 additions & 0 deletions packages/plugins/typescript/solid-query/.changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-codegen/typescript-solid-query': major
---

feat: add solid-query pkg
1 change: 1 addition & 0 deletions packages/plugins/typescript/solid-query/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @graphql-codegen/typescript-solid-query
1 change: 1 addition & 0 deletions packages/plugins/typescript/solid-query/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../../../../jest.project')({ dirname: __dirname });
56 changes: 56 additions & 0 deletions packages/plugins/typescript/solid-query/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "@graphql-codegen/typescript-solid-query",
"version": "0.0.1",
"type": "module",
"description": "GraphQL Code Generator plugin for generating a ready-to-use Solid-Query Hooks based on GraphQL operations",
"repository": {
"type": "git",
"url": "https://github.com/dotansimha/graphql-code-generator-community.git",
"directory": "packages/plugins/typescript/solid-query"
},
"license": "MIT",
"engines": {
"node": ">= 18.0.0"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
".": {
"require": {
"types": "./dist/typings/index.d.cts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
},
"default": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./package.json": "./package.json"
},
"typings": "dist/typings/index.d.ts",
"scripts": {
"lint": "eslint **/*.ts",
"test": "jest --no-watchman --config ../../../../jest.config.js --forceExit"
},
"peerDependencies": {
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
},
"dependencies": {
"@graphql-codegen/plugin-helpers": "^3.0.0",
"@graphql-codegen/visitor-plugin-common": "2.13.1",
"auto-bind": "~4.0.0",
"change-case-all": "1.0.15",
"tslib": "~2.6.0"
},
"publishConfig": {
"directory": "dist",
"access": "public"
},
"typescript": {
"definition": "dist/typings/index.d.ts"
}
}
131 changes: 131 additions & 0 deletions packages/plugins/typescript/solid-query/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { RawClientSideBasePluginConfig } from '@graphql-codegen/visitor-plugin-common';

export type HardcodedFetch = { endpoint: string; fetchParams?: string | Record<string, any> };
export type CustomFetch = { func: string; isSolidHook?: boolean } | string;
export type GraphQlRequest = 'graphql-request' | { clientImportPath: string };

export interface BaseSolidQueryPluginConfig {
/**
* @default unknown
* @description Changes the default "TError" generic type.
*/
errorType?: string;

/**
* @default false
* @description For each generate query hook adds `document` field with a
* corresponding GraphQL query. Useful for `queryClient.fetchQuery`.
* @exampleMarkdown
* ```ts
* queryClient.fetchQuery(
* useUserDetailsQuery.getKey(variables),
* () => gqlRequest(useUserDetailsQuery.document, variables)
* )
* ```
*/
exposeDocument?: boolean;

/**
* @default false
* @description For each generate query hook adds getKey(variables: QueryVariables) function. Useful for cache updates. If addInfiniteQuery is true, it will also add a getKey function to each infinite query.
* @exampleMarkdown
* ```ts
* const query = useUserDetailsQuery(...)
* const key = useUserDetailsQuery.getKey({ id: theUsersId })
* // use key in a cache update after a mutation
* ```
*/
exposeQueryKeys?: boolean;

/**
* @default false
* @description For each generate query hook adds rootKey. Useful for cache updates.
* @exampleMarkdown
* ```ts
* const query = useUserDetailsQuery(...)
* const key = useUserDetailsQuery.rootKey
* // use key in a cache update after a mutation
* ```
*/
exposeQueryRootKeys?: boolean;

/**
* @default false
* @description For each generate mutation hook adds getKey() function. Useful for call outside of functional component.
* @exampleMarkdown
* ```ts
* const mutation = useUserDetailsMutation(...)
* const key = useUserDetailsMutation.getKey()
* ```
*/
exposeMutationKeys?: boolean;

/**
* @default false
* @description For each generate query hook adds `fetcher` field with a corresponding GraphQL query using the fetcher.
* It is useful for `queryClient.fetchQuery` and `queryClient.prefetchQuery`.
* @exampleMarkdown
* ```ts
* await queryClient.prefetchQuery(userQuery.getKey(), () => userQuery.fetcher())
* ```
*/
exposeFetcher?: boolean;

/**
* @default false
* @description Adds an Infinite Query along side the standard one
*/
addInfiniteQuery?: boolean;

/**
* @default false
* @description Adds a Suspense Query along side the standard one
*/
addSuspenseQuery?: boolean;

/**
* @default empty
* @description Add custom import for solid-query.
* It can be used to import from `@tanstack/solid-query` instead of `solid-query`. But make sure it include createQuery, CreateQueryOptions, createMutation, CreateMutationOptions, createInfiniteQuery, CreateInfiniteQueryOptions
*
* The following options are available to use:
*
* - "src/your-own-solid-query-customized": import { createQuery, CreateQueryOptions, createMutation, CreateMutationOptions, createInfiniteQuery, CreateInfiniteQueryOptions } from your own solid-query customized package.
*/
solidQueryImportFrom?: string;
}

/**
* @description This plugin generates `Solid-Query` Hooks with TypeScript typings.
*
* It extends the basic TypeScript plugins: `@graphql-codegen/typescript`, `@graphql-codegen/typescript-operations` - and thus shares a similar configuration.
*
* > **If you are using the `solid-query` package instead of the `@tanstack/solid-query` package in your project, please set the `legacyMode` option to `true`.**
*
*/
export interface SolidQueryRawPluginConfig
extends Omit<
RawClientSideBasePluginConfig,
| 'documentMode'
| 'noGraphQLTag'
| 'gqlImport'
| 'documentNodeImport'
| 'noExport'
| 'importOperationTypesFrom'
| 'importDocumentNodeExternallyFrom'
| 'useTypeImports'
| 'legacyMode'
>,
BaseSolidQueryPluginConfig {
/**
* @description Customize the fetcher you wish to use in the generated file. Solid-Query is agnostic to the data-fetching layer, so you should provide it, or use a custom one.
*
* The following options are available to use:
*
* - 'fetch' - requires you to specify endpoint and headers on each call, and uses `fetch` to do the actual http call.
* - `{ endpoint: string, fetchParams: RequestInit }`: hardcode your endpoint and fetch options into the generated output, using the environment `fetch` method. You can also use `process.env.MY_VAR` as endpoint or header value.
* - `file#identifier` - You can use custom fetcher method that should implement the exported `SolidQueryFetcher` interface. Example: `./my-fetcher#myCustomFetcher`.
* - `graphql-request`: Will generate each hook with `client` argument, where you should pass your own `GraphQLClient` (created from `graphql-request`).
*/
fetcher?: 'fetch' | HardcodedFetch | GraphQlRequest | CustomFetch;
}
115 changes: 115 additions & 0 deletions packages/plugins/typescript/solid-query/src/fetcher-custom-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import autoBind from 'auto-bind';
import {
buildMapperImport,
ParsedMapper,
parseMapper,
} from '@graphql-codegen/visitor-plugin-common';
import { CustomFetch } from './config.js';
import { FetcherRenderer, type GenerateConfig } from './fetcher.js';
import { SolidQueryVisitor } from './visitor.js';

export class CustomMapperFetcher extends FetcherRenderer {
private _mapper: ParsedMapper;
private _isSolidHook: boolean;

constructor(protected visitor: SolidQueryVisitor, customFetcher: CustomFetch) {
super(visitor);
if (typeof customFetcher === 'string') {
customFetcher = { func: customFetcher };
}
this._mapper = parseMapper(customFetcher.func);
this._isSolidHook = customFetcher.isSolidHook;
autoBind(this);
}

private getFetcherFnName(operationResultType: string, operationVariablesTypes: string): string {
return `${this._mapper.type}<${operationResultType}, ${operationVariablesTypes}>`;
}

generateFetcherImplementation(): string {
if (this._mapper.isExternal) {
return buildMapperImport(
this._mapper.source,
[
{
identifier: this._mapper.type,
asDefault: this._mapper.default,
},
],
this.visitor.config.useTypeImports,
);
}

return null;
}

generateInfiniteQueryHook(config: GenerateConfig, isSuspense = false): string {
const { documentVariableName, operationResultType, operationVariablesTypes } = config;

const typedFetcher = this.getFetcherFnName(operationResultType, operationVariablesTypes);
const implHookOuter = this._isSolidHook
? `const query = ${typedFetcher}(${documentVariableName})`
: '';
const implFetcher = this._isSolidHook
? `(metaData) => query({...variables, ...(metaData.pageParam ?? {})})`
: `(metaData) => ${typedFetcher}(${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})})()`;

const { generateBaseInfiniteQueryHook } = this.generateInfiniteQueryHelper(config, isSuspense);

return generateBaseInfiniteQueryHook({
implHookOuter,
implFetcher,
});
}

generateQueryHook(config: GenerateConfig, isSuspense = false): string {
const { generateBaseQueryHook } = this.generateQueryHelper(config, isSuspense);

const { documentVariableName, operationResultType, operationVariablesTypes } = config;

const typedFetcher = this.getFetcherFnName(operationResultType, operationVariablesTypes);
const implFetcher = this._isSolidHook
? `${typedFetcher}(${documentVariableName}).bind(null, variables)`
: `${typedFetcher}(${documentVariableName}, variables)`;

return generateBaseQueryHook({
implFetcher,
});
}

generateMutationHook(config: GenerateConfig): string {
const { documentVariableName, operationResultType, operationVariablesTypes } = config;

const { generateBaseMutationHook, variables } = this.generateMutationHelper(config);

const typedFetcher = this.getFetcherFnName(operationResultType, operationVariablesTypes);
const implFetcher = this._isSolidHook
? `${typedFetcher}(${documentVariableName})`
: `(${variables}) => ${typedFetcher}(${documentVariableName}, variables)()`;

return generateBaseMutationHook({
implFetcher,
});
}

generateFetcherFetch(config: GenerateConfig): string {
const {
documentVariableName,
operationResultType,
operationVariablesTypes,
hasRequiredVariables,
operationName,
} = config;

// We can't generate a fetcher field since we can't call solid hooks outside of a Solid Fucntion Component
// Related: https://solidjs.org/docs/hooks-rules.html
if (this._isSolidHook) return '';

const variables = `variables${hasRequiredVariables ? '' : '?'}: ${operationVariablesTypes}`;

const typedFetcher = this.getFetcherFnName(operationResultType, operationVariablesTypes);
const impl = `${typedFetcher}(${documentVariableName}, variables, options)`;

return `\ncreate${operationName}.fetcher = (${variables}, options?: RequestInit['headers']) => ${impl};`;
}
}
Loading