@comapeo/core-react 10.0.1 → 11.0.1

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.
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DeclineReason = void 0;
6
+ exports.InvalidStatusTransitionError = exports.MapShareCanceledError = exports.DeclineReason = exports.MapShareErrorCode = void 0;
7
+ exports.getErrorCode = getErrorCode;
7
8
  exports.createReceivedMapSharesStore = createReceivedMapSharesStore;
8
9
  exports.createSentMapSharesStore = createSentMapSharesStore;
9
10
  const constants_js_1 = require("@comapeo/map-server/constants.js");
@@ -16,6 +17,118 @@ const react_query_js_1 = require("./react-query.js");
16
17
  // functions - if the documentation comments are added inline for the store
17
18
  // actions, they do not show for the mutate() function in hooks.
18
19
  // ============================================
20
+ /**
21
+ * Error codes for map share operations. Use with {@link getErrorCode} to safely
22
+ * check the error code of an unknown error thrown by a map share mutation, or
23
+ * to check the `error.code` on a share in `status='error'`.
24
+ *
25
+ * ## Receiver errors
26
+ *
27
+ * **Mutation errors** (thrown by receiver hooks, check via
28
+ * `getErrorCode(mutation.error)`):
29
+ * - `MAP_SHARE_CANCELED` — the sender canceled the share
30
+ * - `INVALID_STATUS_TRANSITION` — the action is not valid for the share's
31
+ * current status (e.g. declining a share that is already downloading)
32
+ * - `MAP_SHARE_NOT_FOUND` — no share with the given `shareId` exists
33
+ * - `DOWNLOAD_NOT_FOUND` — abort was called but no download is tracked for
34
+ * this share
35
+ *
36
+ * **Share state errors** (on received `share.error.code` when
37
+ * `share.status === 'error'`):
38
+ * - `DOWNLOAD_ERROR` — the download failed (network, disk, or server error)
39
+ * - `DECLINE_CANNOT_CONNECT` — the decline was accepted locally but the sender
40
+ * could not be reached to notify them
41
+ *
42
+ * ## Sender errors
43
+ *
44
+ * **Mutation errors** (thrown by sender hooks, check via
45
+ * `getErrorCode(mutation.error)`):
46
+ * - `INVALID_STATUS_TRANSITION` — the action is not valid for the share's
47
+ * current status (e.g. canceling a share that is already canceled)
48
+ * - `MAP_SHARE_NOT_FOUND` — no share with the given `shareId` exists
49
+ *
50
+ * **Share state errors** (on sent `share.error.code` when
51
+ * `share.status === 'error'`):
52
+ * - `CANCEL_SHARE_NOT_CANCELABLE` — the cancel request reached the server but
53
+ * the share is already in a final state (e.g. completed or declined)
54
+ *
55
+ * ## Common
56
+ *
57
+ * - `UNKNOWN_ERROR` — fallback when the original error has no specific code.
58
+ * Can appear as both a mutation error and a share state error for either
59
+ * sender or receiver.
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * import { getErrorCode, MapShareErrorCode } from '@comapeo/core-react'
64
+ *
65
+ * // Receiver: checking a mutation error
66
+ * const decline = useDeclineReceivedMapShare()
67
+ * // ... after mutation fails:
68
+ * if (getErrorCode(decline.error) === MapShareErrorCode.MAP_SHARE_CANCELED) {
69
+ * // Show "this share was canceled by the sender"
70
+ * }
71
+ * ```
72
+ *
73
+ * @example
74
+ * ```tsx
75
+ * // Receiver: checking a share state error
76
+ * const share = useSingleReceivedMapShare({ shareId })
77
+ * if (share.status === 'error') {
78
+ * if (share.error.code === MapShareErrorCode.DOWNLOAD_ERROR) {
79
+ * // Show "download failed, try again?"
80
+ * }
81
+ * }
82
+ * ```
83
+ */
84
+ exports.MapShareErrorCode = {
85
+ // --- Receiver mutation errors (thrown by receiver actions) ---
86
+ /** Receiver: the sender canceled the share before the action could complete */
87
+ MAP_SHARE_CANCELED: 'MAP_SHARE_CANCELED',
88
+ /** Receiver/Sender: the action is not valid for the share's current status */
89
+ INVALID_STATUS_TRANSITION: 'INVALID_STATUS_TRANSITION',
90
+ /** Receiver/Sender: no map share with the given `shareId` exists in the store */
91
+ MAP_SHARE_NOT_FOUND: 'MAP_SHARE_NOT_FOUND',
92
+ /** Receiver: abort was called but no download is tracked for this share */
93
+ DOWNLOAD_NOT_FOUND: 'DOWNLOAD_NOT_FOUND',
94
+ // --- Receiver share state errors (in share.error.code) ---
95
+ /** Receiver: the download failed due to a network, disk, or server error */
96
+ DOWNLOAD_ERROR: 'DOWNLOAD_ERROR',
97
+ /** Receiver: could not connect to the sender to notify them of the decline */
98
+ DECLINE_CANNOT_CONNECT: 'DECLINE_CANNOT_CONNECT',
99
+ // --- Sender share state errors (in share.error.code) ---
100
+ /** Sender: cancel failed because the share is already in a final state on the server */
101
+ CANCEL_SHARE_NOT_CANCELABLE: 'CANCEL_SHARE_NOT_CANCELABLE',
102
+ // --- Common ---
103
+ /** Receiver/Sender: fallback code when the original error has no specific code */
104
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
105
+ };
106
+ /**
107
+ * Safely extract the `code` property from an unknown error. Returns `undefined`
108
+ * if the value is not an Error or has no string `code` property.
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * import { getErrorCode, MapShareErrorCode } from '@comapeo/core-react'
113
+ *
114
+ * try {
115
+ * await decline.mutateAsync({ shareId, reason: 'user_rejected' })
116
+ * } catch (e) {
117
+ * const code = getErrorCode(e)
118
+ * if (code === MapShareErrorCode.MAP_SHARE_CANCELED) {
119
+ * // handle cancellation
120
+ * }
121
+ * }
122
+ * ```
123
+ */
124
+ function getErrorCode(error) {
125
+ if (error instanceof Error &&
126
+ 'code' in error &&
127
+ typeof error.code === 'string') {
128
+ return error.code;
129
+ }
130
+ return undefined;
131
+ }
19
132
  /** Known reasons for declining a map share */
