Skip to content

Instantly share code, notes, and snippets.

@narley
Created April 12, 2024 11:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save narley/88390e3428dc7f8379f739085c15b2a3 to your computer and use it in GitHub Desktop.
Save narley/88390e3428dc7f8379f739085c15b2a3 to your computer and use it in GitHub Desktop.
Effect Demo
export class QueryVideosError {
readonly _tag = 'QueryVideosError'
constructor(readonly message: string) {}
}
export class VideoDetailsError {
readonly _tag = 'VideoDetailsError'
constructor(readonly message: string) {}
}
export class GetPropError {
readonly _tag = 'GetPropError'
constructor(readonly message: string, readonly prop?: string) {}
}
export class GetVideoIdError {
readonly _tag = 'GetVideoIdError'
constructor(readonly message: string, readonly errorTag?: string) {}
}
export class GetViewCountError {
readonly _tag = 'GetViewCountError'
constructor(readonly message: string, readonly errorTag?: string) {}
}
import { Effect, Console, pipe, ReadonlyRecord, ReadonlyArray } from 'effect'
import axios from 'axios'
import { QueryResult, VideoDetail } from './types'
import {
QueryVideosError,
VideoDetailsError,
GetPropError,
GetVideoIdError,
GetViewCountError,
} from './errorClasses'
const getSearchOptions = (query: string) => ({
method: 'GET',
url: 'https://youtube-v31.p.rapidapi.com/search',
params: {
q: query,
part: 'snippet,id',
regionCode: 'US',
maxResults: '50',
order: 'date',
},
headers: {
'X-RapidAPI-Key': '9864632205msh9c7d308da48bfc6p1c2871jsn020a1eb4fe45',
'X-RapidAPI-Host': 'youtube-v31.p.rapidapi.com',
},
})
const getVideoOptions = (id: string) => ({
method: 'GET',
url: 'https://youtube-v31.p.rapidapi.com/videos',
params: {
part: 'contentDetails,snippet,statistics',
id,
},
headers: {
'X-RapidAPI-Key': '9864632205msh9c7d308da48bfc6p1c2871jsn020a1eb4fe45',
'X-RapidAPI-Host': 'youtube-v31.p.rapidapi.com',
},
})
const queryVideosEffect = (
query: string
): Effect.Effect<QueryResult, QueryVideosError, never> => {
return Effect.tryPromise(async () => {
const result = await axios.request(getSearchOptions(query))
const videos: QueryResult = await result.data
return videos
}).pipe(Effect.mapError((e) => new QueryVideosError(e.message)))
}
const getVideoDetailsEffect = (id: string) => {
return Effect.tryPromise(async () => {
const result = await axios.request(getVideoOptions(id))
const video: VideoDetail = await result.data
return video
}).pipe(Effect.mapError((e) => new VideoDetailsError(e.message)))
}
const getPropEffect =
(prop: string) =>
(data: any): Effect.Effect<any, GetPropError, never> => {
return ReadonlyRecord.get<string, any>(data, prop).pipe(
Effect.mapError(
() => new GetPropError(`Could not get value for key: ${prop}`)
)
)
}
const getVideoIdEffect = (
videos: QueryResult
): Effect.Effect<string, GetVideoIdError, never> => {
return pipe(
videos,
getPropEffect('items'),
Effect.map(ReadonlyArray.head),
Effect.map(getPropEffect('id')),
Effect.flatMap(getPropEffect('videoId'))
).pipe(Effect.mapError((e) => new GetVideoIdError(e.message, e._tag)))
}
const getViewCountEffect = (
video: VideoDetail
): Effect.Effect<string, GetViewCountError, never> => {
return pipe(
video,
getPropEffect('items'),
Effect.map(ReadonlyArray.head),
Effect.map(getPropEffect('statistics')),
Effect.flatMap(getPropEffect('viewCount'))
).pipe(Effect.mapError((e) => new GetViewCountError(e.message, e._tag)))
}
const getVideoViewCountEffect = (
videoTitle: string
): Effect.Effect<
string,
QueryVideosError | GetVideoIdError | GetViewCountError | VideoDetailsError,
never
> => {
return pipe(
queryVideosEffect(videoTitle),
Effect.andThen(getVideoIdEffect),
Effect.andThen(getVideoDetailsEffect),
Effect.andThen(getViewCountEffect)
)
}
const main = pipe(
getVideoViewCountEffect('Latest and Greatest by Tim Smart'),
Effect.tap(Console.log)
)
Effect.runPromise(main)
import { Schema } from '@effect/schema'
// Schemas
export const QueryResultItemSchema = Schema.struct({
kind: Schema.string,
id: Schema.struct({
kind: Schema.string,
videoId: Schema.string,
}),
snippet: Schema.struct({
publishedAt: Schema.string,
channelId: Schema.string,
title: Schema.string,
description: Schema.string,
thumbnails: Schema.any,
channelTitle: Schema.string,
liveBroadcastContent: Schema.string,
publishTime: Schema.string,
}),
})
// Extracting Inferred Types
export interface QueryResultItem
extends Schema.Schema.Type<typeof QueryResultItemSchema> {}
export const VideoDetailItemSchema = Schema.struct({
kind: Schema.string,
id: Schema.string,
snippet: Schema.struct({
publishedAt: Schema.string,
channelId: Schema.string,
title: Schema.string,
description: Schema.string,
thumbnails: Schema.any,
channelTitle: Schema.string,
tags: Schema.array(Schema.string),
categoryId: Schema.string,
liveBroadcastContent: Schema.string,
defaultLanguage: Schema.string,
localized: Schema.any,
defaultAudioLanguage: Schema.string,
publishTime: Schema.string,
}),
contentDetails: Schema.any,
statistics: Schema.struct({
viewCount: Schema.string,
likeCount: Schema.string,
favoriteCount: Schema.string,
commentCount: Schema.string,
}),
})
// Extracting Inferred Types
export interface VideoDetailItem
extends Schema.Schema.Type<typeof VideoDetailItemSchema> {}
export const QueryResultSchema = Schema.struct({
kind: Schema.string,
regionCode: Schema.string,
pageInfo: Schema.struct({
totalResults: Schema.number,
resultsPerPage: Schema.number,
}),
items: Schema.array(QueryResultItemSchema),
})
// Extracting Inferred Types
export interface QueryResult
extends Schema.Schema.Type<typeof QueryResultItemSchema> {}
export const VideoDetailSchema = Schema.struct({
kind: Schema.string,
pageInfo: Schema.struct({
totalResults: Schema.number,
resultsPerPage: Schema.number,
}),
items: Schema.array(VideoDetailItemSchema),
})
// Extracting Inferred Types
export interface VideoDetail
extends Schema.Schema.Type<typeof VideoDetailSchema> {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment