Skip to content

Commit

Permalink
Fiber interruption done in per request and not in client itself
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel Tremko authored and SamTremko committed Sep 18, 2024
1 parent 35dec12 commit f33472b
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 88 deletions.
69 changes: 51 additions & 18 deletions apps/mobile/src/components/LocationSearch/molecule.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {effectToTaskEither} from '@vexl-next/resources-utils/src/effect-helpers/TaskEitherConverter'
import {type ExtractErrorFromEffect} from '@vexl-next/resources-utils/src/utils/ExtractErrorFromEffect'
import {type LocationSuggestion} from '@vexl-next/rest-api/src/services/location/contracts'
import {createScope, molecule, type MoleculeOrInterface} from 'bunshi'
import {useMolecule} from 'bunshi/dist/react'
import {Effect, Either, Exit, Fiber} from 'effect'
import * as E from 'fp-ts/Either'
import * as TE from 'fp-ts/TaskEither'
import {pipe} from 'fp-ts/lib/function'
import {atom} from 'jotai'
import {splitAtom} from 'jotai/utils'
import {randomUUID} from 'node:crypto'
import {z} from 'zod'
import {apiAtom} from '../../api'
import {loadableEither} from '../../utils/atomUtils/loadableEither'
import {
loadableEffectEither,
type UnknownLoadingError,
} from '../../utils/atomUtils/loadableEither'
import {getCurrentLocale} from '../../utils/localization/I18nProvider'