20
133
  exports.DeclineReason = {
21
134
  /** User explicitly rejected the map share */
@@ -23,6 +136,32 @@ exports.DeclineReason = {
23
136
  /** Device storage is full */
24
137
  storage_full: 'storage_full',
25
138
  };
139
+ /**
140
+ * Thrown when a receiver action (download, decline, or abort) is attempted on a
141
+ * map share that has been canceled by the sender. Has `code: 'MAP_SHARE_CANCELED'`.
142
+ */
143
+ class MapShareCanceledError extends Error {
144
+ code = 'MAP_SHARE_CANCELED';
145
+ constructor(shareId) {
146
+ super(`Map share ${shareId} has been canceled by the sender`);
147
+ this.name = 'MapShareCanceledError';
148
+ }
149
+ }
150
+ exports.MapShareCanceledError = MapShareCanceledError;
151
+ /**
152
+ * Thrown when an action is attempted on a map share whose current status does
153
+ * not allow the requested transition (e.g. declining a share that is already
154
+ * downloading, or aborting a share that is still pending).
155
+ * Has `code: 'INVALID_STATUS_TRANSITION'`.
156
+ */
157
+ class InvalidStatusTransitionError extends Error {
158
+ code = 'INVALID_STATUS_TRANSITION';
159
+ constructor(current, next) {
160
+ super(`Invalid status transition from ${current} to ${next}`);
161
+ this.name = 'InvalidStatusTransitionError';
162
+ }
163
+ }
164
+ exports.InvalidStatusTransitionError = InvalidStatusTransitionError;
26
165
  /**
27
166
  * This is like a mini zustand store. Keeping the map shares in an external
28
167
  * store avoids unnecessary re-renders of the entire app when map shares are
@@ -143,6 +282,13 @@ function createReceivedMapSharesStore({ clientApi, mapServerApi, queryClient, })
143
282
  const actions = {
144
283
  async download({ shareId }) {
145
284
  const mapShare = get(shareId);
285
+ // This path should be be impossible, because the map share only receives
286
+ // status updates from the receiver after the download starts, but adding
287
+ // for completeness, and the edge-case of the receiving trying to
288
+ // download() a second time.
289
+ if (mapShare.status === 'canceled') {
290
+ throw new MapShareCanceledError(shareId);
291
+ }
146
292
  update(shareId, { status: 'downloading', bytesDownloaded: 0 });
147
293
  try {
148
294
  const downloadIdPromise = mapServerApi
@@ -184,22 +330,39 @@ function createReceivedMapSharesStore({ clientApi, mapServerApi, queryClient, })
184
330
  },
185
331
  async decline({ shareId, reason }) {
186
332
  const mapShare = get(shareId);
187
- update(shareId, { status: 'declined', reason });
333
+ if (mapShare.status === 'canceled') {
334
+ throw new MapShareCanceledError(shareId);
335
+ }
336
+ if (mapShare.status !== 'pending') {
337
+ throw new InvalidStatusTransitionError(mapShare.status, 'declined');
338
+ }
188
339
  try {
189
- await mapServerApi.post(`mapShares/${shareId}/decline`, {
340
+ await mapServerApi
341
+ .post(`mapShares/${shareId}/decline`, {
190
342
  json: {
191
343
  senderDeviceId: mapShare.senderDeviceId,
192
344
  mapShareUrls: mapShare.mapShareUrls,
193
345
  reason,
194
346
  },
195
- });
347
+ })
348
+ .json();
349
+ update(shareId, { status: 'declined', reason });
196
350
  }
197
351
  catch (e) {
352
+ const error = (0, ensure_error_1.default)(e);
353
+ if ('code' in error && error.code === 'MAP_SHARE_CANCELED') {
354
+ update(shareId, { status: 'canceled' });
355
+ throw new MapShareCanceledError(shareId);
356
+ }
198
357
  handleError(shareId, e);
199
358
  throw e;
200
359
  }
201
360
  },
202
361
  async abort({ shareId }) {
362
+ const mapShare = get(shareId);
363
+ if (mapShare.status === 'canceled') {
364
+ throw new MapShareCanceledError(shareId);
365
+ }
203
366
  update(shareId, { status: 'aborted' });
204
367
  try {
205
368
  const downloadId = await downloads.get(shareId);
@@ -229,15 +392,14 @@ function createReceivedMapSharesStore({ clientApi, mapServerApi, queryClient, })
229
392
  function createSentMapSharesStore({ clientApi, mapServerApi, }) {
230
393
  const { subscribe, getSnapshot, update, add, handleError, monitor } = createMapSharesStore({ mapServerApi });
231
394
  const actions = {
232
- async createAndSend({ projectId, receiverDeviceId, mapId = constants_js_1.CUSTOM_MAP_ID, }) {
395
+ async createAndSend({ receiverDeviceId, mapId = constants_js_1.CUSTOM_MAP_ID, }) {
233
396
  const mapShare = await mapServerApi
234
397
  .post('mapShares', {
235
398
  json: { receiverDeviceId, mapId },
236
399
  })
237
400
  .json();
238
401
  try {
239
- const project = await clientApi.getProject(projectId);
240
- await project.$sendMapShare(mapShare);
402
+ await clientApi.sendMapShare(mapShare);
241
403
  }
242
404
  catch (e) {
243
405
  await mapServerApi.post(`mapShares/${mapShare.shareId}/cancel`);
@@ -279,7 +441,7 @@ const allowedStatusTransitions = {
279
441
  */
280
442
  function assertValidStatusTransition(current, next) {
281
443
  if (!allowedStatusTransitions[current].includes(next)) {
282
- throw new Error(`Invalid status transition from ${current} to ${next}`);
444
+ throw new InvalidStatusTransitionError(current, next);
283
445
  }
284
446
  }
285
447
  const finalStatuses = [
@@ -23,7 +23,7 @@ exports.getDocumentsQueryKey = getDocumentsQueryKey;
23
23
  exports.getManyDocumentsQueryKey = getManyDocumentsQueryKey;
24
24
  exports.getDocumentByDocIdQueryKey = getDocumentByDocIdQueryKey;
25
25
  exports.getDocumentByVersionIdQueryKey = getDocumentByVersionIdQueryKey;
26
- const map_server_1 = require("@comapeo/map-server");
26
+ const constants_js_1 = require("@comapeo/map-server/constants.js");
27
27
  // #region Shared
28
28
  const ROOT_QUERY_KEY = '@comapeo/core-react';
29
29
  // Since the API is running locally, queries should run regardless of network
@@ -103,7 +103,7 @@ async function invalidateMapQueries(queryClient, { mapId }) {
103
103
  queryKey: getMapQueryKey({ mapId }),
104
104
  }),
105
105
  queryClient.invalidateQueries({
106
- queryKey: getMapQueryKey({ mapId: map_server_1.DEFAULT_MAP_ID }),
106
+ queryKey: getMapQueryKey({ mapId: constants_js_1.DEFAULT_MAP_ID }),
107
107
  }),
108
108
  ]);
