@remit/drizzle-service 0.0.51 → 0.0.53

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/drizzle-service",
3
- "version": "0.0.51",
3
+ "version": "0.0.53",
4
4
  "description": "Drizzle ORM service over SQLite",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,8 +1,6 @@
1
1
  import type {
2
2
  AccountConfigDescription,
3
3
  AccountConfigItem,
4
- AccountItem,
5
- AddressItem,
6
4
  CreateAccountConfigInput,
7
5
  IAccountConfigRepository,
8
6
  ResultList,
@@ -18,6 +16,8 @@ import {
18
16
  accountTable,
19
17
  } from "../schema/i4-account-config.js";
20
18
  import { addressTable } from "../schema/i4-address.js";
19
+ import { rowToAccount } from "./i4-account.js";
20
+ import { rowToAddress } from "./i4-address.js";
21
21
 
22
22
  type DB = Db<Record<string, unknown>>;
23
23
 
@@ -36,62 +36,6 @@ function rowToAccountConfig(
36
36
  };
37
37
  }
38
38
 
39
- function rowToAccount(row: typeof accountTable.$inferSelect): AccountItem {
40
- return {
41
- accountId: row.accountId,
42
- accountConfigId: row.accountConfigId,
43
- username: row.username,
44
- email: row.email,
45
- authType: row.authType as AccountItem["authType"],
46
- passwordHash: row.passwordHash ?? undefined,
47
- oauthRefreshTokenHash: row.oauthRefreshTokenHash ?? undefined,
48
- oauthTokenUpdatedAt: row.oauthTokenUpdatedAt ?? undefined,
49
- imapHost: row.imapHost,
50
- imapPort: row.imapPort,
51
- imapTls: row.imapTls,
52
- imapStartTls: row.imapStartTls,
53
- smtpEnabled: row.smtpEnabled,
54
- smtpHost: row.smtpHost,
55
- smtpPort: row.smtpPort,
56
- smtpTls: row.smtpTls,
57
- smtpStartTls: row.smtpStartTls,
58
- smtpUsername: row.smtpUsername,
59
- smtpPasswordHash: row.smtpPasswordHash ?? undefined,
60
- isActive: row.isActive,
61
- connectionState: row.connectionState as AccountItem["connectionState"],
62
- lastConnectedAt: row.lastConnectedAt ?? undefined,
63
- lastSyncAt: row.lastSyncAt ?? undefined,
64
- lastError: row.lastError ?? undefined,
65
- syncPhase: (row.syncPhase as AccountItem["syncPhase"]) ?? undefined,
66
- mailboxCountTotal: row.mailboxCountTotal ?? undefined,
67
- mailboxCountSynced: row.mailboxCountSynced ?? undefined,
68
- createdAt: row.createdAt,
69
- updatedAt: row.updatedAt,
70
- deletedAt: row.deletedAt ?? undefined,
71
- };
72
- }
73
-
74
- function rowToAddress(row: typeof addressTable.$inferSelect): AddressItem {
75
- return {
76
- addressId: row.addressId,
77
- accountConfigId: row.accountConfigId,
78
- displayName: row.displayName ?? undefined,
79
- localPart: row.localPart,
80
- domain: row.domain,
81
- normalizedEmail: row.normalizedEmail,
82
- normalizedCompound: row.normalizedCompound,
83
- flags: (row.flags ?? {}) as AddressItem["flags"],
84
- inboundCount: row.inboundCount,
85
- outboundCount: row.outboundCount,
86
- replyCount: row.replyCount,
87
- lastInboundAt: row.lastInboundAt,
88
- lastOutboundAt: row.lastOutboundAt ?? undefined,
89
- lastReplyAt: row.lastReplyAt,
90
- createdAt: row.createdAt,
91
- updatedAt: row.updatedAt,
92
- };
93
- }
94
-
95
39
  export class AccountConfigRepo implements IAccountConfigRepository {
96
40
  constructor(private db: DB) {}
97
41
 
@@ -10,6 +10,7 @@ import type {
10
10
  UpdateAddressInput,
11
11
  } from "@remit/data-ports";
12
12
  import { BadRequestError } from "@remit/data-ports/errors";
13
+ import { shouldPromoteWellknown } from "@remit/data-ports/wellknown";
13
14
  import {
14
15
  and,
15
16
  asc,
@@ -39,7 +40,6 @@ import {
39
40
  addressRecency,
40
41
  addressSearchMatch,
41
42
  } from "./address-search-predicates.js";
42
- import { shouldPromoteWellknown } from "./i4-address-wellknown.js";
43
43
 
44
44
  type DB = Db<Record<string, unknown>>;
45
45
 
@@ -14,11 +14,15 @@ import { AccountSettingRepo } from "./i4-account-setting.js";
14
14
  import { MailboxRepo } from "./i4-mailbox.js";
15
15
  import { MailboxSpecialUseRepo } from "./i4-mailbox-special-use.js";
16
16
 
17
- const makeMailboxInput = (accountId: string, fullPath: string) => ({
17
+ const makeMailboxInput = (
18
+ accountId: string,
19
+ fullPath: string,
20
+ hierarchyDelimiter = "/",
21
+ ) => ({
18
22
  accountId,
19
23
  namespaceType: "personal" as const,
20
24
  namespacePrefix: "",
21
- hierarchyDelimiter: "/",
25
+ hierarchyDelimiter,
22
26
  fullPath,
23
27
  uidValidity: 1,
24
28
  uidNext: 1,
@@ -210,6 +214,59 @@ describe("MailboxSpecialUseRepo role lookups (sqlite)", () => {
210
214
  assert.equal(found?.mailboxId, spam.mailboxId);
211
215
  });
212
216
 
217
+ test("resolves an INBOX-nested Trash folder that advertises no special use", async () => {
218
+ // #837: `findTrashMailbox` carried its own copy of the discarded
219
+ // whole-path rule, so `INBOX/Trash` resolved to nothing and a delete
220
+ // refused on an account that plainly has a Trash folder.
221
+ const { accountId } = await makeAccount();
222
+ await mailboxes.create(makeMailboxInput(accountId, "INBOX"));
223
+ const trash = await mailboxes.create(
224
+ makeMailboxInput(accountId, "INBOX/Trash"),
225
+ );
226
+
227
+ assert.equal(
228
+ (await repo.findTrashMailbox(accountId))?.mailboxId,
229
+ trash.mailboxId,
230
+ );
231
+ // Resolving the name widens where a delete FILES mail, never what an
232
+ // Empty Trash may expunge (#846): that still needs the flag or an
233
+ // appointment.
234
+ assert.equal(await repo.findConfirmedTrashMailbox(accountId), null);
235
+ });
236
+
237
+ test("resolves an INBOX-nested Archive folder that advertises no special use", async () => {
238
+ const { accountId } = await makeAccount();
239
+ await mailboxes.create(makeMailboxInput(accountId, "INBOX"));
240
+ const archive = await mailboxes.create(
241
+ makeMailboxInput(accountId, "INBOX/Archive"),
242
+ );
243
+
244
+ assert.equal(
245
+ (await repo.findArchiveMailbox(accountId))?.mailboxId,
246
+ archive.mailboxId,
247
+ );
248
+ });
249
+
250
+ test("splits the leaf on the account's own delimiter, not on a slash", async () => {
251
+ const { accountId } = await makeAccount();
252
+ await mailboxes.create(makeMailboxInput(accountId, "INBOX", "."));
253
+ const trash = await mailboxes.create(
254
+ makeMailboxInput(accountId, "INBOX.Trash", "."),
255
+ );
256
+ const archive = await mailboxes.create(
257
+ makeMailboxInput(accountId, "INBOX.Archive", "."),
258
+ );
259
+
260
+ assert.equal(
261
+ (await repo.findTrashMailbox(accountId))?.mailboxId,
262
+ trash.mailboxId,
263
+ );
264
+ assert.equal(
265
+ (await repo.findArchiveMailbox(accountId))?.mailboxId,
266
+ archive.mailboxId,
267
+ );
268
+ });
269
+
213
270
  test("answers null when the account has no Junk folder at all", async () => {
214
271
  const { accountId } = await makeAccount();
215
272
  await mailboxes.create(makeMailboxInput(accountId, "INBOX"));
@@ -1,31 +0,0 @@
1
- export const WELLKNOWN_INBOUND_THRESHOLD = 3;
2
- export const WELLKNOWN_REPLY_THRESHOLD = 1;
3
- export const WELLKNOWN_INBOUND_WINDOW_MS = 90 * 24 * 60 * 60 * 1000;
4
-
5
- export interface WellknownEngagementSnapshot {
6
- inboundCount?: number;
7
- replyCount?: number;
8
- lastInboundAt?: number;
9
- isBulk?: boolean;
10
- flags?: { wellknown?: { value: boolean } | undefined };
11
- }
12
-
13
- export const shouldPromoteWellknown = (
14
- snapshot: WellknownEngagementSnapshot,
15
- now: number,
16
- ): boolean => {
17
- if (snapshot.flags?.wellknown?.value === true) return false;
18
-
19
- const replyCount = snapshot.replyCount ?? 0;
20
- const inboundCount = snapshot.inboundCount ?? 0;
21
- const replied = replyCount >= WELLKNOWN_REPLY_THRESHOLD;
22
- const inboundEnough =
23
- !snapshot.isBulk && inboundCount >= WELLKNOWN_INBOUND_THRESHOLD;
24
- if (!replied && !inboundEnough) return false;
25
-
26
- const lastInboundAt = snapshot.lastInboundAt;
27
- if (lastInboundAt === undefined) return false;
28
- if (now - lastInboundAt > WELLKNOWN_INBOUND_WINDOW_MS) return false;
29
-
30
- return true;
31
- };