Files
supabase/apps/docs/resources/error/errorModel.ts
Charis fba3f7bd45 feat(docs): add error code filtering to GraphQL errors endpoint (#36284)
- Add support for filtering errors by specific error codes
- Implement `errors` query that accepts array of error code IDs
- Add tests for error code filtering functionality
- Update GraphQL schema with new error filtering capabilities
2025-06-10 10:55:19 -04:00

213 lines
5.0 KiB
TypeScript

import { type PostgrestError } from '@supabase/supabase-js'
import {
ApiErrorGeneric,
CollectionQueryError,
convertPostgrestToApiError,
NoDataError,
} from '~/app/api/utils'
import { Result } from '~/features/helpers.fn'
import { supabase } from '~/lib/supabase'
import { type CollectionFetch } from '../utils/connections'
export const SERVICES = {
AUTH: {
value: 'AUTH',
},
REALTIME: {
value: 'REALTIME',
},
STORAGE: {
value: 'STORAGE',
},
} as const
type Service = keyof typeof SERVICES
type ErrorCollectionFetch = CollectionFetch<
ErrorModel,
{ service?: Service; code?: string }
>['fetch']
export class ErrorModel {
public id: string
public code: string
public service: Service
public httpStatusCode?: number
public message?: string
constructor({
id,
code,
service,
httpStatusCode,
message,
}: {
id: string
code: string
service: Service
httpStatusCode?: number
message?: string
}) {
this.id = id
this.code = code
this.service = service
this.httpStatusCode = httpStatusCode
this.message = message
}
static async loadSingleError({
code,
service,
}: {
code: string
service: Service
}): Promise<Result<ErrorModel, ApiErrorGeneric>> {
return new Result(
await supabase()
.schema('content')
.from('error')
.select('id, code, service(name), httpStatusCode:http_status_code, message')
.eq('code', code)
.eq('service.name', service)
.is('deleted_at', null)
.single<{
id: string
code: string
service: {
name: Service
}
httpStatusCode?: number
message?: string
}>()
)
.map((data) => {
return new ErrorModel({
...data,
service: data.service.name,
})
})
.mapError((error) => {
if (error.code === 'PGRST116') {
return new NoDataError('Error for given code and service does not exist', error)
}
return convertPostgrestToApiError(error)
})
}
static async loadErrors(
args: Parameters<ErrorCollectionFetch>[0]
): ReturnType<ErrorCollectionFetch> {
const PAGE_SIZE = 20
const limit = args?.first ?? args?.last ?? PAGE_SIZE
const service = args?.additionalArgs?.service as Service | undefined
const code = args?.additionalArgs?.code as string | undefined
const [countResult, errorCodesResult] = await Promise.all([
fetchTotalErrorCount(service, code),
fetchErrorDescriptions({
after: args?.after ?? undefined,
before: args?.before ?? undefined,
reverse: !!args?.last,
limit: limit + 1,
service,
code,
}),
])
return countResult
.join(errorCodesResult)
.map(([count, errorCodes]) => {
const hasMoreItems = errorCodes.length > limit
const items = args?.last ? errorCodes.slice(1) : errorCodes.slice(0, limit)
return {
items: items.map((errorCode) => new ErrorModel(errorCode)),
totalCount: count,
hasNextPage: args?.last ? !!args?.before : hasMoreItems,
hasPreviousPage: args?.last ? hasMoreItems : !!args?.after,
}
})
.mapError(([countError, errorCodeError]) => {
return CollectionQueryError.fromErrors(countError, errorCodeError)
})
}
}
async function fetchTotalErrorCount(
service?: Service,
code?: string
): Promise<Result<number, PostgrestError>> {
const query = supabase()
.schema('content')
.from('error')
.select('id, service!inner(name)', { count: 'exact', head: true })
.is('deleted_at', null)
if (service) {
query.eq('service.name', service)
}
if (code) {
query.eq('code', code)
}
const { count, error } = await query
if (error) {
return Result.error(error)
}
return Result.ok(count ?? 0)
}
type ErrorDescription = {
id: string
code: string
service: Service
httpStatusCode?: number
message?: string
}
async function fetchErrorDescriptions({
after,
before,
reverse,
limit,
service,
code,
}: {
after?: string
before?: string
reverse: boolean
limit: number
service?: Service
code?: string
}): Promise<Result<ErrorDescription[], PostgrestError>> {
const query = supabase()
.schema('content')
.from('error')
.select('id, code, service!inner(name), httpStatusCode: http_status_code, message')
.is('deleted_at', null)
.order('id', { ascending: reverse ? false : true })
if (service) {
query.eq('service.name', service)
}
if (code) {
query.eq('code', code)
}
if (after != undefined) {
query.gt('id', after)
}
if (before != undefined) {
query.lt('id', before)
}
query.limit(limit)
const result = await query
return new Result(result).map((results) => {
const transformedResults = (reverse ? results.toReversed() : results).map((error) => ({
...error,
service: error.service.name,
}))
return transformedResults as ErrorDescription[]
})
}