@remit/data-ports 0.0.37 → 0.0.38

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/errors.ts +58 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/data-ports",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
package/src/errors.ts CHANGED
@@ -1,5 +1,29 @@
1
+ import type { CanonicalMailboxRoleValue } from "./folder-role.js";
2
+
3
+ /**
4
+ * The half of an error a client may read: a stable `code` it branches on and
5
+ * string `details` it words its own prompt from. Opt-in — an error that
6
+ * declares none keeps today's `{ message }` body, so no response gains a
7
+ * shape the API never promised and no internal failure leaks its innards.
8
+ */
9
+ export interface PublicApiError {
10
+ code: string;
11
+ details?: Record<string, string>;
12
+ }
13
+
14
+ export const isPublicApiError = (value: unknown): value is PublicApiError => {
15
+ if (typeof value !== "object" || value === null) return false;
16
+ if (!("code" in value) || typeof value.code !== "string") return false;
17
+ if (!("details" in value) || value.details === undefined) return true;
18
+ if (typeof value.details !== "object" || value.details === null) return false;
19
+ return Object.values(value.details).every(
20
+ (detail) => typeof detail === "string",
21
+ );
22
+ };
23
+
1
24
  export class HTTPError extends Error {
2
25
  public statusCode = 500;
26
+ publicApiError?: PublicApiError;
3
27
  }
4
28
 
5
29
  export class BadRequestError extends HTTPError {
@@ -27,6 +51,40 @@ export class ConflictError extends HTTPError {
27
51
  public statusCode = 409;
28
52
  }
29
53
 
54
+ /**
55
+ * Why a canonical role names no folder this action may act on. `none`: the
56
+ * account has no candidate at all. `stale`: the folder the user appointed is
57
+ * gone from the server. `unconfirmed`: a folder matches by name, but nobody —
58
+ * neither the user nor the server's own flag — ever said it holds the role.
59
+ * These three and no others; a target that is merely unsettled is a different
60
+ * refusal under its own code.
61
+ */
62
+ export type FolderRoleUnresolvedReason = "none" | "stale" | "unconfirmed";
63
+
64
+ /**
65
+ * A destructive action refused because the role it needs is unresolved. The
66
+ * client reads `code` and words its prompt from `details` — never from the
67
+ * message — so the copy can change without breaking the branch, and the
68
+ * account the prompt has to appoint a folder on travels with the refusal
69
+ * (the delete endpoint's body carries no accountId).
70
+ */
71
+ export class FolderRoleUnresolvedError extends ConflictError {
72
+ name = "FolderRoleUnresolvedError";
73
+
74
+ constructor(
75
+ message: string,
76
+ role: CanonicalMailboxRoleValue,
77
+ reason: FolderRoleUnresolvedReason,
78
+ accountId: string,
79
+ ) {
80
+ super(message);
81
+ this.publicApiError = {
82
+ code: "folder_role_unresolved",
83
+ details: { role, reason, accountId },
84
+ };
85
+ }
86
+ }
87
+
30
88
  /**
31
89
  * A message exists but its body could not be fetched/parsed after every
32
90
  * body-sync retry was spent (issue #1270 / epic #1281 invariant 3). This is