Files
supabase/apps/docs/app/api/utils.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

137 lines
3.1 KiB
TypeScript

import { type PostgrestError } from '@supabase/supabase-js'
import { type ZodError } from 'zod'
import { isObject } from '~/features/helpers.misc'
type ObjectOrNever = object | never
export type ApiErrorGeneric = ApiError<ObjectOrNever>
export class ApiError<Details extends ObjectOrNever = never> extends Error {
constructor(
message: string,
public source?: unknown,
public details?: Details
) {
super(message)
}
isPrivate() {
return true
}
isUserError() {
return false
}
statusCode() {
return 500
}
}
export class InvalidRequestError<Details extends ObjectOrNever = never> extends ApiError<Details> {
constructor(message: string, source?: unknown, details?: Details) {
super(`Invalid request: ${message}`, source, details)
}
isPrivate() {
return false
}
isUserError() {
return true
}
statusCode() {
return 400
}
}
export class NoDataError<Details extends ObjectOrNever = never> extends ApiError<Details> {
constructor(message: string, source?: unknown, details?: Details) {
super(`Data not found: ${message}`, source, details)
}
isPrivate() {
return false
}
isUserError() {
return true
}
statusCode() {
return 404
}
}
export class MultiError<ErrorType = unknown, Details extends ObjectOrNever = never> extends Error {
constructor(
message: string,
cause?: Array<ErrorType>,
public details?: Details
) {
super(message, { cause })
}
get totalErrors(): number {
return (this.cause as Array<ErrorType>)?.length || 0
}
appendError(message: string, error: ErrorType): this {
this.message = `${this.message}\n\t${message}`
;((this.cause ?? (this.cause = [])) as Array<ErrorType>).push(error)
return this
}
}
export class CollectionQueryError extends Error {
constructor(
message: string,
public readonly queryErrors: {
count?: PostgrestError
data?: PostgrestError
}
) {
super(message)
}
public static fromErrors(
countError: PostgrestError | undefined,
dataError: PostgrestError | undefined
): CollectionQueryError {
const fetchFailedFor =
countError && dataError ? 'count and collection' : countError ? 'count' : 'collection'
return new CollectionQueryError(`Failed to fetch ${fetchFailedFor}`, {
count: countError,
data: dataError,
})
}
}
export function convertUnknownToApiError(error: unknown): ApiError {
return new ApiError('Unknown error', error)
}
export function convertPostgrestToApiError(error: PostgrestError): ApiError {
const message = `${error.code}: ${error.hint}`
return new ApiError(message, error)
}
export function convertZodToInvalidRequestError(
error: ZodError,
prelude?: string
): InvalidRequestError {
const issue = error.issues[0]
const pathStr = issue.path.join('.')
const message = `${prelude ? `${prelude}: ` : ''}${issue.message} at key "${pathStr}"`
return new InvalidRequestError(message, error)
}
export function extractMessageFromAnyError(error: unknown): string {
if (isObject(error) && 'message' in error && typeof error.message === 'string') {
return error.message
}
return String(error)
}