export const LocationSessionId = z.string().uuid().brand<'LocationSessionId'>()
Expand All @@ -29,26 +32,56 @@ export const LocationSearchMolecule = molecule((_, getScope) => {

const searchQueryAtom = atom('')

const searchResultsAtom = loadableEither(
atom(async (get, {signal}) => {
const query = get(searchQueryAtom)
if (query.trim() === '') return await TE.right([])()
const searchResultsAtom = loadableEffectEither(
atom(
async (
get,
{signal}
): Promise<
Either.Either<
readonly LocationSuggestion[],
| ExtractErrorFromEffect<
ReturnType<typeof api.location.getLocationSuggestions>
>
| UnknownLoadingError
>
> => {
const api = get(apiAtom)
const query = get(searchQueryAtom)
if (query.trim() === '') return Either.right([])

return await pipe(
effectToTaskEither(
get(apiAtom).location.getLocationSuggestions(
{
const fiber = Effect.runFork(
api.location
.getLocationSuggestions({
query: {
phrase: query,
lang: getCurrentLocale(),
},
},
signal
)
),
TE.map((one) => one.result)
)()
})
})
.pipe(
Effect.map((one) => {
return one.result
}),
Effect.either
)
)

signal.onabort = () => {
Effect.runFork(Fiber.interruptFork(fiber))
}

const exit = await Effect.runPromise(Fiber.await(fiber))

if (Exit.isSuccess(exit)) {
return exit.value
}

return Either.left({
_tag: 'UnknownLoadingError',
error: exit.cause,
} as UnknownLoadingError)
}
)
)

const searchResultsOrEmptyArrayAtom = atom((get): LocationSuggestion[] => {
Expand Down
77 changes: 54 additions & 23 deletions apps/mobile/src/components/Map/components/MapLocationSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {Latitude, Longitude} from '@vexl-next/domain/src/utility/geoCoordinates'
import {effectToTaskEither} from '@vexl-next/resources-utils/src/effect-helpers/TaskEitherConverter'
import {type ExtractErrorFromEffect} from '@vexl-next/resources-utils/src/utils/ExtractErrorFromEffect'
import {type GetGeocodedCoordinatesResponse} from '@vexl-next/rest-api/src/services/location/contracts'
import {Effect, Either, Exit, Fiber} from 'effect'
import * as E from 'fp-ts/Either'
import * as TE from 'fp-ts/TaskEither'
import {pipe} from 'fp-ts/lib/function'
import {atom, useAtomValue, useSetAtom} from 'jotai'
import {useMemo} from 'react'
Expand All @@ -13,7 +14,10 @@ import MapView, {
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {Stack, Text} from 'tamagui'
import {apiAtom} from '../../../api'
import {loadableEither} from '../../../utils/atomUtils/loadableEither'
import {
loadableEffectEither,
type UnknownLoadingError,
} from '../../../utils/atomUtils/loadableEither'
import {
getCurrentLocale,
useTranslation,
Expand Down Expand Up @@ -53,33 +57,60 @@ function useAtoms({

return {
selectedRegionAtom,
getGeocodedRegion: loadableEither(
atom(async (get, {signal}) => {
const region = get(selectedRegionAtom)

onPick(null) // loading
return await pipe(
effectToTaskEither(
api.location.getGeocodedCoordinates(
{
getGeocodedRegion: loadableEffectEither(
atom(
async (
get,
{signal}
): Promise<
Either.Either<
GetGeocodedCoordinatesResponse,
| ExtractErrorFromEffect<
ReturnType<typeof api.location.getGeocodedCoordinates>
>
| UnknownLoadingError
>
> => {
const region = get(selectedRegionAtom)

onPick(null) // loading
const fiber = Effect.runFork(
api.location
.getGeocodedCoordinates({
query: {
lang: getCurrentLocale(),
latitude: Latitude.parse(region.latitude),
longitude: Longitude.parse(region.longitude),
},
},
signal
)
),
TE.map((data) => {
onPick(data)
return data
})
)()
})
})
.pipe(
Effect.map((data) => {
onPick(data)
return data
}),
Effect.either
)
)

signal.onabort = () => {
Effect.runFork(Fiber.interruptFork(fiber))
}

const exit = await Effect.runPromise(Fiber.await(fiber))

if (Exit.isSuccess(exit)) {
return exit.value
}

return Either.left({
_tag: 'UnknownLoadingError',
error: exit.cause,
} as UnknownLoadingError)
}
)
),
}
}, [initialRegion, onPick, api.location])
}, [api, initialRegion, onPick])
}

function PickedLocationText({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import {
Radius,
longitudeDeltaToKilometers,
} from '@vexl-next/domain/src/utility/geoCoordinates'
import {effectToTaskEither} from '@vexl-next/resources-utils/src/effect-helpers/TaskEitherConverter'
import {type ExtractErrorFromEffect} from '@vexl-next/resources-utils/src/utils/ExtractErrorFromEffect'
import {type GetGeocodedCoordinatesResponse} from '@vexl-next/rest-api/src/services/location/contracts'
import {Effect, Either, Exit, Fiber} from 'effect'
import * as E from 'fp-ts/Either'
import * as TE from 'fp-ts/TaskEither'
import {pipe} from 'fp-ts/lib/function'
import {atom, useAtomValue, useSetAtom} from 'jotai'
import {useMemo} from 'react'
Expand All @@ -15,7 +16,10 @@ import MapView, {PROVIDER_GOOGLE, type Region} from 'react-native-maps'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {Stack, Text, getTokens} from 'tamagui'
import {apiAtom} from '../../../api'
import {loadableEither} from '../../../utils/atomUtils/loadableEither'
import {
type UnknownLoadingError,
loadableEffectEither,
} from '../../../utils/atomUtils/loadableEither'
import {
getCurrentLocale,
useTranslation,
Expand Down Expand Up @@ -77,44 +81,73 @@ function useAtoms({
) / 10
)
}),
getGeocodedRegion: loadableEither(
atom(async (get, {signal}) => {
const region = get(selectedRegionAtom)
getGeocodedRegion: loadableEffectEither(
atom(
async (
get,
{signal}
): Promise<
Either.Either<
GetGeocodedCoordinatesResponse,
| ExtractErrorFromEffect<
ReturnType<typeof api.location.getGeocodedCoordinates>
>
| UnknownLoadingError
>
> => {
const region = get(selectedRegionAtom)

onPick(null) // loading
return await pipe(
effectToTaskEither(
api.location.getGeocodedCoordinates(
{
onPick(null) // loading
const fiber = Effect.runFork(
api.location
.getGeocodedCoordinates({
query: {
lang: getCurrentLocale(),
latitude: Latitude.parse(region.latitude),
longitude: Longitude.parse(region.longitude),
},
},
signal
)
),
TE.map((data) => {
const {width} = Dimensions.get('window')
const usedWidthWithoutPadding = (width - circleMargin * 2) / width
})
.pipe(
Effect.map((data) => {
const {width} = Dimensions.get('window')
const usedWidthWithoutPadding =
(width - circleMargin * 2) / width

onPick({
...data,
latitude: Latitude.parse(region.latitude),
longitude: Longitude.parse(region.longitude),
radius: Radius.parse(
(Math.abs(region.longitudeDelta) *
usedWidthWithoutPadding) /
2
),
})
return data
}),
Effect.either
)
)

signal.onabort = () => {
Effect.runFork(Fiber.interruptFork(fiber))
}

onPick({
...data,
latitude: Latitude.parse(region.latitude),
longitude: Longitude.parse(region.longitude),
radius: Radius.parse(
(Math.abs(region.longitudeDelta) * usedWidthWithoutPadding) /
2
),
})
return data
})
)()
})
const exit = await Effect.runPromise(Fiber.await(fiber))

if (Exit.isSuccess(exit)) {
return exit.value
}

return Either.left({
_tag: 'UnknownLoadingError',
error: exit.cause,
} as UnknownLoadingError)
}
)
),
}
}, [initialRegion, onPick, api.location])
}, [api, initialRegion, onPick])
}

function PickedLocationText({
Expand Down
30 changes: 30 additions & 0 deletions apps/mobile/src/utils/atomUtils/loadableEither.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {Either} from 'effect'
import * as E from 'fp-ts/Either'
import {atom, type Atom} from 'jotai'
import {loadable} from 'jotai/utils'
Expand Down Expand Up @@ -35,3 +36,32 @@ export function loadableEither<Left, Right>(
return {state: 'done', either: result.data}
})
}

export type LoadableEffectEither<R, L> =
| {
state: 'loading'
}
| {
state: 'done'
either: Either.Either<R, L | UnknownLoadingError>
}

export function loadableEffectEither<R, L>(
anAtom: Atom<Promise<Either.Either<R, L>>>
): Atom<LoadableEffectEither<R, L>> {
const loadableAtom = loadable(anAtom)
return atom((get): LoadableEffectEither<R, L> => {
const result = get(loadableAtom)
if (result.state === 'loading') return {state: 'loading' as const}
if (result.state === 'hasError')
return {
state: 'done' as const,
either: Either.left({
_tag: 'UnknownLoadingError',
error: result.error,
} as UnknownLoadingError),
}

return {state: 'done', either: result.data}
})
}
Loading

0 comments on commit f33472b

Please sign in to comment.