diff --git a/docs/API.md b/docs/API.md index 3feaee4..b05e7ad 100644 --- a/docs/API.md +++ b/docs/API.md @@ -23,6 +23,9 @@ - [useSingleDocByDocId](#usesingledocbydocid) - [useSingleDocByVersionId](#usesingledocbyversionid) - [useManyDocs](#usemanydocs) +- [useCreateDocument](#usecreatedocument) +- [useUpdateDocument](#useupdatedocument) +- [useDeleteDocument](#usedeletedocument) - [useAcceptInvite](#useacceptinvite) - [useRejectInvite](#userejectinvite) - [useSendInvite](#usesendinvite) @@ -439,7 +442,7 @@ Triggers the closest error boundary if the document cannot be found | Function | Type | | ---------- | ---------- | -| `useSingleDocByDocId` | `({ projectId, docType, docId, lang, }: { projectId: string; docType: D; docId: string; lang?: string or undefined; }) => ReadHookResult({ projectId, docType, docId, lang, }: { projectId: string; docType: D; docId: string; lang?: string or undefined; }) => ReadHookResult({ projectId, docType, versionId, lang, }: { projectId: string; docType: D; versionId: string; lang?: string or undefined; }) => ReadHookResult({ projectId, docType, versionId, lang, }: { projectId: string; docType: D; versionId: string; lang?: string or undefined; }) => ReadHookResult({ projectId, docType, includeDeleted, lang, }: { projectId: string; docType: D; includeDeleted?: boolean or undefined; lang?: string or undefined; }) => ReadHookResult({ projectId, docType, includeDeleted, lang, }: { projectId: string; docType: D; includeDeleted?: boolean or undefined; lang?: string or undefined; }) => ReadHookResult({ docType, projectId, }: { docType: D; projectId: string; }) => { mutate: UseMutateFunction and { forks: string[]; }, Error, { value: Omit, "schemaName">; }, unknown>; reset: () => void; status: "pending" or ... 2 more ... or "idle"; }` | + +Parameters: + +* `opts.docType`: Document type to create. +* `opts.projectId`: Public ID of project to create document for. + + +### useUpdateDocument + +Update a document within a project. + +| Function | Type | +| ---------- | ---------- | +| `useUpdateDocument` | `({ docType, projectId, }: { docType: D; projectId: string; }) => { mutate: UseMutateFunction and { forks: string[]; }, Error, { versionId: string; value: Omit<...>; }, unknown>; reset: () => void; status: "pending" or ... 2 more ... or "idle"; }` | + +Parameters: + +* `opts.docType`: Document type to update. +* `opts.projectId`: Public ID of project document belongs to. + + +### useDeleteDocument + +Delete a document within a project. + +| Function | Type | +| ---------- | ---------- | +| `useDeleteDocument` | `({ docType, projectId, }: { docType: D; projectId: string; }) => { mutate: UseMutateFunction and { forks: string[]; }, Error, { docId: string; }, unknown>; reset: () => void; status: "pending" or ... 2 more ... or "idle"; }` | + +Parameters: + +* `opts.docType`: Document type to delete. +* `opts.projectId`: Public ID of project document belongs to. + + ### useAcceptInvite Accept an invite that has been received. diff --git a/src/hooks/documents.ts b/src/hooks/documents.ts index 700e461..808c131 100644 --- a/src/hooks/documents.ts +++ b/src/hooks/documents.ts @@ -1,11 +1,18 @@ import type { MapeoDoc } from '@comapeo/schema' with { 'resolution-mode': 'import' } -import { useSuspenseQuery } from '@tanstack/react-query' +import { + useMutation, + useQueryClient, + useSuspenseQuery, +} from '@tanstack/react-query' import { + createDocumentMutationOptions, + deleteDocumentMutationOptions, documentByDocumentIdQueryOptions, documentByVersionIdQueryOptions, documentsQueryOptions, - type DocumentType, + updateDocumentMutationOptions, + type WriteableDocumentType, } from '../lib/react-query/documents.js' import { useSingleProject } from './projects.js' @@ -38,7 +45,7 @@ type ReadHookResult = { * } * ``` */ -export function useSingleDocByDocId({ +export function useSingleDocByDocId({ projectId, docType, docId, @@ -94,7 +101,7 @@ export function useSingleDocByDocId({ * } * ``` */ -export function useSingleDocByVersionId({ +export function useSingleDocByVersionId({ projectId, docType, versionId, @@ -159,7 +166,7 @@ export function useSingleDocByVersionId({ * } * ``` */ -export function useManyDocs({ +export function useManyDocs({ projectId, docType, includeDeleted, @@ -189,3 +196,87 @@ export function useManyDocs({ isRefetching, } } + +/** + * Create a document for a project. + * + * @param opts.docType Document type to create. + * @param opts.projectId Public ID of project to create document for. + */ +export function useCreateDocument({ + docType, + projectId, +}: { + docType: D + projectId: string +}) { + const queryClient = useQueryClient() + const { data: projectApi } = useSingleProject({ projectId }) + + const { mutate, reset, status } = useMutation( + createDocumentMutationOptions({ + docType, + projectApi, + projectId, + queryClient, + }), + ) + + return { mutate, reset, status } +} + +/** + * Update a document within a project. + * + * @param opts.docType Document type to update. + * @param opts.projectId Public ID of project document belongs to. + */ +export function useUpdateDocument({ + docType, + projectId, +}: { + docType: D + projectId: string +}) { + const queryClient = useQueryClient() + const { data: projectApi } = useSingleProject({ projectId }) + + const { mutate, reset, status } = useMutation( + updateDocumentMutationOptions({ + docType, + projectApi, + projectId, + queryClient, + }), + ) + + return { mutate, reset, status } +} + +/** + * Delete a document within a project. + * + * @param opts.docType Document type to delete. + * @param opts.projectId Public ID of project document belongs to. + */ +export function useDeleteDocument({ + docType, + projectId, +}: { + docType: D + projectId: string +}) { + const queryClient = useQueryClient() + const { data: projectApi } = useSingleProject({ projectId }) + + const { mutate, reset, status } = useMutation( + deleteDocumentMutationOptions({ + docType, + projectApi, + projectId, + queryClient, + }), + ) + + return { mutate, reset, status } +} diff --git a/src/index.ts b/src/index.ts index 80b269d..caaf1f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,9 +7,12 @@ export { useSetOwnDeviceInfo, } from './hooks/client.js' export { + useCreateDocument, + useDeleteDocument, useManyDocs, useSingleDocByDocId, useSingleDocByVersionId, + useUpdateDocument, } from './hooks/documents.js' export { useAcceptInvite, @@ -44,7 +47,9 @@ export { getDocumentByVersionIdQueryKey, getDocumentsQueryKey, getManyDocumentsQueryKey, - type DocumentType, + type WriteableDocument, + type WriteableDocumentType, + type WriteableValue, } from './lib/react-query/documents.js' export { getInvitesQueryKey, diff --git a/src/lib/react-query/documents.ts b/src/lib/react-query/documents.ts index 73487a0..472b7a0 100644 --- a/src/lib/react-query/documents.ts +++ b/src/lib/react-query/documents.ts @@ -1,15 +1,34 @@ import type { MapeoProjectApi } from '@comapeo/ipc' with { 'resolution-mode': 'import' } -import type { MapeoDoc } from '@comapeo/schema' with { 'resolution-mode': 'import' } -import { queryOptions } from '@tanstack/react-query' +import type { + MapeoDoc, + MapeoValue, +} from '@comapeo/schema' with { 'resolution-mode': 'import' } +import { + queryOptions, + type QueryClient, + type UseMutationOptions, +} from '@tanstack/react-query' -import { baseQueryOptions, ROOT_QUERY_KEY } from './shared.js' +import { + baseMutationOptions, + baseQueryOptions, + ROOT_QUERY_KEY, +} from './shared.js' -export type DocumentType = Extract< +export type WriteableDocumentType = Extract< MapeoDoc['schemaName'], 'field' | 'observation' | 'preset' | 'track' | 'remoteDetectionAlert' > +export type WriteableValue = Extract< + MapeoValue, + { schemaName: D } +> +export type WriteableDocument = Extract< + MapeoDoc, + { schemaName: D } +> -export function getDocumentsQueryKey({ +export function getDocumentsQueryKey({ projectId, docType, }: { @@ -19,7 +38,7 @@ export function getDocumentsQueryKey({ return [ROOT_QUERY_KEY, 'projects', projectId, docType] as const } -export function getManyDocumentsQueryKey({ +export function getManyDocumentsQueryKey({ projectId, docType, includeDeleted, @@ -39,7 +58,7 @@ export function getManyDocumentsQueryKey({ ] as const } -export function getDocumentByDocIdQueryKey({ +export function getDocumentByDocIdQueryKey({ projectId, docType, docId, @@ -60,7 +79,9 @@ export function getDocumentByDocIdQueryKey({ ] as const } -export function getDocumentByVersionIdQueryKey({ +export function getDocumentByVersionIdQueryKey< + D extends WriteableDocumentType, +>({ projectId, docType, versionId, @@ -81,7 +102,7 @@ export function getDocumentByVersionIdQueryKey({ ] as const } -export function documentsQueryOptions({ +export function documentsQueryOptions({ projectApi, projectId, docType, @@ -111,7 +132,9 @@ export function documentsQueryOptions({ }) } -export function documentByDocumentIdQueryOptions({ +export function documentByDocumentIdQueryOptions< + D extends WriteableDocumentType, +>({ projectApi, projectId, docType, @@ -142,7 +165,9 @@ export function documentByDocumentIdQueryOptions({ }) } -export function documentByVersionIdQueryOptions({ +export function documentByVersionIdQueryOptions< + D extends WriteableDocumentType, +>({ projectApi, projectId, docType, @@ -168,3 +193,109 @@ export function documentByVersionIdQueryOptions({ }, }) } + +export function createDocumentMutationOptions({ + docType, + projectApi, + projectId, + queryClient, +}: { + docType: D + projectApi: MapeoProjectApi + projectId: string + queryClient: QueryClient +}) { + return { + ...baseMutationOptions(), + mutationFn: async ({ + value, + }): Promise & { forks: Array }> => { + // @ts-expect-error TS not handling this well + return projectApi[docType].create({ + ...value, + schemaName: docType, + }) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: getDocumentsQueryKey({ + projectId, + docType, + }), + }) + }, + } satisfies UseMutationOptions< + WriteableDocument & { forks: Array }, + Error, + { value: Omit, 'schemaName'> } + > +} + +export function updateDocumentMutationOptions({ + docType, + projectApi, + projectId, + queryClient, +}: { + docType: D + projectApi: MapeoProjectApi + projectId: string + queryClient: QueryClient +}) { + return { + ...baseMutationOptions(), + mutationFn: async ({ + versionId, + value, + }): Promise & { forks: Array }> => { + // @ts-expect-error TS not handling this well + return projectApi[docType].update(versionId, value) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: getDocumentsQueryKey({ + projectId, + docType, + }), + }) + }, + } satisfies UseMutationOptions< + WriteableDocument & { forks: Array }, + Error, + { versionId: string; value: Omit, 'schemaName'> } + > +} + +export function deleteDocumentMutationOptions({ + docType, + projectApi, + projectId, + queryClient, +}: { + docType: D + projectApi: MapeoProjectApi + projectId: string + queryClient: QueryClient +}) { + return { + ...baseMutationOptions(), + mutationFn: async ({ + docId, + }): Promise & { forks: Array }> => { + // @ts-expect-error TS not handling this well + return projectApi[docType].delete(docId) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: getDocumentsQueryKey({ + projectId, + docType, + }), + }) + }, + } satisfies UseMutationOptions< + WriteableDocument & { forks: Array }, + Error, + { docId: string } + > +}