@remit/data-ports 0.0.36 → 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.
- package/package.json +1 -1
- package/src/errors.ts +58 -0
- package/src/id.ts +0 -17
- package/src/mailbox-role.ts +1 -32
package/package.json
CHANGED
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
|
package/src/id.ts
CHANGED
|
@@ -7,13 +7,6 @@ const translator = shortUuid.createTranslator(shortUuid.constants.uuid25Base36);
|
|
|
7
7
|
|
|
8
8
|
export const base36uuid = (): string => translator.generate();
|
|
9
9
|
|
|
10
|
-
export const fromUUID = (uuid: string): string => translator.fromUUID(uuid);
|
|
11
|
-
|
|
12
|
-
export const toUUID = (shortId: string): string => translator.toUUID(shortId);
|
|
13
|
-
|
|
14
|
-
export const generateUuidv5 = (name: string, namespace: string): string =>
|
|
15
|
-
uuidv5(name, namespace);
|
|
16
|
-
|
|
17
10
|
export const base36uuidv5 = (name: string, namespace: string): string =>
|
|
18
11
|
translator.fromUUID(uuidv5(name, namespace));
|
|
19
12
|
|
|
@@ -118,16 +111,6 @@ export const ROOT_PART_PATH = "0";
|
|
|
118
111
|
export const deriveBodyPartId = (messageId: string, partPath: string): string =>
|
|
119
112
|
base36uuidv5(`bodypart:${messageId}:${partPath}`, REMIT_NAMESPACE);
|
|
120
113
|
|
|
121
|
-
export const deriveBodyPartParameterId = (
|
|
122
|
-
messageId: string,
|
|
123
|
-
partPath: string,
|
|
124
|
-
parameterName: string,
|
|
125
|
-
): string =>
|
|
126
|
-
base36uuidv5(
|
|
127
|
-
`bodypartparam:${messageId}:${partPath}:${parameterName.toLowerCase()}`,
|
|
128
|
-
REMIT_NAMESPACE,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
114
|
/**
|
|
132
115
|
* Identity of a quarantined message (issue #72). Derived rather than generated
|
|
133
116
|
* so re-quarantining the same message rewrites its own row: the sync path can
|
package/src/mailbox-role.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CanonicalMailboxRole } from "@remit/domain-enums";
|
|
2
|
-
import { ROLE_NAME_HINTS
|
|
2
|
+
import { ROLE_NAME_HINTS } from "./folder-role.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The conventional names for a role, taken from the one table every
|
|
@@ -12,34 +12,3 @@ export const JUNK_FOLDER_NAMES: readonly string[] =
|
|
|
12
12
|
|
|
13
13
|
export const TRASH_FOLDER_NAMES: readonly string[] =
|
|
14
14
|
ROLE_NAME_HINTS[CanonicalMailboxRole.Trash] ?? [];
|
|
15
|
-
|
|
16
|
-
export interface MailboxRole {
|
|
17
|
-
readonly fullPath: string;
|
|
18
|
-
readonly hierarchyDelimiter?: string;
|
|
19
|
-
readonly specialUse?: readonly string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Whether one mailbox, considered alone, carries a role: its SPECIAL-USE flag
|
|
24
|
-
* or its conventional name. A single mailbox carries no account context, so
|
|
25
|
-
* this cannot see the user's appointment — a caller holding an accountId asks
|
|
26
|
-
* `IMailboxSpecialUseRepository` instead, which does.
|
|
27
|
-
*/
|
|
28
|
-
const carriesRole = (
|
|
29
|
-
mailbox: MailboxRole,
|
|
30
|
-
role: (typeof CanonicalMailboxRole)[keyof typeof CanonicalMailboxRole],
|
|
31
|
-
): boolean =>
|
|
32
|
-
resolveMailboxForRole(role, [
|
|
33
|
-
{
|
|
34
|
-
mailboxId: "",
|
|
35
|
-
fullPath: mailbox.fullPath,
|
|
36
|
-
hierarchyDelimiter: mailbox.hierarchyDelimiter ?? "",
|
|
37
|
-
specialUse: mailbox.specialUse,
|
|
38
|
-
},
|
|
39
|
-
]) !== null;
|
|
40
|
-
|
|
41
|
-
export const isJunkMailbox = (mailbox: MailboxRole): boolean =>
|
|
42
|
-
carriesRole(mailbox, CanonicalMailboxRole.Junk);
|
|
43
|
-
|
|
44
|
-
export const isTrashMailbox = (mailbox: MailboxRole): boolean =>
|
|
45
|
-
carriesRole(mailbox, CanonicalMailboxRole.Trash);
|