Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Events v2: Update tRPC #8772

Merged
merged 26 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a93a7d5
WIP
rikukissa Feb 25, 2025
d05521f
Merge branch 'develop' into update-trpc
rikukissa Feb 25, 2025
3ba9120
Merge branch 'develop' of github.com:opencrvs/opencrvs-core into upda…
rikukissa Feb 25, 2025
89cc67d
revert change in gateway
rikukissa Feb 25, 2025
8af2e0b
Merge branch 'update-trpc' of github.com:opencrvs/opencrvs-core into …
rikukissa Feb 25, 2025
138d7ba
fix linting errors
rikukissa Feb 25, 2025
0f03b8c
cleanup
rikukissa Feb 26, 2025
011cfe5
Merge branch 'develop' into update-trpc
rikukissa Feb 26, 2025
4cc7c9e
Merge branch 'develop' of github.com:opencrvs/opencrvs-core into upda…
rikukissa Feb 26, 2025
2462e68
Merge branch 'update-trpc' of github.com:opencrvs/opencrvs-core into …
rikukissa Feb 26, 2025
01d37cd
fix linter errors
rikukissa Feb 26, 2025
82d650e
Merge branch 'develop' into update-trpc
rikukissa Feb 26, 2025
22739c9
cleanup
rikukissa Feb 26, 2025
9a45fd1
revert bugfix for data stringifier
rikukissa Feb 26, 2025
ca830ba
Merge branch 'update-trpc' of github.com:opencrvs/opencrvs-core into …
rikukissa Feb 26, 2025
094a1bb
move logic out of useEvents
rikukissa Feb 26, 2025
318aad6
more cleanup
rikukissa Feb 26, 2025
18a2916
add a comment for useEventAction
rikukissa Feb 26, 2025
29b4209
remove unused dep
rikukissa Feb 26, 2025
609e129
update readme
rikukissa Feb 26, 2025
deb0021
ensure all data is processed for action payload before submitting it …
rikukissa Feb 27, 2025
9c53cac
Merge branch 'develop' into update-trpc
rikukissa Feb 27, 2025
8a0f121
Merge branch 'develop' into update-trpc
rikukissa Feb 27, 2025
07be5a1
Merge branch 'develop' of github.com:opencrvs/opencrvs-core into upda…
rikukissa Feb 27, 2025
e2bf6cf
Merge branch 'update-trpc' of github.com:opencrvs/opencrvs-core into …
rikukissa Feb 27, 2025
8bb4e6a
Merge branch 'develop' into update-trpc
rikukissa Feb 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@
"@tanstack/react-query": "^5.61.5",
"@tanstack/react-query-devtools": "^5.62.2",
"@tanstack/react-query-persist-client": "^5.62.2",
"@trpc/client": "^11.0.0-rc.648",
"@trpc/react-query": "^11.0.0-rc.648",
"@trpc/server": "^11.0.0-rc.648",
"@trpc/client": "^11.0.0-rc.804",
"@trpc/server": "^11.0.0-rc.804",
"@trpc/tanstack-react-query": "^11.0.0-rc.804",
"@types/bcryptjs": "^2.4.2",
"@types/history": "^4.6.2",
"@types/html-to-pdfmake": "^2.4.4",
Expand Down
7 changes: 7 additions & 0 deletions packages/client/src/v2-events/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,10 @@ By default, Events V2 is accessible via the /v2 route, allowing the application
- When building new features, aim to have a separate component that handles interaction with router and data fetching when it makes sense. Features should be route independent, and necessary information (ids and such) should be passed in as props or similar. See (`features/events/actions/register` or `features/events/actions/declare`)
- Use constants through object pattern. e.g.`ActionType.CREATE` over `'CREATE'`. In most situations, it does not matter. However, changing the names will get much easier.
- When building new features, prefer to import them through `index.ts`. Managing imports will be cleaner and easier that way. See (`/layouts`)

