Created
April 4, 2025 17:30
-
-
Save dmmulroy/2fc6ce8e53005a30f9fa35b5747cdd11 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Data } from 'effect'; | |
import type { StandardSchemaV1 } from '@standard-schema/spec'; | |
import { type ResultAsync, errAsync, okAsync } from 'neverthrow'; | |
export class ParseError extends Data.TaggedError('ParseError')<{ | |
response: Response; | |
issues: ReadonlyArray<StandardSchemaV1.Issue>; | |
}> {} | |
export class UnexpectedError extends Data.TaggedError('UnexpectedError')<{ | |
cause?: unknown; | |
}> {} | |
export class ApiError<E extends StandardSchemaV1> extends Data.TaggedError( | |
'ApiError', | |
)<{ | |
error: SchemaOutput<E>; | |
}> {} | |
export type SafeFetchOptions< | |
Success extends StandardSchemaV1, | |
Failure extends StandardSchemaV1, | |
> = Readonly<{ | |
successSchema: Success; | |
failureSchema: Failure; | |
}> & | |
RequestInit; | |
export type SchemaOutput<A extends StandardSchemaV1> = | |
StandardSchemaV1.InferOutput<A>; | |
export type SafeFetchError<E extends StandardSchemaV1> = | |
| ApiError<E> | |
| ParseError | |
| UnexpectedError; | |
export type SafeFetchResult< | |
SuccessSchema extends StandardSchemaV1, | |
FailureSchema extends StandardSchemaV1, | |
> = ResultAsync<SchemaOutput<SuccessSchema>, SafeFetchError<FailureSchema>>; | |
export async function safeFetch< | |
Success extends StandardSchemaV1, | |
Failure extends StandardSchemaV1, | |
>( | |
url: string, | |
options: SafeFetchOptions<Success, Failure>, | |
): Promise<SafeFetchResult<Success, Failure>> { | |
try { | |
const response = await fetch(url, options); | |
const schema = response.ok ? options.successSchema : options.failureSchema; | |
const data = await response.json(); | |
const parsed = await schema['~standard'].validate(data); | |
if (parsed.issues !== undefined) { | |
return errAsync( | |
new ParseError({ | |
response, | |
issues: parsed.issues, | |
}), | |
); | |
} | |
return response.ok | |
? okAsync(parsed) | |
: errAsync(new ApiError({ error: parsed })); | |
} catch (cause) { | |
return errAsync(new UnexpectedError({ cause })); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment