@remit/data-ports 0.0.56 → 0.0.58
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/data-ports",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.58",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"types": "./src/mutation-events.ts",
|
|
38
38
|
"default": "./src/mutation-events.ts"
|
|
39
39
|
},
|
|
40
|
+
"./virtual-copy": {
|
|
41
|
+
"types": "./src/virtual-copy.ts",
|
|
42
|
+
"default": "./src/virtual-copy.ts"
|
|
43
|
+
},
|
|
40
44
|
"./mailbox-name": {
|
|
41
45
|
"types": "./src/mailbox-name.ts",
|
|
42
46
|
"default": "./src/mailbox-name.ts"
|
|
@@ -95,6 +95,18 @@ export interface IAddressRepository {
|
|
|
95
95
|
cursor?: string;
|
|
96
96
|
limit?: number;
|
|
97
97
|
}): Promise<ResultList<AddressItem>>;
|
|
98
|
+
/**
|
|
99
|
+
* Every address stored under the configuration, for a reader that must see
|
|
100
|
+
* the whole set: nothing is hidden, so a row the machine marked `junkOnly`
|
|
101
|
+
* still comes back with whatever the user decided about it. Ordered by
|
|
102
|
+
* `addressId`, which no write moves, so a page boundary cannot skip a row
|
|
103
|
+
* while message sync updates counts and timestamps underneath the read.
|
|
104
|
+
*/
|
|
105
|
+
listAllByAccountConfigPage(input: {
|
|
106
|
+
accountConfigId: string;
|
|
107
|
+
cursor?: string;
|
|
108
|
+
limit?: number;
|
|
109
|
+
}): Promise<ResultList<AddressItem>>;
|
|
98
110
|
createEnvelopeAddress(
|
|
99
111
|
input: CreateEnvelopeAddressInput,
|
|
100
112
|
): Promise<EnvelopeAddressItem>;
|
|
@@ -92,5 +92,15 @@ export interface IOutboxMessageRepository {
|
|
|
92
92
|
accountId: string,
|
|
93
93
|
options?: { limit?: number; continuationToken?: string },
|
|
94
94
|
): Promise<ResultList<OutboxMessageItem>>;
|
|
95
|
+
/**
|
|
96
|
+
* One keyset scan over several accounts, ordered globally by createdAt then
|
|
97
|
+
* outboxMessageId. The continuation token names a position in that single
|
|
98
|
+
* ordering rather than one position per account, so a second page stays
|
|
99
|
+
* coherent however the accounts' rows interleave.
|
|
100
|
+
*/
|
|
101
|
+
listByAccounts(
|
|
102
|
+
accountIds: string[],
|
|
103
|
+
options?: { limit?: number; continuationToken?: string },
|
|
104
|
+
): Promise<ResultList<OutboxMessageItem>>;
|
|
95
105
|
listQueued(accountId: string): Promise<OutboxMessageItem[]>;
|
|
96
106
|
}
|
|
@@ -47,6 +47,13 @@ export interface IThreadMessageRepository {
|
|
|
47
47
|
continuationToken?: string;
|
|
48
48
|
inboxMailboxIds?: Set<string>;
|
|
49
49
|
excludeDeleted?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Row criteria applied inside the query, so a page is a page of
|
|
52
|
+
* matches however rare the criterion is. Filtering the rows a page
|
|
53
|
+
* returned instead is what makes a listing look empty whenever the
|
|
54
|
+
* matching mail sits below the newest page (#308).
|
|
55
|
+
*/
|
|
56
|
+
search?: SearchOptions;
|
|
50
57
|
},
|
|
51
58
|
): Promise<ResultList<ThreadMessageItem>>;
|
|
52
59
|
/**
|
|
@@ -100,8 +107,31 @@ export interface IThreadMessageRepository {
|
|
|
100
107
|
continuationToken?: string;
|
|
101
108
|
mailboxIds?: Set<string>;
|
|
102
109
|
excludeDeleted?: boolean;
|
|
110
|
+
/** Row criteria applied inside the query; see `listByDate`. */
|
|
111
|
+
search?: SearchOptions;
|
|
103
112
|
},
|
|
104
113
|
): Promise<ResultList<ThreadMessageItem>>;
|
|
114
|
+
/**
|
|
115
|
+
* COUNT of matching CONVERSATIONS over the same predicate and the same
|
|
116
|
+
* mailbox scope the three cross-account listing modes read with — the scoped
|
|
117
|
+
* counterpart of `countByMailbox`.
|
|
118
|
+
*
|
|
119
|
+
* A page size bounds the rows a response carries and has no bearing on how
|
|
120
|
+
* much matches, so a count returns no rows and is answered in full.
|
|
121
|
+
*
|
|
122
|
+
* Distinct on `threadId`, unlike `countByMailbox`, because the cross-account
|
|
123
|
+
* listing is collapsed by thread before it is rendered: a row is per mailbox,
|
|
124
|
+
* so one message reachable through a real folder and a virtual copy of it is
|
|
125
|
+
* several rows, and two matching messages of one conversation are two more.
|
|
126
|
+
*/
|
|
127
|
+
countThreadsInScope(
|
|
128
|
+
accountConfigId: string,
|
|
129
|
+
search: SearchOptions,
|
|
130
|
+
options?: {
|
|
131
|
+
mailboxIds?: Set<string>;
|
|
132
|
+
excludeDeleted?: boolean;
|
|
133
|
+
},
|
|
134
|
+
): Promise<number>;
|
|
105
135
|
/**
|
|
106
136
|
* Every message of a thread, across all mailboxes of the account. A
|
|
107
137
|
* conversation spans INBOX, Sent and any folder its messages were filed
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import type { MailboxItem } from "./types.js";
|
|
4
|
+
import { isVirtualCopyMailbox } from "./virtual-copy.js";
|
|
5
|
+
|
|
6
|
+
const mailbox = (fullPath: string, specialUse?: string[]): MailboxItem =>
|
|
7
|
+
({
|
|
8
|
+
mailboxId: `mbx-${fullPath}`,
|
|
9
|
+
fullPath,
|
|
10
|
+
...(specialUse ? { specialUse } : {}),
|
|
11
|
+
}) as MailboxItem;
|
|
12
|
+
|
|
13
|
+
describe("which folders hold a second copy of mail that lives elsewhere", () => {
|
|
14
|
+
it("reads the special-use the server advertises", () => {
|
|
15
|
+
assert.equal(isVirtualCopyMailbox(mailbox("Everything", ["All"])), true);
|
|
16
|
+
assert.equal(isVirtualCopyMailbox(mailbox("Pinned", ["Flagged"])), true);
|
|
17
|
+
assert.equal(isVirtualCopyMailbox(mailbox("Urgent", ["Important"])), true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("falls back to the well-known path when the server advertises nothing", () => {
|
|
21
|
+
assert.equal(isVirtualCopyMailbox(mailbox("[Gmail]/All Mail")), true);
|
|
22
|
+
assert.equal(isVirtualCopyMailbox(mailbox("[gmail]/starred")), true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("matches the whole path, so a folder of the user's own is real mail", () => {
|
|
26
|
+
assert.equal(isVirtualCopyMailbox(mailbox("Starred ideas")), false);
|
|
27
|
+
assert.equal(isVirtualCopyMailbox(mailbox("[Gmail]/All Mail/2024")), false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("reads an ordinary folder as the place mail lives", () => {
|
|
31
|
+
assert.equal(isVirtualCopyMailbox(mailbox("INBOX")), false);
|
|
32
|
+
assert.equal(isVirtualCopyMailbox(mailbox("Rubbish", ["Junk"])), false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { MailboxSpecialUse } from "@remit/domain-enums";
|
|
2
|
+
import type { MailboxItem } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Special-use folders that hold a second copy of mail already reachable through
|
|
6
|
+
* the folder it actually lives in — Gmail's All Mail, Starred and Important.
|
|
7
|
+
*/
|
|
8
|
+
const VIRTUAL_COPY_SPECIAL_USE: readonly string[] = [
|
|
9
|
+
MailboxSpecialUse.All,
|
|
10
|
+
MailboxSpecialUse.Flagged,
|
|
11
|
+
MailboxSpecialUse.Important,
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Well-known Gmail virtual paths, for servers that do not advertise the
|
|
16
|
+
* special-use attribute. Whole path, never a prefix — a user's own
|
|
17
|
+
* `Starred ideas` folder is real mail.
|
|
18
|
+
*/
|
|
19
|
+
const VIRTUAL_COPY_FULL_PATHS: readonly string[] = [
|
|
20
|
+
"[gmail]/all mail",
|
|
21
|
+
"[gmail]/starred",
|
|
22
|
+
"[gmail]/important",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether this folder holds mail that also lives in a real folder. Such a
|
|
27
|
+
* folder is a view, not a filing location: seeing a message in it says nothing
|
|
28
|
+
* about where the message was filed, so it neither picks which duplicate a
|
|
29
|
+
* listing drops nor decides where a message lives.
|
|
30
|
+
*/
|
|
31
|
+
export const isVirtualCopyMailbox = (mailbox: MailboxItem): boolean =>
|
|
32
|
+
mailbox.specialUse?.some((use) => VIRTUAL_COPY_SPECIAL_USE.includes(use)) ===
|
|
33
|
+
true || VIRTUAL_COPY_FULL_PATHS.includes(mailbox.fullPath.toLowerCase());
|