### Fetching data from backend

- All data should be fetched using useMutation or useSuspenseQuery from TanStack Query. If you define a completely new (non-tRPC) query or mutation, use `queryClient.setMutationDefaults({ mutationFn: <operation> })` or `queryClient.setQueryDefaults({ queryFn: <operation> })` to define the data fetcher method. This ensures that TanStack Query knows how to call your function even
when the request is first serialized into the local cache and executed later.

- Whenever possible, mutation operations should follow a “fire-and-forget” approach to maintain a snappy UI, even with poor or no internet connection. Perform the query and let it process in the background.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function DeleteEvent() {
const { eventId } = useTypedParams(ROUTES.V2.EVENTS.DELETE)
const navigate = useNavigate()
const events = useEvents()
const deleteEvent = events.deleteEvent
const deleteEvent = events.deleteEvent.useMutation()

useEffect(() => {
deleteEvent.mutate({ eventId })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import { useSuspenseQuery } from '@tanstack/react-query'
import { EventConfig } from '@opencrvs/commons/client'
import { api } from '@client/v2-events/trpc'
import { useTRPC } from '@client/v2-events/trpc'

/**
* Fetches configured events and finds a matching event
* @returns a list of event configurations
*/
export function useEventConfigurations() {
const [config] = api.event.config.get.useSuspenseQuery()
const trpc = useTRPC()
const config = useSuspenseQuery(trpc.event.config.get.queryOptions()).data
return config
}

Expand All @@ -28,7 +30,7 @@ export function useEventConfigurations() {
export function useEventConfiguration(eventIdentifier: string): {
eventConfiguration: EventConfig
} {
const [config] = api.event.config.get.useSuspenseQuery()
const config = useEventConfigurations()
const eventConfiguration = config.find(
(event) => event.id === eventIdentifier
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function useEventFormNavigation() {
const navigate = useNavigate()

const events = useEvents()
const deleteEvent = events.deleteEvent
const deleteEvent = events.deleteEvent.useMutation()

const [modal, openModal] = useModal()

Expand Down
101 changes: 101 additions & 0 deletions packages/client/src/v2-events/features/events/useEvents/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* OpenCRVS is also distributed under the terms of the Civil Registration
* & Healthcare Disclaimer located at http://opencrvs.org/license.
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import {
MutationObserverOptions,
OmitKeyof,
QueryFunctionContext
} from '@tanstack/react-query'
import { TRPCClientError } from '@trpc/client'
import {
DecorateMutationProcedure,
DecorateQueryProcedure,
inferInput,
inferOutput
} from '@trpc/tanstack-react-query'
import { EventDocument, EventIndex } from '@opencrvs/commons/client'
import { AppRouter, queryClient, utils } from '@client/v2-events/trpc'

export function getLocalEventData(eventId: string) {
return queryClient.getQueryData(
utils.event.get.queryKey(eventId)
) as EventDocument
}

export function setEventData(id: string, data: EventDocument) {
return queryClient.setQueryData(utils.event.get.queryKey(id), data)
}

export function setEventListData(
updater: (eventIndices: EventIndex[] | undefined) => EventIndex[] | undefined
) {
return queryClient.setQueryData(utils.event.list.queryKey(), updater)
}

export async function invalidateEventsList() {
return queryClient.invalidateQueries({
queryKey: utils.event.list.queryKey()
})
}

type TRPCError = TRPCClientError<AppRouter>
type TRPCQueryKey<T> = [readonly string[], { input: T }]

/**
* Sets the default options for a mutation procedure.
*
* This function should be the primary method of changing settings for mutations
* because it ensures that mutations stored in IndexedDB for later processing
* after the application has reloaded can use the same settings without running
* the same code paths again.
*
* i.e. if you want to override mutationFn, you must use setMutationDefault to do so.
*
* @template P - The type of the mutation procedure e.g. trpc.events.get.
* @template Context - The type of the context, defaults to `any`.
* @param {OmitKeyof<MutationObserverOptions<inferOutput<P>, TRPCError, inferInput<P>, Context>, 'mutationKey'>} options - The options to set as defaults, excluding the `mutationKey`.
*/
export function setMutationDefaults<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
P extends DecorateMutationProcedure<any>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Context = any
>(
mutation: P,
options: OmitKeyof<
MutationObserverOptions<inferOutput<P>, TRPCError, inferInput<P>, Context>,
'mutationKey'
>
) {
queryClient.setMutationDefaults(mutation.mutationKey(), options)
}

/**
* Sets the default options for a mutation procedure.
*/
export function setQueryDefaults<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
P extends DecorateQueryProcedure<any>
>(
query: P,
options: Omit<
Parameters<typeof queryClient.setQueryDefaults>[1],
'queryFn'
> & {
queryFn: (
input: QueryFunctionContext<TRPCQueryKey<inferInput<P>>>
) => inferOutput<P> | Promise<inferOutput<P>>
}
) {
queryClient.setQueryDefaults(
query.queryKey(),
options as Parameters<typeof queryClient.setQueryDefaults>[1]
)
}
99 changes: 99 additions & 0 deletions packages/client/src/v2-events/features/events/useEvents/outbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* OpenCRVS is also distributed under the terms of the Civil Registration
* & Healthcare Disclaimer located at http://opencrvs.org/license.
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/

import { hashKey, Mutation, useQuery } from '@tanstack/react-query'

import {
DecorateMutationProcedure,
inferInput,
inferOutput
} from '@trpc/tanstack-react-query'
import { EventIndex } from '@opencrvs/commons/client'
import { queryClient, useTRPC } from '@client/v2-events/trpc'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getPendingMutations<T extends DecorateMutationProcedure<any>>(
procedure: T
) {
// type MutationFn = Exclude<T['mutationFn'], undefined>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented code

type Data = inferOutput<T>
type Variables = inferInput<T>

const mutationOptions = procedure.mutationOptions()
const key = mutationOptions.mutationKey

return queryClient
.getMutationCache()
.getAll()
.filter((mutation) => mutation.state.status !== 'success')
.filter(
(mutation) =>
mutation.options.mutationKey &&
hashKey(mutation.options.mutationKey) === hashKey(key)
) as Mutation<Data, Error, Variables>[]
}

function filterOutboxEventsWithMutation<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends DecorateMutationProcedure<any>
>(
events: EventIndex[],
mutation: T,
filter: (event: EventIndex, parameters: inferInput<T>) => boolean
) {
return getPendingMutations(mutation).flatMap((m) => {
const variables = m.state.variables
return events.filter((event) => filter(event, variables))
})
}

export function useOutbox() {
const trpc = useTRPC()
const eventListQuery = useQuery({
...trpc.event.list.queryOptions(),
queryKey: trpc.event.list.queryKey()
})

const eventsList = eventListQuery.data ?? []

const eventFromDeclareActions = filterOutboxEventsWithMutation(
eventsList,
trpc.event.actions.declare,
(event, parameters) => {
return event.id === parameters.eventId && !parameters.draft
}
)

const eventFromValidateActions = filterOutboxEventsWithMutation(
eventsList,
trpc.event.actions.validate,
(event, parameters) => {
return event.id === parameters.eventId && !parameters.draft
}
)

const eventFromRegisterActions = filterOutboxEventsWithMutation(
eventsList,
trpc.event.actions.register,
(event, parameters) => {
return event.id === parameters.eventId && !parameters.draft
}
)

return eventFromDeclareActions
.concat(eventFromDeclareActions)
.concat(eventFromValidateActions)
.concat(eventFromRegisterActions)
.filter(
/* uniqueById */
(e, i, arr) => arr.findIndex((a) => a.id === e.id) === i
)
}
Loading
Loading