Files
supabase/apps/docs/resources/error/errorResolver.ts
Charis 4e916fc16a feat(graphql): add paginated errors collection query (#36149)
* feat(graphql): add paginated errors collection query

- Add new GraphQL query field 'errors' with cursor-based pagination

- Add UUID id column to content.error table for cursor pagination

- Implement error collection resolver with forward/backward pagination

- Add comprehensive test suite for pagination functionality

- Update database types and schema to support new error collection

- Add utility functions for handling collection queries and errors

- Add seed data for testing pagination scenarios

This change allows clients to efficiently paginate through error codes using cursor-based pagination, supporting both forward and backward traversal. The implementation follows the Relay connection specification and includes proper error handling and type safety.

* docs(graphql): add comprehensive GraphQL architecture documentation

Add detailed documentation for the docs GraphQL endpoint architecture, including:
- Modular query pattern and folder structure
- Step-by-step guide for creating new top-level queries
- Best practices for error handling, field optimization, and testing
- Code examples for schemas, models, resolvers, and tests

* feat(graphql): add service filtering to errors collection query

Enable filtering error codes by Supabase service in the GraphQL errors collection:
- Add optional service argument to errors query resolver
- Update error model to support service-based filtering in database queries
- Maintain pagination compatibility with service filtering
- Add comprehensive tests for service filtering with and without pagination

* feat(graphql): add service filtering and fix cursor encoding for errors collection

- Add service parameter to errors GraphQL query for filtering by Supabase service
- Implement base64 encoding/decoding for pagination cursors in error resolver
- Fix test cursor encoding to match resolver implementation
- Update GraphQL schema snapshot to reflect new service filter field

* docs(graphql): fix codegen instruction
2025-06-09 10:24:17 -04:00

130 lines
3.7 KiB
TypeScript

import { GraphQLError, GraphQLNonNull, GraphQLResolveInfo, GraphQLString } from 'graphql'
import type {
ErrorCollection,
RootQueryTypeErrorArgs,
RootQueryTypeErrorsArgs,
Service,
} from '~/__generated__/graphql'
import { ApiError, convertUnknownToApiError } from '~/app/api/utils'
import { Result } from '~/features/helpers.fn'
import {
createCollectionType,
GraphQLCollectionBuilder,
paginationArgs,
type CollectionFetch,
} from '../utils/connections'
import { ErrorModel } from './errorModel'
import {
GRAPHQL_FIELD_ERROR_GLOBAL,
GRAPHQL_FIELD_ERRORS_GLOBAL,
GraphQLEnumTypeService,
GraphQLObjectTypeError,
} from './errorSchema'
/**
* Encodes a string to base64
*/
function encodeBase64(str: string): string {
return Buffer.from(str, 'utf8').toString('base64')
}
/**
* Decodes a base64 string back to the original string
*/
function decodeBase64(base64: string): string {
return Buffer.from(base64, 'base64').toString('utf8')
}
async function resolveSingleError(
_parent: unknown,
args: RootQueryTypeErrorArgs,
_context: unknown,
_info: GraphQLResolveInfo
): Promise<ErrorModel | GraphQLError> {
return (
await Result.tryCatchFlat(ErrorModel.loadSingleError, convertUnknownToApiError, args)
).match(
(data) => data,
(error) => {
console.error(`Error resolving ${GRAPHQL_FIELD_ERROR_GLOBAL}:`, error)
return new GraphQLError(error.isPrivate() ? 'Internal Server Error' : error.message)
}
)
}
async function resolveErrors(
_parent: unknown,
args: RootQueryTypeErrorsArgs,
_context: unknown,
_info: GraphQLResolveInfo
): Promise<ErrorCollection | GraphQLError> {
return (
await Result.tryCatchFlat(
async (...args) => {
const fetch: CollectionFetch<ErrorModel, { service?: Service }, ApiError>['fetch'] = async (
fetchArgs
) => {
const result = await ErrorModel.loadErrors({
...fetchArgs,
additionalArgs: {
service: args[0].service ?? undefined,
},
})
return result.mapError((error) => new ApiError('Failed to resolve error codes', error))
}
return await GraphQLCollectionBuilder.create<ErrorModel, { service?: Service }, ApiError>({
fetch,
args: {
...args[0],
// Decode base64 cursors before passing to fetch function
after: args[0].after ? decodeBase64(args[0].after) : undefined,
before: args[0].before ? decodeBase64(args[0].before) : undefined,
},
getCursor: (item) => encodeBase64(item.id),
})
},
convertUnknownToApiError,
args
)
).match(
(data) => data as ErrorCollection,
(error) => {
console.error(`Error resolving ${GRAPHQL_FIELD_ERRORS_GLOBAL}:`, error)
return error instanceof GraphQLError
? error
: new GraphQLError(error.isPrivate() ? 'Internal Server Error' : error.message)
}
)
}
export const errorRoot = {
[GRAPHQL_FIELD_ERROR_GLOBAL]: {
description: 'Get the details of an error code returned from a Supabase service',
args: {
code: {
type: new GraphQLNonNull(GraphQLString),
},
service: {
type: new GraphQLNonNull(GraphQLEnumTypeService),
},
},
type: GraphQLObjectTypeError,
resolve: resolveSingleError,
},
}
export const errorsRoot = {
[GRAPHQL_FIELD_ERRORS_GLOBAL]: {
description: 'Get error codes that can potentially be returned by Supabase services',
args: {
...paginationArgs,
service: {
type: GraphQLEnumTypeService,
description: 'Filter errors by a specific Supabase service',
},
},
type: createCollectionType(GraphQLObjectTypeError),
resolve: resolveErrors,
},
}