@comapeo/core-react 2.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import type { BitmapOpts, SvgOpts } from '@comapeo/core/dist/icon-api.js' with { 'resolution-mode': 'import' };
2
2
  import type { BlobId } from '@comapeo/core/dist/types.js' with { 'resolution-mode': 'import' };
3
+ import { type SyncState } from '../lib/sync.js';
3
4
  /**
4
5
  * Retrieve the project settings for a project.
5
6
  *
@@ -249,6 +250,28 @@ export declare function useDocumentCreatedBy({ projectId, originalVersionId, }:
249
250
  error: Error | null;
250
251
  isRefetching: boolean;
251
252
  };
253
+ /**
254
+ * Get the role for the current device in a specified project.
255
+ * This is a more convenient alternative to using the `useOwnDeviceInfo` and `useManyMembers` hooks.
256
+ *
257
+ * @param opts.projectId Project public ID
258
+ *
259
+ * @example
260
+ * ```tsx
261
+ * function BasicExample() {
262
+ * const { data } = useOwnRoleInProject({
263
+ * projectId: '...',
264
+ * })
265
+ * }
266
+ * ```
267
+ */
268
+ export declare function useOwnRoleInProject({ projectId }: {
269
+ projectId: string;
270
+ }): {
271
+ data: import("@comapeo/core/dist/roles.js").Role<"a12a6702b93bd7ff" | "f7c150f5a3a9a855" | "012fd2d431c0bf60" | "9e6d29263cba36c9" | "8ced989b1904606b" | "08e4251e36f6e7ed">;
272
+ error: Error | null;
273
+ isRefetching: boolean;
274
+ };
252
275
  export declare function useAddServerPeer({ projectId }: {
253
276
  projectId: string;
254
277
  }): {
@@ -332,3 +355,52 @@ export declare function useCreateBlob({ projectId }: {
332
355
  reset: () => void;
333
356
  status: "pending" | "error" | "success" | "idle";
334
357
  };
358
+ /**
359
+ * Hook to subscribe to the current sync state.
360
+ *
361
+ * Creates a global singleton for each project, to minimize traffic over IPC -
362
+ * this hook can safely be used in more than one place without attaching
363
+ * additional listeners across the IPC channel.
364
+ *
365
+ * @example
366
+ * ```ts
367
+ * function Example() {
368
+ * const syncState = useSyncState({ projectId });
369
+ *
370
+ * if (!syncState) {
371
+ * // Sync information hasn't been loaded yet
372
+ * }
373
+ *
374
+ * // Actual info about sync state is available...
375
+ * }
376
+ * ```
377
+ *
378
+ * @param opts.projectId Project public ID
379
+ */
380
+ export declare function useSyncState({ projectId, }: {
381
+ projectId: string;
382
+ }): SyncState | null;
383
+ /**
384
+ * Provides the progress of data sync for sync-enabled connected peers
385
+ *
386
+ * @returns `null` if no sync state events have been received. Otherwise returns a value between 0 and 1 (inclusive)
387
+ */
388
+ export declare function useDataSyncProgress({ projectId, }: {
389
+ projectId: string;
390
+ }): number | null;
391
+ export declare function useStartSync({ projectId }: {
392
+ projectId: string;
393
+ }): {
394
+ mutate: import("@tanstack/react-query").UseMutateFunction<void, Error, {
395
+ autostopDataSyncAfter: number | null;
396
+ } | undefined, unknown>;
397
+ reset: () => void;
398
+ status: "pending" | "error" | "success" | "idle";
399
+ };
400
+ export declare function useStopSync({ projectId }: {
401
+ projectId: string;
402
+ }): {
403
+ mutate: import("@tanstack/react-query").UseMutateFunction<void, Error, void, unknown>;
404
+ reset: () => void;
405
+ status: "pending" | "error" | "success" | "idle";
406
+ };
@@ -1,5 +1,7 @@
1
1
  import { useMutation, useQueryClient, useSuspenseQuery, } from '@tanstack/react-query';
2
- import { addServerPeerMutationOptions, attachmentUrlQueryOptions, createBlobMutationOptions, createProjectMutationOptions, documentCreatedByQueryOptions, iconUrlQueryOptions, importProjectConfigMutationOptions, leaveProjectMutationOptions, projectByIdQueryOptions, projectMemberByIdQueryOptions, projectMembersQueryOptions, projectSettingsQueryOptions, projectsQueryOptions, updateProjectSettingsMutationOptions, } from '../lib/react-query/projects.js';
2
+ import { useSyncExternalStore } from 'react';
3
+ import { addServerPeerMutationOptions, attachmentUrlQueryOptions, createBlobMutationOptions, createProjectMutationOptions, documentCreatedByQueryOptions, iconUrlQueryOptions, importProjectConfigMutationOptions, leaveProjectMutationOptions, projectByIdQueryOptions, projectMemberByIdQueryOptions, projectMembersQueryOptions, projectOwnRoleQueryOptions, projectSettingsQueryOptions, projectsQueryOptions, startSyncMutationOptions, stopSyncMutationOptions, updateProjectSettingsMutationOptions, } from '../lib/react-query/projects.js';
4
+ import { SyncStore } from '../lib/sync.js';
3
5
  import { useClientApi } from './client.js';
4
6
  /**
5
7
  * Retrieve the project settings for a project.
@@ -246,6 +248,26 @@ export function useDocumentCreatedBy({ projectId, originalVersionId, }) {
246
248
  const { data, error, isRefetching } = useSuspenseQuery(documentCreatedByQueryOptions({ projectApi, projectId, originalVersionId }));
247
249
  return { data, error, isRefetching };
248
250
  }
251
+ /**
252
+ * Get the role for the current device in a specified project.
253
+ * This is a more convenient alternative to using the `useOwnDeviceInfo` and `useManyMembers` hooks.
254
+ *
255
+ * @param opts.projectId Project public ID
256
+ *
257
+ * @example
258
+ * ```tsx
259
+ * function BasicExample() {
260
+ * const { data } = useOwnRoleInProject({
261
+ * projectId: '...',
262
+ * })
263
+ * }
264
+ * ```
265
+ */
266
+ export function useOwnRoleInProject({ projectId }) {
267
+ const { data: projectApi } = useSingleProject({ projectId });
268
+ const { data, error, isRefetching } = useSuspenseQuery(projectOwnRoleQueryOptions({ projectApi, projectId }));
269
+ return { data, error, isRefetching };
270
+ }
249
271
  export function useAddServerPeer({ projectId }) {
250
272
  const queryClient = useQueryClient();
251
273
  const { data: projectApi } = useSingleProject({ projectId });
@@ -302,3 +324,59 @@ export function useCreateBlob({ projectId }) {
302
324
  const { mutate, reset, status } = useMutation(createBlobMutationOptions({ projectApi }));
303
325
  return { mutate, reset, status };
304
326
  }
327
+ const PROJECT_SYNC_STORE_MAP = new WeakMap();
328
+ function useSyncStore({ projectId }) {
329
+ const { data: projectApi } = useSingleProject({ projectId });
330
+ let syncStore = PROJECT_SYNC_STORE_MAP.get(projectApi);
331
+ if (!syncStore) {
332
+ syncStore = new SyncStore(projectApi);
333
+ PROJECT_SYNC_STORE_MAP.set(projectApi, syncStore);
334
+ }
335
+ return syncStore;
336
+ }
337
+ /**
338
+ * Hook to subscribe to the current sync state.
339
+ *
340
+ * Creates a global singleton for each project, to minimize traffic over IPC -
341
+ * this hook can safely be used in more than one place without attaching
342
+ * additional listeners across the IPC channel.
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * function Example() {
347
+ * const syncState = useSyncState({ projectId });
348
+ *
349
+ * if (!syncState) {
350
+ * // Sync information hasn't been loaded yet
351
+ * }
352
+ *
353
+ * // Actual info about sync state is available...
354
+ * }
355
+ * ```
356
+ *
357
+ * @param opts.projectId Project public ID
358
+ */
359
+ export function useSyncState({ projectId, }) {
360
+ const syncStore = useSyncStore({ projectId });
361
+ const { subscribe, getStateSnapshot } = syncStore;
362
+ return useSyncExternalStore(subscribe, getStateSnapshot);
363
+ }
364
+ /**
365
+ * Provides the progress of data sync for sync-enabled connected peers
366
+ *
367
+ * @returns `null` if no sync state events have been received. Otherwise returns a value between 0 and 1 (inclusive)
368
+ */
369
+ export function useDataSyncProgress({ projectId, }) {
370
+ const { subscribe, getDataProgressSnapshot } = useSyncStore({ projectId });
371
+ return useSyncExternalStore(subscribe, getDataProgressSnapshot);
372
+ }
373
+ export function useStartSync({ projectId }) {
374
+ const { data: projectApi } = useSingleProject({ projectId });
375
+ const { mutate, reset, status } = useMutation(startSyncMutationOptions({ projectApi }));
376
+ return { mutate, reset, status };
377
+ }
378
+ export function useStopSync({ projectId }) {
379
+ const { data: projectApi } = useSingleProject({ projectId });
380
+ const { mutate, reset, status } = useMutation(stopSyncMutationOptions({ projectApi }));
381
+ return { mutate, reset, status };
382
+ }
@@ -3,10 +3,6 @@ export { useClientApi, useIsArchiveDevice, useOwnDeviceInfo, useSetIsArchiveDevi
3
3
  export { useCreateDocument, useDeleteDocument, useManyDocs, useSingleDocByDocId, useSingleDocByVersionId, useUpdateDocument, } from './hooks/documents.js';
4
4
  export { useAcceptInvite, useRejectInvite, useRequestCancelInvite, useSendInvite, } from './hooks/invites.js';
5
5
  export { useMapStyleUrl } from './hooks/maps.js';
6
- export { useAddServerPeer, useAttachmentUrl, useCreateBlob, useCreateProject, useDocumentCreatedBy, useIconUrl, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useProjectSettings, useSingleMember, useSingleProject, useUpdateProjectSettings, } from './hooks/projects.js';
7
- export { getClientQueryKey, getDeviceInfoQueryKey, getIsArchiveDeviceQueryKey, } from './lib/react-query/client.js';
8
- export { getDocumentByDocIdQueryKey, getDocumentByVersionIdQueryKey, getDocumentsQueryKey, getManyDocumentsQueryKey, type WriteableDocument, type WriteableDocumentType, type WriteableValue, } from './lib/react-query/documents.js';
9
- export { getInvitesQueryKey, getPendingInvitesQueryKey, } from './lib/react-query/invites.js';
10
- export { getMapsQueryKey, getStyleJsonUrlQueryKey, } from './lib/react-query/maps.js';
11
- export { getAttachmentUrlQueryKey, getDocumentCreatedByQueryKey, getIconUrlQueryKey, getMemberByIdQueryKey, getMembersQueryKey, getProjectByIdQueryKey, getProjectRoleQueryKey, getProjectSettingsQueryKey, getProjectsQueryKey, } from './lib/react-query/projects.js';
12
- export { ROOT_QUERY_KEY } from './lib/react-query/shared.js';
6
+ export { useAddServerPeer, useAttachmentUrl, useCreateBlob, useCreateProject, useDataSyncProgress, useDocumentCreatedBy, useIconUrl, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useOwnRoleInProject, useProjectSettings, useSingleMember, useSingleProject, useStartSync, useStopSync, useSyncState, useUpdateProjectSettings, } from './hooks/projects.js';
7
+ export { type SyncState } from './lib/sync.js';
8
+ export { type WriteableDocument, type WriteableDocumentType, type WriteableValue, } from './lib/types.js';
package/dist/esm/index.js CHANGED
@@ -3,10 +3,4 @@ export { useClientApi, useIsArchiveDevice, useOwnDeviceInfo, useSetIsArchiveDevi
3
3
  export { useCreateDocument, useDeleteDocument, useManyDocs, useSingleDocByDocId, useSingleDocByVersionId, useUpdateDocument, } from './hooks/documents.js';
4
4
  export { useAcceptInvite, useRejectInvite, useRequestCancelInvite, useSendInvite, } from './hooks/invites.js';
5
5
  export { useMapStyleUrl } from './hooks/maps.js';
6
- export { useAddServerPeer, useAttachmentUrl, useCreateBlob, useCreateProject, useDocumentCreatedBy, useIconUrl, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useProjectSettings, useSingleMember, useSingleProject, useUpdateProjectSettings, } from './hooks/projects.js';
7
- export { getClientQueryKey, getDeviceInfoQueryKey, getIsArchiveDeviceQueryKey, } from './lib/react-query/client.js';
8
- export { getDocumentByDocIdQueryKey, getDocumentByVersionIdQueryKey, getDocumentsQueryKey, getManyDocumentsQueryKey, } from './lib/react-query/documents.js';
9
- export { getInvitesQueryKey, getPendingInvitesQueryKey, } from './lib/react-query/invites.js';
10
- export { getMapsQueryKey, getStyleJsonUrlQueryKey, } from './lib/react-query/maps.js';
11
- export { getAttachmentUrlQueryKey, getDocumentCreatedByQueryKey, getIconUrlQueryKey, getMemberByIdQueryKey, getMembersQueryKey, getProjectByIdQueryKey, getProjectRoleQueryKey, getProjectSettingsQueryKey, getProjectsQueryKey, } from './lib/react-query/projects.js';
12
- export { ROOT_QUERY_KEY } from './lib/react-query/shared.js';
6
+ export { useAddServerPeer, useAttachmentUrl, useCreateBlob, useCreateProject, useDataSyncProgress, useDocumentCreatedBy, useIconUrl, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useOwnRoleInProject, useProjectSettings, useSingleMember, useSingleProject, useStartSync, useStopSync, useSyncState, useUpdateProjectSettings, } from './hooks/projects.js';
@@ -1,13 +1,6 @@
1
1
  import type { MapeoProjectApi } from '@comapeo/ipc' with { 'resolution-mode': 'import' };
2
- import type { MapeoDoc, MapeoValue } from '@comapeo/schema' with { 'resolution-mode': 'import' };
3
2
  import { type QueryClient } from '@tanstack/react-query';
4
- export type WriteableDocumentType = Extract<MapeoDoc['schemaName'], 'field' | 'observation' | 'preset' | 'track' | 'remoteDetectionAlert'>;
5
- export type WriteableValue<D extends WriteableDocumentType> = Extract<MapeoValue, {
6
- schemaName: D;
7
- }>;
8
- export type WriteableDocument<D extends WriteableDocumentType> = Extract<MapeoDoc, {
9
- schemaName: D;
10
- }>;
3
+ import { WriteableDocument, WriteableDocumentType, WriteableValue } from '../types.js';
11
4
  export declare function getDocumentsQueryKey<D extends WriteableDocumentType>({ projectId, docType, }: {
12
5
  projectId: string;
13
6
  docType: D;
@@ -330,3 +330,19 @@ export declare function createBlobMutationOptions({ projectApi, }: {
330
330
  networkMode: "always";
331
331
  retry: false;
332
332
  };
333
+ export declare function startSyncMutationOptions({ projectApi, }: {
334
+ projectApi: MapeoProjectApi;
335
+ }): {
336
+ mutationFn: (opts: {
337
+ autostopDataSyncAfter: number | null;
338
+ } | undefined) => Promise<void>;
339
+ networkMode: "always";
340
+ retry: false;
341
+ };
342
+ export declare function stopSyncMutationOptions({ projectApi, }: {
343
+ projectApi: MapeoProjectApi;
344
+ }): {
345
+ mutationFn: () => Promise<void>;
346
+ networkMode: "always";
347
+ retry: false;
348
+ };
@@ -207,3 +207,21 @@ export function createBlobMutationOptions({ projectApi, }) {
207
207
  },
208
208
  };
209
209
  }
210
+ export function startSyncMutationOptions({ projectApi, }) {
211
+ return {
212
+ ...baseMutationOptions(),
213
+ mutationFn: async (opts) => {
214
+ // Have to avoid passing `undefined` explicitly
215
+ // See https://github.com/digidem/rpc-reflector/issues/21
216
+ return opts ? projectApi.$sync.start(opts) : projectApi.$sync.start();
217
+ },
218
+ };
219
+ }
220
+ export function stopSyncMutationOptions({ projectApi, }) {
221
+ return {
222
+ ...baseMutationOptions(),
223
+ mutationFn: async () => {
224
+ return projectApi.$sync.stop();
225
+ },
226
+ };
227
+ }
@@ -0,0 +1,10 @@
1
+ import type { MapeoProjectApi } from '@comapeo/ipc' with { 'resolution-mode': 'import' };
2
+ export type SyncState = Awaited<ReturnType<MapeoProjectApi['$sync']['getState']>>;
3
+ export declare function getDataSyncCountForDevice(syncStateForDevice: SyncState['remoteDeviceSyncState'][string]): number;
4
+ export declare class SyncStore {
5
+ #private;
6
+ constructor(project: MapeoProjectApi);
7
+ subscribe: (listener: () => void) => () => void;
8
+ getStateSnapshot: () => import("@comapeo/core/dist/sync/sync-api.js").State | null;
9
+ getDataProgressSnapshot: () => number | null;
10
+ }
@@ -0,0 +1,114 @@
1
+ export function getDataSyncCountForDevice(syncStateForDevice) {
2
+ const { data } = syncStateForDevice;
3
+ return data.want + data.wanted;
4
+ }
5
+ export class SyncStore {
6
+ #project;
7
+ #listeners = new Set();
8
+ #isSubscribedInternal = false;
9
+ #error = null;
10
+ #state = null;
11
+ // Used for calculating sync progress
12
+ #perDeviceMaxSyncCount = new Map();
13
+ constructor(project) {
14
+ this.#project = project;
15
+ }
16
+ subscribe = (listener) => {
17
+ this.#listeners.add(listener);
18
+ if (!this.#isSubscribedInternal)
19
+ this.#startSubscription();
20
+ return () => {
21
+ this.#listeners.delete(listener);
22
+ if (this.#listeners.size === 0)
23
+ this.#stopSubscription();
24
+ };
25
+ };
26
+ getStateSnapshot = () => {
27
+ if (this.#error)
28
+ throw this.#error;
29
+ return this.#state;
30
+ };
31
+ getDataProgressSnapshot = () => {
32
+ if (this.#state === null) {
33
+ return null;
34
+ }
35
+ let currentSyncCount = 0;
36
+ let totalMaxSyncCount = 0;
37
+ let otherEnabledDevicesExist = false;
38
+ for (const [deviceId, deviceSyncState] of Object.entries(this.#state.remoteDeviceSyncState)) {
39
+ if (deviceSyncState.data.isSyncEnabled) {
40
+ otherEnabledDevicesExist = true;
41
+ }
42
+ else {
43
+ continue;
44
+ }
45
+ const existingMaxCount = this.#perDeviceMaxSyncCount.get(deviceId);
46
+ if (typeof existingMaxCount === 'number' && existingMaxCount > 0) {
47
+ currentSyncCount = getDataSyncCountForDevice(deviceSyncState);
48
+ totalMaxSyncCount += existingMaxCount;
49
+ }
50
+ }
51
+ if (!otherEnabledDevicesExist) {
52
+ return null;
53
+ }
54
+ if (totalMaxSyncCount === 0) {
55
+ return 1;
56
+ }
57
+ const ratio = (totalMaxSyncCount - currentSyncCount) / totalMaxSyncCount;
58
+ if (ratio <= 0)
59
+ return 0;
60
+ if (ratio >= 1)
61
+ return 1;
62
+ return clamp(ratio, 0.01, 0.99);
63
+ };
64
+ #notifyListeners() {
65
+ for (const listener of this.#listeners) {
66
+ listener();
67
+ }
68
+ }
69
+ #onSyncState = (state) => {
70
+ const dataSyncWasEnabled = this.#state
71
+ ? this.#state.data.isSyncEnabled
72
+ : false;
73
+ // Reset map keeping track of counts used for progress if data sync is toggled
74
+ if (dataSyncWasEnabled !== state.data.isSyncEnabled) {
75
+ this.#perDeviceMaxSyncCount.clear();
76
+ }
77
+ else {
78
+ // Remove devices from #perDeviceMaxSyncCount that are no longer found in the new sync state
79
+ for (const deviceId of this.#perDeviceMaxSyncCount.keys()) {
80
+ if (!Object.hasOwn(state.remoteDeviceSyncState, deviceId)) {
81
+ this.#perDeviceMaxSyncCount.delete(deviceId);
82
+ }
83
+ }
84
+ }
85
+ for (const [deviceId, stateForDevice] of Object.entries(state.remoteDeviceSyncState)) {
86
+ const existingCount = this.#perDeviceMaxSyncCount.get(deviceId);
87
+ const newCount = getDataSyncCountForDevice(stateForDevice);
88
+ if (existingCount === undefined || existingCount < newCount) {
89
+ this.#perDeviceMaxSyncCount.set(deviceId, newCount);
90
+ }
91
+ }
92
+ this.#state = state;
93
+ this.#error = null;
94
+ this.#notifyListeners();
95
+ };
96
+ #startSubscription = () => {
97
+ this.#project.$sync.on('sync-state', this.#onSyncState);
98
+ this.#isSubscribedInternal = true;
99
+ this.#project.$sync
100
+ .getState()
101
+ .then(this.#onSyncState)
102
+ .catch((e) => {
103
+ this.#error = e;
104
+ this.#notifyListeners();
105
+ });
106
+ };
107
+ #stopSubscription = () => {
108
+ this.#isSubscribedInternal = false;
109
+ this.#project.$sync.off('sync-state', this.#onSyncState);
110
+ };
111
+ }
112
+ function clamp(value, min, max) {
113
+ return Math.max(min, Math.min(value, max));
114
+ }
@@ -0,0 +1,9 @@
1
+ import type { MapeoDoc, MapeoValue } from '@comapeo/schema' with { 'resolution-mode': 'import' };
2
+ export type WriteableDocumentType = Extract<MapeoDoc['schemaName'], 'field' | 'observation' | 'preset' | 'track' | 'remoteDetectionAlert'>;
3
+ export type WriteableValue<D extends WriteableDocumentType> = Extract<MapeoValue, {
4
+ schemaName: D;
5
+ }>;
6
+ export type WriteableDocument<D extends WriteableDocumentType> = Extract<MapeoDoc, {
7
+ schemaName: D;
8
+ }>;
9
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/docs/API.md CHANGED
@@ -14,12 +14,17 @@
14
14
  - [useIconUrl](#useiconurl)
15
15
  - [useAttachmentUrl](#useattachmenturl)
16
16
  - [useDocumentCreatedBy](#usedocumentcreatedby)
17
+ - [useOwnRoleInProject](#useownroleinproject)
17
18
  - [useAddServerPeer](#useaddserverpeer)
18
19
  - [useCreateProject](#usecreateproject)
19
20
  - [useLeaveProject](#useleaveproject)
20
21
  - [useImportProjectConfig](#useimportprojectconfig)
21
22
  - [useUpdateProjectSettings](#useupdateprojectsettings)
22
23
  - [useCreateBlob](#usecreateblob)
24
+ - [useSyncState](#usesyncstate)
25
+ - [useDataSyncProgress](#usedatasyncprogress)
26
+ - [useStartSync](#usestartsync)
27
+ - [useStopSync](#usestopsync)
23
28
  - [useSingleDocByDocId](#usesingledocbydocid)
24
29
  - [useSingleDocByVersionId](#usesingledocbyversionid)
25
30
  - [useManyDocs](#usemanydocs)
@@ -38,7 +43,7 @@ Create a context provider that holds a CoMapeo API client instance.
38
43
 
39
44
  | Function | Type |
40
45
  | ---------- | ---------- |
41
- | `ClientApiProvider` | `({ children, clientApi, }: { children: ReactNode; clientApi: MapeoClientApi; }) => FunctionComponentElement<ProviderProps<MapeoClientApi or null>>` |
46
+ | `ClientApiProvider` | `({ children, clientApi, }: { children: ReactNode; clientApi: MapeoClientApi; }) => Element` |
42
47
 
43
48
  Parameters:
44
49
 
@@ -373,6 +378,31 @@ function BasicExample() {
373
378
  ```
374
379
 
375
380
 
381
+ ### useOwnRoleInProject
382
+
383
+ Get the role for the current device in a specified project.
384
+ This is a more convenient alternative to using the `useOwnDeviceInfo` and `useManyMembers` hooks.
385
+
386
+ | Function | Type |
387
+ | ---------- | ---------- |
388
+ | `useOwnRoleInProject` | `({ projectId }: { projectId: string; }) => { data: Role<"a12a6702b93bd7ff" or "f7c150f5a3a9a855" or "012fd2d431c0bf60" or "9e6d29263cba36c9" or "8ced989b1904606b" or "08e4251e36f6e7ed">; error: Error or null; isRefetching: boolean; }` |
389
+
390
+ Parameters:
391
+
392
+ * `opts.projectId`: Project public ID
393
+
394
+
395
+ Examples:
396
+
397
+ ```tsx
398
+ function BasicExample() {
399
+ const { data } = useOwnRoleInProject({
400
+ projectId: '...',
401
+ })
402
+ }
403
+ ```
404
+
405
+
376
406
  ### useAddServerPeer
377
407
 
378
408
  | Function | Type |
@@ -427,13 +457,65 @@ Create a blob for a project.
427
457
 
428
458
  | Function | Type |
429
459
  | ---------- | ---------- |
430
- | `useCreateBlob` | `({ projectId }: { projectId: string; }) => { mutate: UseMutateFunction<{ driveId: string; name: string; type: "audio" or "video" or "photo"; hash: string; }, Error, { original: string; preview?: string or undefined; thumbnail?: string or undefined; metadata: Metadata; }, unknown>; reset: () => void; status: "pending" or ...` |
460
+ | `useCreateBlob` | `({ projectId }: { projectId: string; }) => { mutate: UseMutateFunction<{ driveId: string; name: string; type: "photo" or "audio" or "video"; hash: string; }, Error, { original: string; preview?: string or undefined; thumbnail?: string or undefined; metadata: Metadata; }, unknown>; reset: () => void; status: "pending" or ...` |
431
461
 
432
462
  Parameters:
433
463
 
434
464
  * `opts.projectId`: Public project ID of project to apply to changes to.
435
465
 
436
466
 
467
+ ### useSyncState
468
+
469
+ Hook to subscribe to the current sync state.
470
+
471
+ Creates a global singleton for each project, to minimize traffic over IPC -
472
+ this hook can safely be used in more than one place without attaching
473
+ additional listeners across the IPC channel.
474
+
475
+ | Function | Type |
476
+ | ---------- | ---------- |
477
+ | `useSyncState` | `({ projectId, }: { projectId: string; }) => State or null` |
478
+
479
+ Parameters:
480
+
481
+ * `opts.projectId`: Project public ID
482
+
483
+
484
+ Examples:
485
+
486
+ ```ts
487
+ function Example() {
488
+ const syncState = useSyncState({ projectId });
489
+
490
+ if (!syncState) {
491
+ // Sync information hasn't been loaded yet
492
+ }
493
+
494
+ // Actual info about sync state is available...
495
+ }
496
+ ```
497
+
498
+
499
+ ### useDataSyncProgress
500
+
501
+ Provides the progress of data sync for sync-enabled connected peers
502
+
503
+ | Function | Type |
504
+ | ---------- | ---------- |
505
+ | `useDataSyncProgress` | `({ projectId, }: { projectId: string; }) => number or null` |
506
+
507
+ ### useStartSync
508
+
509
+ | Function | Type |
510
+ | ---------- | ---------- |
511
+ | `useStartSync` | `({ projectId }: { projectId: string; }) => { mutate: UseMutateFunction<void, Error, { autostopDataSyncAfter: number or null; } or undefined, unknown>; reset: () => void; status: "pending" or ... 2 more ... or "idle"; }` |
512
+
513
+ ### useStopSync
514
+
515
+ | Function | Type |
516
+ | ---------- | ---------- |
517
+ | `useStopSync` | `({ projectId }: { projectId: string; }) => { mutate: UseMutateFunction<void, Error, void, unknown>; reset: () => void; status: "pending" or "error" or "success" or "idle"; }` |
518
+
437
519
  ### useSingleDocByDocId
438
520
 
439
521
  Retrieve a single document from the database based on the document's document ID.
@@ -678,3 +760,28 @@ function ExampleWithRefreshToken() {
678
760
  | `ClientApiContext` | `Context<MapeoClientApi or null>` |
679
761
 
680
762
 
763
+
764
+ ## Types
765
+
766
+ - [WriteableDocumentType](#writeabledocumenttype)
767
+ - [WriteableValue](#writeablevalue)
768
+ - [WriteableDocument](#writeabledocument)
769
+
770
+ ### WriteableDocumentType
771
+
772
+ | Type | Type |
773
+ | ---------- | ---------- |
774
+ | `WriteableDocumentType` | `Extract< MapeoDoc['schemaName'], 'field' or 'observation' or 'preset' or 'track' or 'remoteDetectionAlert' >` |
775
+
776
+ ### WriteableValue
777
+
778
+ | Type | Type |
779
+ | ---------- | ---------- |
780
+ | `WriteableValue` | `Extract< MapeoValue, { schemaName: D } >` |
781
+
782
+ ### WriteableDocument
783
+
784
+ | Type | Type |
785
+ | ---------- | ---------- |
786
+ | `WriteableDocument` | `Extract< MapeoDoc, { schemaName: D } >` |
787
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comapeo/core-react",
3
- "version": "2.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "React wrapper for working with @comapeo/core",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,7 +35,6 @@
35
35
  "types": "./dist/commonjs/index.d.ts",
36
36
  "module": "./dist/esm/index.js",
37
37
  "files": [
38
- "CHANGELOG.md",
39
38
  "dist/",
40
39
  "docs/API.md"
41
40
  ],
@@ -48,16 +47,14 @@
48
47
  }
49
48
  },
50
49
  "scripts": {
51
- "prepare:husky": "husky",
52
- "prepare:tshy": "tshy",
53
- "prepare": "npm-run-all --parallel prepare:*",
50
+ "docs:generate": "tsdoc --src=src/contexts/*,src/hooks/*,src/lib/types.ts --dest=docs/API.md --noemoji --types",
54
51
  "lint:eslint": "eslint --cache .",
55
52
  "lint:format": "prettier --cache --check .",
56
53
  "lint": "npm-run-all --parallel --continue-on-error --print-label --aggregate-output lint:*",
57
- "types": "tsc",
58
- "test:unit": "vitest run",
59
- "test": "npm-run-all --parallel --continue-on-error --print-label --aggregate-output types test:*",
60
- "docs:generate": "tsdoc --src=src/contexts/*,src/hooks/* --dest=docs/API.md --noemoji --types"
54
+ "prepack": "tshy",
55
+ "prepare": "husky",
56
+ "test": "vitest run",
57
+ "types": "tsc"
61
58
  },
62
59
  "peerDependencies": {
63
60
  "@comapeo/core": "*",
@@ -67,34 +64,33 @@
67
64
  "react": "^18 || ^19"
68
65
  },
69
66
  "devDependencies": {
70
- "@comapeo/core": "2.3.0",
71
- "@comapeo/schema": "1.3.0",
72
- "@eslint/compat": "1.2.5",
73
- "@eslint/js": "9.18.0",
67
+ "@comapeo/core": "2.3.1",
68
+ "@comapeo/ipc": "2.1.0",
69
+ "@comapeo/schema": "1.4.1",
70
+ "@eslint/compat": "1.2.7",
71
+ "@eslint/js": "9.23.0",
74
72
  "@ianvs/prettier-plugin-sort-imports": "4.4.1",
75
73
  "@mapeo/crypto": "1.0.0-alpha.10",
76
- "@tanstack/eslint-plugin-query": "5.62.16",
77
- "@tanstack/react-query": "5.64.1",
74
+ "@tanstack/eslint-plugin-query": "5.68.0",
75
+ "@tanstack/react-query": "5.69.0",
78
76
  "@testing-library/dom": "10.4.0",
79
- "@testing-library/react": "16.1.0",
80
- "@types/lint-staged": "13.3.0",
81
- "@types/node": "22.10.5",
82
- "@types/react": "19.0.6",
83
- "@types/react-dom": "19.0.3",
84
- "commit-and-tag-version": "12.5.0",
85
- "eslint": "9.18.0",
77
+ "@testing-library/react": "16.2.0",
78
+ "@types/node": "22.13.13",
79
+ "@types/react": "19.0.12",
80
+ "@types/react-dom": "19.0.4",
81
+ "eslint": "9.23.0",
86
82
  "fastify": "4.29.0",
87
- "globals": "15.14.0",
83
+ "globals": "16.0.0",
88
84
  "husky": "9.1.7",
89
- "lint-staged": "15.3.0",
85
+ "lint-staged": "15.5.0",
90
86
  "npm-run-all2": "7.0.2",
91
- "prettier": "3.4.2",
87
+ "prettier": "3.5.3",
92
88
  "random-access-memory": "6.2.1",
93
89
  "react": "19.0.0",
94
- "tsdoc-markdown": "1.1.0",
90
+ "tsdoc-markdown": "1.2.0",
95
91
  "tshy": "3.0.2",
96
- "typescript": "5.7.3",
97
- "typescript-eslint": "8.20.0",
98
- "vitest": "2.1.8"
92
+ "typescript": "5.8.2",
93
+ "typescript-eslint": "8.27.0",
94
+ "vitest": "3.0.9"
99
95
  }
100
96
  }