109
109
  }
@@ -42,7 +42,7 @@ export declare function useReceivedMapSharesState<T>(selector: (state: Array<Rec
42
42
  * @internal
43
43
  */
44
44
  export declare function useSentMapSharesActions(): {
45
- createAndSend({ projectId, receiverDeviceId, mapId, }: import("../lib/map-shares-stores.js").CreateAndSendMapShareOptions): Promise<import("@comapeo/map-server").MapShareState>;
45
+ createAndSend({ receiverDeviceId, mapId, }: import("../lib/map-shares-stores.js").CreateAndSendMapShareOptions): Promise<import("@comapeo/map-server").MapShareState>;
46
46
  cancel({ shareId }: import("../lib/map-shares-stores.js").CancelMapShareOptions): Promise<void>;
47
47
  };
48
48
  /**
@@ -141,6 +141,9 @@ export declare function useManyReceivedMapShares(): ReceivedMapShareState[];
141
141
  *
142
142
  * @param opts.shareId ID of the map share
143
143
  *
144
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no received share
145
+ * with the given `shareId` exists.
146
+ *
144
147
  * @example
145
148
  * ```tsx
146
149
  * function MapShareDetail({ shareId }: { shareId: string }) {
@@ -156,15 +159,28 @@ export declare function useSingleReceivedMapShare({ shareId }: {
156
159
  /**
157
160
  * Accept and download a map share that has been received. The mutate promise
158
161
  * resolves once the map _starts_ downloading, before it finishes downloading.
159
- * Use `useManyMapShares` or `useSingleMapShare` to track download progress.
160
- *
161
- * Throws if the share is not in `status="pending"` or if the download fails to
162
- * start (e.g. if the shareId if invalid).
162
+ * Use `useManyReceivedMapShares` or `useSingleReceivedMapShare` to track
163
+ * download progress and final status.
164
+ *
165
+ * If the sender canceled the share before the receiver calls this, the
166
+ * mutation will still resolve (the download starts), but the share status will
167
+ * end up as `'canceled'` rather than `'completed'`. This is the only way the
168
+ * receiver discovers that a share has been canceled — check `share.status`
169
+ * after the download settles.
170
+ *
171
+ * @throws An error with code `'MAP_SHARE_CANCELED'` if the share is already
172
+ * known to be canceled (i.e. `status` is `'canceled'` in the store, e.g.
173
+ * after a previous download attempt discovered the cancellation).
174
+ * @throws An error with code `'INVALID_STATUS_TRANSITION'` if the share is
175
+ * not in a valid state to start downloading (e.g. already downloading,
176
+ * completed, or declined).
177
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no share with the
178
+ * given `shareId` exists in the store.
163
179
  *
164
180
  * @example
165
181
  * ```tsx
166
182
  * function AcceptButton({ shareId }: { shareId: string }) {
167
- * const { mutate: accept } = useAcceptMapShare()
183
+ * const { mutate: accept } = useDownloadReceivedMapShare()
168
184
  *
169
185
  * return <button onClick={() => accept({ shareId })}>Accept</button>
170
186
  * }
@@ -189,17 +205,28 @@ export declare function useDownloadReceivedMapShare(): Pick<import("@tanstack/re
189
205
  }, "error" | "status" | "mutate" | "reset" | "mutateAsync">;
190
206
  /**
191
207
  * Decline a map share that has been received. Notifies the sender that the
192
- * share was declined.
193
- *
194
- * Throws if the share is not with `status="pending"`
195
- * Throws if shareId is invalid
196
- * Throws if decline request fails (e.g. network error)
208
+ * share was declined. The share status is only updated to `'declined'` after
209
+ * the server confirms the decline — there is no optimistic update.
210
+ *
211
+ * If the sender canceled the share before the decline reaches the server, the
212
+ * share status will transition to `'canceled'` (not `'error'`) and the
213
+ * mutation will throw a `MapShareCanceledError`.
214
+ *
215
+ * @throws An error with code `'MAP_SHARE_CANCELED'` if the share is already
216
+ * known to be canceled, or if the server reports that the sender canceled
217
+ * the share while the decline was in flight. In both cases `share.status`
218
+ * will be `'canceled'`.
219
+ * @throws An error with code `'INVALID_STATUS_TRANSITION'` if the share is
220
+ * not in `status='pending'` (e.g. already downloading, completed, or
221
+ * declined).
222
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no share with the
223
+ * given `shareId` exists in the store.
197
224
  *
198
225
  * @example
199
226
  * ```tsx
200
227
  * import { DeclineReason } from '@comapeo/core-react'
201
228
  * function DeclineButton({ shareId }: { shareId: string }) {
202
- * const { mutate: decline } = useDeclineMapShare()
229
+ * const { mutate: decline } = useDeclineReceivedMapShare()
203
230
  *
204
231
  * return (
205
232
  * <button onClick={() => decline({ shareId, reason: DeclineReason.user_rejected })}>
@@ -229,13 +256,20 @@ export declare function useDeclineReceivedMapShare(): Pick<import("@tanstack/rea
229
256
  /**
230
257
  * Abort an in-progress map share download.
231
258
  *
232
- * Throws if the share is not in `status="downloading"`
233
- * Throws if shareId is invalid
259
+ * @throws An error with code `'MAP_SHARE_CANCELED'` if the share is already
260
+ * known to be canceled.
261
+ * @throws An error with code `'INVALID_STATUS_TRANSITION'` if the share is
262
+ * not in `status='downloading'` (e.g. still pending, already completed, or
263
+ * declined).
264
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no share with the
265
+ * given `shareId` exists in the store.
266
+ * @throws An error with code `'DOWNLOAD_NOT_FOUND'` if no download is
267
+ * currently tracked for this share (e.g. the download was never started).
234
268
  *
235
269
  * @example
236
270
  * ```tsx
237
271
  * function AbortButton({ shareId }: { shareId: string }) {
238
- * const { mutate: abort } = useAbortMapShareDownload()
272
+ * const { mutate: abort } = useAbortReceivedMapShareDownload()
239
273
  *
240
274
  * return <button onClick={() => abort({ shareId })}>Cancel Download</button>
241
275
  * }
@@ -264,8 +298,6 @@ export declare function useAbortReceivedMapShareDownload(): Pick<import("@tansta
264
298
  * mutation resolves with the created map share object, including its ID, which
265
299
  * can be used to track the share status with `useSingleSentMapShare`.
266
300
  *
267
- * @param opts.projectId Public ID of project for sending the map share: you can only send map shares to users on the same project
268
- *
269
301
  * @example
270
302
  * ```tsx
271
303
  * function SendMapButton({ projectId, deviceId }: { projectId: string; deviceId: string }) {
@@ -274,7 +306,7 @@ export declare function useAbortReceivedMapShareDownload(): Pick<import("@tansta
274
306
  * return (
275
307
  * <button
276
308
  * onClick={() =>
277
- * send({ projectId, receiverDeviceId: deviceId, mapId: 'custom' }, {
309
+ * send({ receiverDeviceId: deviceId, mapId: 'custom' }, {
278
310
  * onSuccess: (mapShare) => {
279
311
  * console.log('Share sent with id', mapShare.shareId)
280
312
  * }
@@ -311,6 +343,12 @@ export declare function useSendMapShare(): Pick<import("@tanstack/react-query").
311
343
  * the share, the download will be canceled before completion. If the download
312
344
  * is already complete, this action will throw an error.
313
345
  *
346
+ * @throws An error with code `'INVALID_STATUS_TRANSITION'` if the share is
347
+ * not in `status='pending'` or `status='downloading'` (e.g. already
348
+ * completed, canceled, aborted, or declined).
349
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no share with the
350
+ * given `shareId` exists in the store.
351
+ *
314
352
  * @param opts.projectId Public ID of project to request the map share cancellation for.
315
353
  *
316
354
  * @example
@@ -344,7 +382,8 @@ export declare function useCancelSentMapShare(): Pick<import("@tanstack/react-qu
344
382
  * of the share, updated in real-time. When the recipient starts downloading, or
345
383
  * if they decline the share, then the returned share will update.
346
384
  *
347
- * Throws if no share with the specified ID is found.
385
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no sent share with
386
+ * the given `shareId` exists in the store.
348
387
  *
349
388
  * @param opts.shareId ID of the sent map share
350
389
  *
@@ -149,6 +149,9 @@ export function useManyReceivedMapShares() {
149
149
  *
150
150
  * @param opts.shareId ID of the map share
151
151
  *
152
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no received share
153
+ * with the given `shareId` exists.
154
+ *
152
155
  * @example
153
156
  * ```tsx
154
157
  * function MapShareDetail({ shareId }: { shareId: string }) {
@@ -161,22 +164,35 @@ export function useManyReceivedMapShares() {
161
164
  export function useSingleReceivedMapShare({ shareId }) {
162
165
  const mapShare = useReceivedMapSharesState(useCallback((shares) => shares.find((s) => s.shareId === shareId), [shareId]));
163
166
  if (!mapShare) {
164
- throw new Error(`Map share with id ${shareId} not found`);
167
+ throw new errors.MAP_SHARE_NOT_FOUND(`Received map share with id ${shareId} not found`);
165
168
  }
166
169
  return mapShare;
167
170
  }
168
171
  /**
169
172
  * Accept and download a map share that has been received. The mutate promise
170
173
  * resolves once the map _starts_ downloading, before it finishes downloading.
171
- * Use `useManyMapShares` or `useSingleMapShare` to track download progress.
172
- *
173
- * Throws if the share is not in `status="pending"` or if the download fails to
174
- * start (e.g. if the shareId if invalid).
174
+ * Use `useManyReceivedMapShares` or `useSingleReceivedMapShare` to track
175
+ * download progress and final status.
176
+ *
177
+ * If the sender canceled the share before the receiver calls this, the
178
+ * mutation will still resolve (the download starts), but the share status will
179
+ * end up as `'canceled'` rather than `'completed'`. This is the only way the
180
+ * receiver discovers that a share has been canceled — check `share.status`
181
+ * after the download settles.
182
+ *
183
+ * @throws An error with code `'MAP_SHARE_CANCELED'` if the share is already
184
+ * known to be canceled (i.e. `status` is `'canceled'` in the store, e.g.
185
+ * after a previous download attempt discovered the cancellation).
186
+ * @throws An error with code `'INVALID_STATUS_TRANSITION'` if the share is
187
+ * not in a valid state to start downloading (e.g. already downloading,
188
+ * completed, or declined).
189
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no share with the
190
+ * given `shareId` exists in the store.
175
191
  *
176
192
  * @example
177
193
  * ```tsx
178
194
  * function AcceptButton({ shareId }: { shareId: string }) {
179
- * const { mutate: accept } = useAcceptMapShare()
195
+ * const { mutate: accept } = useDownloadReceivedMapShare()
180
196
  *
181
197
  * return <button onClick={() => accept({ shareId })}>Accept</button>
182
198
  * }
@@ -193,17 +209,28 @@ export function useDownloadReceivedMapShare() {
193
209
  }
194
210
  /**
195
211
  * Decline a map share that has been received. Notifies the sender that the
196
- * share was declined.
197
- *
198
- * Throws if the share is not with `status="pending"`
199
- * Throws if shareId is invalid
200
- * Throws if decline request fails (e.g. network error)
212
+ * share was declined. The share status is only updated to `'declined'` after
213
+ * the server confirms the decline — there is no optimistic update.
214
+ *
215
+ * If the sender canceled the share before the decline reaches the server, the
216
+ * share status will transition to `'canceled'` (not `'error'`) and the
217
+ * mutation will throw a `MapShareCanceledError`.
218
+ *
219
+ * @throws An error with code `'MAP_SHARE_CANCELED'` if the share is already
220
+ * known to be canceled, or if the server reports that the sender canceled
221
+ * the share while the decline was in flight. In both cases `share.status`
222
+ * will be `'canceled'`.
223
+ * @throws An error with code `'INVALID_STATUS_TRANSITION'` if the share is
224
+ * not in `status='pending'` (e.g. already downloading, completed, or
225
+ * declined).
226
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no share with the
227
+ * given `shareId` exists in the store.
201
228
  *
202
229
  * @example
203
230
  * ```tsx
204
231
  * import { DeclineReason } from '@comapeo/core-react'
205
232
  * function DeclineButton({ shareId }: { shareId: string }) {
206
- * const { mutate: decline } = useDeclineMapShare()
233
+ * const { mutate: decline } = useDeclineReceivedMapShare()
207
234
  *
208
235
  * return (
209
236
  * <button onClick={() => decline({ shareId, reason: DeclineReason.user_rejected })}>
@@ -225,13 +252,20 @@ export function useDeclineReceivedMapShare() {
225
252
  /**
226
253
  * Abort an in-progress map share download.
227
254
  *
228
- * Throws if the share is not in `status="downloading"`
229
- * Throws if shareId is invalid
255
+ * @throws An error with code `'MAP_SHARE_CANCELED'` if the share is already
256
+ * known to be canceled.
257
+ * @throws An error with code `'INVALID_STATUS_TRANSITION'` if the share is
258
+ * not in `status='downloading'` (e.g. still pending, already completed, or
259
+ * declined).
260
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no share with the
261
+ * given `shareId` exists in the store.
262
+ * @throws An error with code `'DOWNLOAD_NOT_FOUND'` if no download is
263
+ * currently tracked for this share (e.g. the download was never started).
230
264
  *
231
265
  * @example
232
266
  * ```tsx
233
267
  * function AbortButton({ shareId }: { shareId: string }) {
234
- * const { mutate: abort } = useAbortMapShareDownload()
268
+ * const { mutate: abort } = useAbortReceivedMapShareDownload()
235
269
  *
236
270
  * return <button onClick={() => abort({ shareId })}>Cancel Download</button>
237
271
  * }
@@ -255,8 +289,6 @@ export function useAbortReceivedMapShareDownload() {
255
289
  * mutation resolves with the created map share object, including its ID, which
256
290
  * can be used to track the share status with `useSingleSentMapShare`.
257
291
  *
258
- * @param opts.projectId Public ID of project for sending the map share: you can only send map shares to users on the same project
259
- *
260
292
  * @example
261
293
  * ```tsx
262
294
  * function SendMapButton({ projectId, deviceId }: { projectId: string; deviceId: string }) {
@@ -265,7 +297,7 @@ export function useAbortReceivedMapShareDownload() {
265
297
  * return (
266
298
  * <button
267
299
  * onClick={() =>
268
- * send({ projectId, receiverDeviceId: deviceId, mapId: 'custom' }, {
300
+ * send({ receiverDeviceId: deviceId, mapId: 'custom' }, {
269
301
  * onSuccess: (mapShare) => {
270
302
  * console.log('Share sent with id', mapShare.shareId)
271
303
  * }
@@ -294,6 +326,12 @@ export function useSendMapShare() {
294
326
  * the share, the download will be canceled before completion. If the download
295
327
  * is already complete, this action will throw an error.
296
328
  *
329
+ * @throws An error with code `'INVALID_STATUS_TRANSITION'` if the share is
330
+ * not in `status='pending'` or `status='downloading'` (e.g. already
331
+ * completed, canceled, aborted, or declined).
332
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no share with the
333
+ * given `shareId` exists in the store.
334
+ *
297
335
  * @param opts.projectId Public ID of project to request the map share cancellation for.
298
336
  *
299
337
  * @example
@@ -319,7 +357,8 @@ export function useCancelSentMapShare() {
319
357
  * of the share, updated in real-time. When the recipient starts downloading, or
320
358
  * if they decline the share, then the returned share will update.
321
359
  *
322
- * Throws if no share with the specified ID is found.
360
+ * @throws An error with code `'MAP_SHARE_NOT_FOUND'` if no sent share with
361
+ * the given `shareId` exists in the store.
323
362
  *
324
363
  * @param opts.shareId ID of the sent map share
325
364
  *
@@ -4,7 +4,7 @@ export { useCreateDocument, useDeleteDocument, useManyDocs, usePresetsSelection,
4
4
  export { useAcceptInvite, useManyInvites, useRejectInvite, useRequestCancelInvite, useSendInvite, useSingleInvite, } from './hooks/invites.js';
5
5
  export { useMapStyleUrl, useImportCustomMapFile, useRemoveCustomMapFile, useGetCustomMapInfo, useManyReceivedMapShares, useSingleReceivedMapShare, useDeclineReceivedMapShare, useDownloadReceivedMapShare, useAbortReceivedMapShareDownload, useSendMapShare, useCancelSentMapShare, useSingleSentMapShare, } from './hooks/maps.js';
6
6
  export type { SentMapShareState, ReceivedMapShareState, AbortMapShareOptions, CancelMapShareOptions, DeclineMapShareOptions, DownloadMapShareOptions, CreateAndSendMapShareOptions, } from './lib/map-shares-stores.js';
7
- export { DeclineReason } from './lib/map-shares-stores.js';
7
+ export { DeclineReason, MapShareErrorCode, getErrorCode, MapShareCanceledError, InvalidStatusTransitionError, } from './lib/map-shares-stores.js';
8
8
  export { useAddServerPeer, useAttachmentUrl, useConnectSyncServers, useCreateBlob, useCreateProject, useDataSyncProgress, useDisconnectSyncServers, useDocumentCreatedBy, useIconUrl, useImportProjectCategories, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useOwnRoleInProject, useProjectOwnRoleChangeListener, useProjectSettings, useRemoveServerPeer, useRemoveMember, useSetAutostopDataSyncTimeout, useSingleMember, useSingleProject, useStartSync, useStopSync, useSyncState, useUpdateProjectSettings, useChangeMemberRole, useExportGeoJSON, useExportZipFile, } from './hooks/projects.js';
9
9
  export type { SyncState } from './lib/sync.js';
10
10
  export type { WriteableDocument, WriteableDocumentType, WriteableValue, } from './lib/types.js';
package/dist/esm/index.js CHANGED
@@ -3,6 +3,6 @@ export { useClientApi, useIsArchiveDevice, useOwnDeviceInfo, useSetIsArchiveDevi
3
3
  export { useCreateDocument, useDeleteDocument, useManyDocs, usePresetsSelection, useSingleDocByDocId, useSingleDocByVersionId, useUpdateDocument, } from './hooks/documents.js';
4
4
  export { useAcceptInvite, useManyInvites, useRejectInvite, useRequestCancelInvite, useSendInvite, useSingleInvite, } from './hooks/invites.js';
5
5
  export { useMapStyleUrl, useImportCustomMapFile, useRemoveCustomMapFile, useGetCustomMapInfo, useManyReceivedMapShares, useSingleReceivedMapShare, useDeclineReceivedMapShare, useDownloadReceivedMapShare, useAbortReceivedMapShareDownload, useSendMapShare, useCancelSentMapShare, useSingleSentMapShare, } from './hooks/maps.js';
6
- export { DeclineReason } from './lib/map-shares-stores.js';
6
+ export { DeclineReason, MapShareErrorCode, getErrorCode, MapShareCanceledError, InvalidStatusTransitionError, } from './lib/map-shares-stores.js';
7
7
  export { useAddServerPeer, useAttachmentUrl, useConnectSyncServers, useCreateBlob, useCreateProject, useDataSyncProgress, useDisconnectSyncServers, useDocumentCreatedBy, useIconUrl, useImportProjectCategories, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useOwnRoleInProject, useProjectOwnRoleChangeListener, useProjectSettings, useRemoveServerPeer, useRemoveMember, useSetAutostopDataSyncTimeout, useSingleMember, useSingleProject, useStartSync, useStopSync, useSyncState, useUpdateProjectSettings, useChangeMemberRole, useExportGeoJSON, useExportZipFile, } from './hooks/projects.js';
8
8
  export { HTTPError, isHTTPError } from './lib/http.js';