@remit/data-ports 0.0.82 → 0.0.84
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/dist/account-settings.d.ts +371 -0
- package/dist/account-settings.js +114 -0
- package/dist/conformance/account.d.ts +3 -0
- package/dist/conformance/account.js +118 -0
- package/dist/conformance/calendar-collection.d.ts +3 -0
- package/dist/conformance/calendar-collection.js +251 -0
- package/dist/conformance/calendar-event-index.d.ts +3 -0
- package/dist/conformance/calendar-event-index.js +114 -0
- package/dist/conformance/calendar-feed-token.d.ts +3 -0
- package/dist/conformance/calendar-feed-token.js +97 -0
- package/dist/conformance/calendar-object.d.ts +3 -0
- package/dist/conformance/calendar-object.js +156 -0
- package/dist/conformance/calendar-suggestion.d.ts +3 -0
- package/dist/conformance/calendar-suggestion.js +255 -0
- package/{src/conformance/harness.ts → dist/conformance/harness.d.ts} +4 -4
- package/dist/conformance/harness.js +1 -0
- package/dist/conformance/index.js +8 -0
- package/dist/conformance/label.d.ts +3 -0
- package/dist/conformance/label.js +92 -0
- package/dist/conformance/sender-signer-standing.d.ts +3 -0
- package/dist/conformance/sender-signer-standing.js +81 -0
- package/dist/display-name.d.ts +1 -0
- package/dist/display-name.js +26 -0
- package/dist/errors.d.ts +150 -0
- package/dist/errors.js +181 -0
- package/dist/filter-ttl.d.ts +7 -0
- package/{src/filter-ttl.ts → dist/filter-ttl.js} +8 -11
- package/dist/folder-role.d.ts +239 -0
- package/dist/folder-role.js +276 -0
- package/dist/id.d.ts +124 -0
- package/{src/id.ts → dist/id.js} +41 -127
- package/dist/index.d.ts +37 -0
- package/dist/index.js +5 -0
- package/dist/interfaces/account-config.d.ts +15 -0
- package/dist/interfaces/account-config.js +1 -0
- package/dist/interfaces/account-export-request.d.ts +10 -0
- package/dist/interfaces/account-export-request.js +1 -0
- package/dist/interfaces/account-setting.d.ts +7 -0
- package/dist/interfaces/account-setting.js +1 -0
- package/dist/interfaces/account.d.ts +29 -0
- package/dist/interfaces/account.js +1 -0
- package/dist/interfaces/address.d.ts +69 -0
- package/dist/interfaces/address.js +1 -0
- package/dist/interfaces/calendar-collection.d.ts +38 -0
- package/dist/interfaces/calendar-collection.js +1 -0
- package/dist/interfaces/calendar-event-index.d.ts +19 -0
- package/dist/interfaces/calendar-event-index.js +1 -0
- package/dist/interfaces/calendar-feed-token.d.ts +31 -0
- package/dist/interfaces/calendar-feed-token.js +1 -0
- package/dist/interfaces/calendar-object.d.ts +36 -0
- package/dist/interfaces/calendar-object.js +1 -0
- package/dist/interfaces/calendar-store.d.ts +93 -0
- package/dist/interfaces/calendar-store.js +1 -0
- package/dist/interfaces/calendar-suggestion.d.ts +62 -0
- package/dist/interfaces/calendar-suggestion.js +1 -0
- package/dist/interfaces/config-import.d.ts +8 -0
- package/dist/interfaces/config-import.js +1 -0
- package/dist/interfaces/envelope.d.ts +24 -0
- package/dist/interfaces/envelope.js +1 -0
- package/dist/interfaces/filter.d.ts +28 -0
- package/dist/interfaces/filter.js +1 -0
- package/dist/interfaces/label.d.ts +17 -0
- package/dist/interfaces/label.js +1 -0
- package/dist/interfaces/mailbox-lock.d.ts +12 -0
- package/dist/interfaces/mailbox-lock.js +1 -0
- package/dist/interfaces/mailbox-special-use.d.ts +54 -0
- package/dist/interfaces/mailbox-special-use.js +1 -0
- package/dist/interfaces/mailbox.d.ts +79 -0
- package/dist/interfaces/mailbox.js +1 -0
- package/dist/interfaces/message-flag-push.d.ts +9 -0
- package/dist/interfaces/message-flag-push.js +1 -0
- package/dist/interfaces/message-flag.d.ts +14 -0
- package/dist/interfaces/message-flag.js +1 -0
- package/dist/interfaces/message-label.d.ts +21 -0
- package/dist/interfaces/message-label.js +1 -0
- package/dist/interfaces/message-placement-move.d.ts +8 -0
- package/dist/interfaces/message-placement-move.js +1 -0
- package/dist/interfaces/message.d.ts +60 -0
- package/dist/interfaces/message.js +1 -0
- package/dist/interfaces/organize-job-request.d.ts +10 -0
- package/dist/interfaces/organize-job-request.js +1 -0
- package/dist/interfaces/outbox-attachment.d.ts +101 -0
- package/dist/interfaces/outbox-attachment.js +7 -0
- package/dist/interfaces/outbox-message.d.ts +70 -0
- package/dist/interfaces/outbox-message.js +20 -0
- package/dist/interfaces/quarantine.d.ts +20 -0
- package/dist/interfaces/quarantine.js +1 -0
- package/dist/interfaces/sender-signer-standing.d.ts +26 -0
- package/dist/interfaces/sender-signer-standing.js +1 -0
- package/dist/interfaces/sync-warning.d.ts +11 -0
- package/dist/interfaces/sync-warning.js +1 -0
- package/dist/interfaces/thread-message.d.ts +202 -0
- package/dist/interfaces/thread-message.js +1 -0
- package/dist/mailbox-name.d.ts +35 -0
- package/dist/mailbox-name.js +68 -0
- package/dist/message-parts.d.ts +22 -0
- package/dist/message-parts.js +18 -0
- package/dist/message-settlement.d.ts +35 -0
- package/{src/message-settlement.ts → dist/message-settlement.js} +5 -19
- package/dist/mutation-events.d.ts +19 -0
- package/{src/mutation-events.ts → dist/mutation-events.js} +2 -5
- package/dist/sender-mute.d.ts +26 -0
- package/{src/sender-mute.ts → dist/sender-mute.js} +12 -34
- package/dist/sync-warning-state.d.ts +3 -0
- package/dist/sync-warning-state.js +5 -0
- package/dist/types.d.ts +491 -0
- package/dist/types.js +1 -0
- package/dist/update-manifest.d.ts +24 -0
- package/dist/update-manifest.js +20 -0
- package/dist/virtual-copy.d.ts +8 -0
- package/{src/virtual-copy.ts → dist/virtual-copy.js} +10 -15
- package/dist/wellknown.d.ts +47 -0
- package/{src/wellknown.ts → dist/wellknown.js} +26 -49
- package/package.json +44 -79
- package/src/account-settings.ts +0 -194
- package/src/conformance/account.ts +0 -104
- package/src/conformance/calendar-collection.ts +0 -342
- package/src/conformance/calendar-event-index.ts +0 -203
- package/src/conformance/calendar-feed-token.ts +0 -148
- package/src/conformance/calendar-object.ts +0 -269
- package/src/conformance/calendar-suggestion.ts +0 -490
- package/src/conformance/label.ts +0 -136
- package/src/conformance/sender-signer-standing.ts +0 -117
- package/src/display-name.test.ts +0 -101
- package/src/display-name.ts +0 -40
- package/src/errors.ts +0 -226
- package/src/folder-role.test.ts +0 -422
- package/src/folder-role.ts +0 -447
- package/src/id.test.ts +0 -352
- package/src/index.ts +0 -170
- package/src/interfaces/account-config.ts +0 -25
- package/src/interfaces/account-export-request.ts +0 -21
- package/src/interfaces/account-setting.ts +0 -14
- package/src/interfaces/account.ts +0 -43
- package/src/interfaces/address.ts +0 -122
- package/src/interfaces/calendar-collection.ts +0 -60
- package/src/interfaces/calendar-event-index.ts +0 -34
- package/src/interfaces/calendar-feed-token.ts +0 -38
- package/src/interfaces/calendar-object.ts +0 -58
- package/src/interfaces/calendar-suggestion.ts +0 -93
- package/src/interfaces/calendar-unit-of-work.ts +0 -47
- package/src/interfaces/config-import.ts +0 -16
- package/src/interfaces/envelope.ts +0 -32
- package/src/interfaces/filter-anchor-transaction.ts +0 -23
- package/src/interfaces/filter-anchor.ts +0 -11
- package/src/interfaces/filter.ts +0 -41
- package/src/interfaces/label.ts +0 -30
- package/src/interfaces/mailbox-lock.ts +0 -32
- package/src/interfaces/mailbox-special-use.ts +0 -60
- package/src/interfaces/mailbox.ts +0 -115
- package/src/interfaces/message-flag-push.ts +0 -17
- package/src/interfaces/message-flag.ts +0 -15
- package/src/interfaces/message-label.ts +0 -25
- package/src/interfaces/message-placement-move.ts +0 -15
- package/src/interfaces/message.ts +0 -56
- package/src/interfaces/organize-job-request.ts +0 -19
- package/src/interfaces/outbox-attachment.ts +0 -132
- package/src/interfaces/outbox-message.ts +0 -106
- package/src/interfaces/quarantine.ts +0 -22
- package/src/interfaces/sender-signer-standing.ts +0 -37
- package/src/interfaces/thread-message.ts +0 -264
- package/src/interfaces/unit-of-work.ts +0 -28
- package/src/mailbox-name.test.ts +0 -106
- package/src/mailbox-name.ts +0 -90
- package/src/mailbox-state-doors.test.ts +0 -104
- package/src/message-placement-doors.test.ts +0 -100
- package/src/message-settlement.test.ts +0 -132
- package/src/sender-mute.test.ts +0 -76
- package/src/types.ts +0 -820
- package/src/update-manifest.test.ts +0 -130
- package/src/update-manifest.ts +0 -22
- package/src/virtual-copy.test.ts +0 -34
- package/tsconfig.json +0 -8
- /package/{src/conformance/index.ts → dist/conformance/index.d.ts} +0 -0
|
@@ -20,66 +20,43 @@
|
|
|
20
20
|
* existing `lastInboundAt` (the user is replying to an inbound, which
|
|
21
21
|
* implies a recent inbound, but we still gate on it explicitly).
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
23
|
export const WELLKNOWN_INBOUND_THRESHOLD = 3;
|
|
25
24
|
export const WELLKNOWN_REPLY_THRESHOLD = 1;
|
|
26
25
|
export const WELLKNOWN_INBOUND_WINDOW_MS = 90 * 24 * 60 * 60 * 1000;
|
|
27
|
-
|
|
28
26
|
/**
|
|
29
27
|
* Categories and signals that make a sender "bulk". Bulk senders cannot be
|
|
30
28
|
* promoted by inbound volume alone — only a real reply promotes them.
|
|
31
29
|
*/
|
|
32
|
-
const BULK_CATEGORIES
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
const BULK_CATEGORIES = new Set([
|
|
31
|
+
"newsletter",
|
|
32
|
+
"marketing",
|
|
33
|
+
"automated",
|
|
36
34
|
]);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export const shouldPromoteWellknown = (
|
|
54
|
-
snapshot: WellknownEngagementSnapshot,
|
|
55
|
-
now: number,
|
|
56
|
-
): boolean => {
|
|
57
|
-
if (snapshot.flags?.wellknown?.value === true) return false;
|
|
58
|
-
|
|
59
|
-
const replyCount = snapshot.replyCount ?? 0;
|
|
60
|
-
const inboundCount = snapshot.inboundCount ?? 0;
|
|
61
|
-
const replied = replyCount >= WELLKNOWN_REPLY_THRESHOLD;
|
|
62
|
-
// Bulk senders are only promotable via a real reply, not inbound volume.
|
|
63
|
-
const inboundEnough =
|
|
64
|
-
!snapshot.isBulk && inboundCount >= WELLKNOWN_INBOUND_THRESHOLD;
|
|
65
|
-
if (!replied && !inboundEnough) return false;
|
|
66
|
-
|
|
67
|
-
const lastInboundAt = snapshot.lastInboundAt;
|
|
68
|
-
if (lastInboundAt === undefined) return false;
|
|
69
|
-
if (now - lastInboundAt > WELLKNOWN_INBOUND_WINDOW_MS) return false;
|
|
70
|
-
|
|
71
|
-
return true;
|
|
35
|
+
export const shouldPromoteWellknown = (snapshot, now) => {
|
|
36
|
+
if (snapshot.flags?.wellknown?.value === true)
|
|
37
|
+
return false;
|
|
38
|
+
const replyCount = snapshot.replyCount ?? 0;
|
|
39
|
+
const inboundCount = snapshot.inboundCount ?? 0;
|
|
40
|
+
const replied = replyCount >= WELLKNOWN_REPLY_THRESHOLD;
|
|
41
|
+
// Bulk senders are only promotable via a real reply, not inbound volume.
|
|
42
|
+
const inboundEnough = !snapshot.isBulk && inboundCount >= WELLKNOWN_INBOUND_THRESHOLD;
|
|
43
|
+
if (!replied && !inboundEnough)
|
|
44
|
+
return false;
|
|
45
|
+
const lastInboundAt = snapshot.lastInboundAt;
|
|
46
|
+
if (lastInboundAt === undefined)
|
|
47
|
+
return false;
|
|
48
|
+
if (now - lastInboundAt > WELLKNOWN_INBOUND_WINDOW_MS)
|
|
49
|
+
return false;
|
|
50
|
+
return true;
|
|
72
51
|
};
|
|
73
|
-
|
|
74
52
|
/**
|
|
75
53
|
* Returns true when the category or presence of List-Unsubscribe marks the
|
|
76
54
|
* sender as bulk, meaning they cannot be promoted by inbound volume alone.
|
|
77
55
|
*/
|
|
78
|
-
export function isBulkSender(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return false;
|
|
56
|
+
export function isBulkSender(category, hasListUnsubscribe) {
|
|
57
|
+
if (hasListUnsubscribe)
|
|
58
|
+
return true;
|
|
59
|
+
if (category != null && BULK_CATEGORIES.has(category))
|
|
60
|
+
return true;
|
|
61
|
+
return false;
|
|
85
62
|
}
|
package/package.json
CHANGED
|
@@ -1,81 +1,46 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"default": "./src/mailbox-name.ts"
|
|
47
|
-
},
|
|
48
|
-
"./wellknown": {
|
|
49
|
-
"types": "./src/wellknown.ts",
|
|
50
|
-
"default": "./src/wellknown.ts"
|
|
51
|
-
},
|
|
52
|
-
"./update-manifest": {
|
|
53
|
-
"types": "./src/update-manifest.ts",
|
|
54
|
-
"default": "./src/update-manifest.ts"
|
|
55
|
-
},
|
|
56
|
-
"./message-settlement": {
|
|
57
|
-
"types": "./src/message-settlement.ts",
|
|
58
|
-
"default": "./src/message-settlement.ts"
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
"scripts": {
|
|
62
|
-
"test:typecheck": "tsgo --noEmit",
|
|
63
|
-
"test:run": "node $NODE_TEST_FLAGS --import tsx --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/**/*.test.tsx' --test-coverage-lines=85 --test 'src/**/*.test.ts'"
|
|
64
|
-
},
|
|
65
|
-
"dependencies": {
|
|
66
|
-
"@remit/domain-enums": "*",
|
|
67
|
-
"short-uuid": "*",
|
|
68
|
-
"uuid": "*",
|
|
69
|
-
"zod": "^3.25.0",
|
|
70
|
-
"@remit/api-zod-schemas": "*"
|
|
71
|
-
},
|
|
72
|
-
"license": "MIT",
|
|
73
|
-
"publishConfig": {
|
|
74
|
-
"access": "public"
|
|
75
|
-
},
|
|
76
|
-
"repository": {
|
|
77
|
-
"type": "git",
|
|
78
|
-
"url": "git+https://github.com/remit-mail/reader.git",
|
|
79
|
-
"directory": "packages/data-ports"
|
|
80
|
-
}
|
|
2
|
+
"name": "@remit/data-ports",
|
|
3
|
+
"version": "0.0.84",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./conformance": {
|
|
13
|
+
"types": "./dist/conformance/index.d.ts",
|
|
14
|
+
"default": "./dist/conformance/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./*": {
|
|
17
|
+
"types": "./dist/*.d.ts",
|
|
18
|
+
"default": "./dist/*.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"prepack": "node ../../npm-scripts/pack-dist.mjs prepack",
|
|
23
|
+
"postpack": "node ../../npm-scripts/pack-dist.mjs postpack",
|
|
24
|
+
"test:typecheck": "tsgo --noEmit",
|
|
25
|
+
"test:run": "node $NODE_TEST_FLAGS --import tsx --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/**/*.test.tsx' --test-coverage-lines=85 --test 'src/**/*.test.ts'"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@remit/domain-enums": "*",
|
|
29
|
+
"short-uuid": "*",
|
|
30
|
+
"uuid": "*",
|
|
31
|
+
"zod": "^3.25.0",
|
|
32
|
+
"@remit/api-zod-schemas": "*"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/remit-mail/reader.git",
|
|
41
|
+
"directory": "packages/data-ports"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
]
|
|
81
46
|
}
|
package/src/account-settings.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { AccountSettingName } from "@remit/domain-enums";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
export type AccountSettingNameValue =
|
|
5
|
-
(typeof AccountSettingName)[keyof typeof AccountSettingName];
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Per-`kind` value schemas, one per variant of the `AccountSettingValue`
|
|
9
|
-
* discriminated union in TypeSpec (`AccountSetting.tsp`). Authored here because
|
|
10
|
-
* the registry is the binding authority for what a value must satisfy — these
|
|
11
|
-
* are the contract every read and write is validated against.
|
|
12
|
-
*/
|
|
13
|
-
export const BooleanSettingSchema = z.object({
|
|
14
|
-
kind: z.literal("Boolean"),
|
|
15
|
-
value: z.boolean(),
|
|
16
|
-
});
|
|
17
|
-
export const StringSettingSchema = z.object({
|
|
18
|
-
kind: z.literal("String"),
|
|
19
|
-
value: z.string(),
|
|
20
|
-
});
|
|
21
|
-
export const NumberSettingSchema = z.object({
|
|
22
|
-
kind: z.literal("Number"),
|
|
23
|
-
value: z.number(),
|
|
24
|
-
});
|
|
25
|
-
export const StringListSettingSchema = z.object({
|
|
26
|
-
kind: z.literal("StringList"),
|
|
27
|
-
value: z.array(z.string()),
|
|
28
|
-
});
|
|
29
|
-
export const MapSettingSchema = z.object({
|
|
30
|
-
kind: z.literal("Map"),
|
|
31
|
-
value: z.record(z.string(), z.string()),
|
|
32
|
-
});
|
|
33
|
-
/**
|
|
34
|
-
* MutedFlag-valued setting. The value is a rich mute object (`MutedFlag` in
|
|
35
|
-
* TypeSpec / `@remit/api-openapi-types`): a boolean plus audit metadata. The
|
|
36
|
-
* schema is authored here — generated packages ship only `.d.ts` for this shape,
|
|
37
|
-
* so the registry owns the runtime contract a relocated mute flag is validated
|
|
38
|
-
* against (RFC 032 lesson from the signature slice).
|
|
39
|
-
*/
|
|
40
|
-
export const MutedFlagSettingSchema = z.object({
|
|
41
|
-
kind: z.literal("MutedFlag"),
|
|
42
|
-
value: z.object({
|
|
43
|
-
value: z.boolean(),
|
|
44
|
-
setAt: z.number(),
|
|
45
|
-
setBy: z.string().optional(),
|
|
46
|
-
expiresAt: z.number().optional(),
|
|
47
|
-
reason: z.string().optional(),
|
|
48
|
-
}),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* RFC 032 Tier 1 registry: binds each setting `name` to the zod schema its
|
|
53
|
-
* `value` must satisfy. The discriminated union (`kind`) types the shape on the
|
|
54
|
-
* wire; this registry binds a name to one concrete shape, so a read or write of
|
|
55
|
-
* a given setting is validated against exactly the variant it requires.
|
|
56
|
-
*
|
|
57
|
-
* Adding a setting key is a deliberate change: extend the `AccountSettingName`
|
|
58
|
-
* enum in TypeSpec and add an entry here — never an ad-hoc, unvalidated value.
|
|
59
|
-
*/
|
|
60
|
-
export const accountSettingRegistry = {
|
|
61
|
-
[AccountSettingName.Theme]: StringSettingSchema,
|
|
62
|
-
[AccountSettingName.Density]: StringSettingSchema,
|
|
63
|
-
[AccountSettingName.DefaultComposerFormat]: StringSettingSchema,
|
|
64
|
-
[AccountSettingName.PinnedFolders]: StringListSettingSchema,
|
|
65
|
-
[AccountSettingName.AccountSignaturePlainText]: StringSettingSchema,
|
|
66
|
-
[AccountSettingName.AccountSignatureHtml]: StringSettingSchema,
|
|
67
|
-
[AccountSettingName.AccountDisplayName]: StringSettingSchema,
|
|
68
|
-
[AccountSettingName.AccountMuted]: MutedFlagSettingSchema,
|
|
69
|
-
[AccountSettingName.AccountComposeLanguages]: StringListSettingSchema,
|
|
70
|
-
[AccountSettingName.MailboxDisplayName]: StringSettingSchema,
|
|
71
|
-
[AccountSettingName.MailboxMuted]: MutedFlagSettingSchema,
|
|
72
|
-
[AccountSettingName.FolderRoleAppointment]: StringSettingSchema,
|
|
73
|
-
[AccountSettingName.FolderRoleAppointmentLabel]: StringSettingSchema,
|
|
74
|
-
/**
|
|
75
|
-
* Deprecated tombstone: superseded by `FolderRoleAppointment` (RFC 032
|
|
76
|
-
* exclusive-folder-appointment, #976). Kept only so `baseSettingName`
|
|
77
|
-
* recognises leftover `MailboxRole#<mailboxId>` rows written by the #963/#964
|
|
78
|
-
* backfill instead of throwing `Unknown account setting name`; the read-side
|
|
79
|
-
* groupers (`groupAccountOverrides`, `groupMailboxOverrides`,
|
|
80
|
-
* `groupFolderAppointmentsByAccount`) already skip any base they don't
|
|
81
|
-
* explicitly handle, so a `MailboxRole` row is ignored, not surfaced. Do not
|
|
82
|
-
* write new rows under this name. Remove once the row-deletion migration ships.
|
|
83
|
-
*/
|
|
84
|
-
[AccountSettingName.MailboxRole]: StringSettingSchema,
|
|
85
|
-
} as const satisfies Record<AccountSettingNameValue, z.ZodTypeAny>;
|
|
86
|
-
|
|
87
|
-
export type AccountSettingRegistry = typeof accountSettingRegistry;
|
|
88
|
-
|
|
89
|
-
export type AccountSettingValueFor<N extends AccountSettingNameValue> = z.infer<
|
|
90
|
-
AccountSettingRegistry[N]
|
|
91
|
-
>;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Separator between a setting's base enum member and its per-target suffix in a
|
|
95
|
-
* composite stored name (e.g. `AccountSignaturePlainText#<accountId>`). Some
|
|
96
|
-
* settings are per-account, but `AccountSetting` is keyed per accountConfigId, so
|
|
97
|
-
* the stored `name` encodes the target after this separator. The registry still
|
|
98
|
-
* validates by the base member; readers filter by the suffix.
|
|
99
|
-
*/
|
|
100
|
-
export const SETTING_NAME_SEPARATOR = "#";
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* A persisted setting `name`. Either a plain closed-enum member or a composite
|
|
104
|
-
* `<base>#<suffix>` for a per-target setting. Always a `string` whose first
|
|
105
|
-
* segment is a known base member.
|
|
106
|
-
*/
|
|
107
|
-
export type StoredSettingName = string;
|
|
108
|
-
|
|
109
|
-
/** Compose the stored name for a per-target setting (e.g. one signature per account). */
|
|
110
|
-
export function composeSettingName(
|
|
111
|
-
base: AccountSettingNameValue,
|
|
112
|
-
target: string,
|
|
113
|
-
): StoredSettingName {
|
|
114
|
-
return `${base}${SETTING_NAME_SEPARATOR}${target}`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const isAccountSettingNameValue = (
|
|
118
|
-
value: string,
|
|
119
|
-
): value is AccountSettingNameValue =>
|
|
120
|
-
Object.hasOwn(accountSettingRegistry, value);
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Derive the base enum member from a stored name. Strips the `#<suffix>` from a
|
|
124
|
-
* composite name; returns a plain name unchanged. Throws when the base segment
|
|
125
|
-
* is not a registered setting (let-it-crash — an unknown key is a programmer
|
|
126
|
-
* error, never an ad-hoc column).
|
|
127
|
-
*/
|
|
128
|
-
export function baseSettingName(
|
|
129
|
-
name: StoredSettingName,
|
|
130
|
-
): AccountSettingNameValue {
|
|
131
|
-
const base = name.split(SETTING_NAME_SEPARATOR)[0] ?? "";
|
|
132
|
-
if (!isAccountSettingNameValue(base)) {
|
|
133
|
-
throw new Error(`Unknown account setting name: ${name}`);
|
|
134
|
-
}
|
|
135
|
-
return base;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Every value shape the registry can hold. Useful where a value is handled
|
|
140
|
-
* generically (e.g. the persisted `value` attribute) rather than per-name.
|
|
141
|
-
*/
|
|
142
|
-
export type AnyAccountSettingValue = z.infer<
|
|
143
|
-
| typeof BooleanSettingSchema
|
|
144
|
-
| typeof StringSettingSchema
|
|
145
|
-
| typeof NumberSettingSchema
|
|
146
|
-
| typeof StringListSettingSchema
|
|
147
|
-
| typeof MapSettingSchema
|
|
148
|
-
| typeof MutedFlagSettingSchema
|
|
149
|
-
>;
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Parse-and-narrow a value against the schema registered for `name`. Accepts a
|
|
153
|
-
* plain enum member or a composite `<base>#<suffix>` name; validation resolves
|
|
154
|
-
* to the base member's schema. Throws a ZodError on mismatch (let-it-crash);
|
|
155
|
-
* returns the value typed to the variant the name requires.
|
|
156
|
-
*/
|
|
157
|
-
export function parseAccountSettingValue<N extends AccountSettingNameValue>(
|
|
158
|
-
name: N,
|
|
159
|
-
value: unknown,
|
|
160
|
-
): AccountSettingValueFor<N>;
|
|
161
|
-
export function parseAccountSettingValue(
|
|
162
|
-
name: StoredSettingName,
|
|
163
|
-
value: unknown,
|
|
164
|
-
): AnyAccountSettingValue;
|
|
165
|
-
export function parseAccountSettingValue(
|
|
166
|
-
name: StoredSettingName,
|
|
167
|
-
value: unknown,
|
|
168
|
-
): AnyAccountSettingValue {
|
|
169
|
-
const schema = accountSettingRegistry[baseSettingName(name)];
|
|
170
|
-
return schema.parse(value) as AnyAccountSettingValue;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Non-throwing variant: returns the zod SafeParse result so a caller with a
|
|
175
|
-
* recovery path can branch on `success`. Accepts composite names like above.
|
|
176
|
-
*/
|
|
177
|
-
export function safeParseAccountSettingValue<N extends AccountSettingNameValue>(
|
|
178
|
-
name: N,
|
|
179
|
-
value: unknown,
|
|
180
|
-
): z.SafeParseReturnType<unknown, AccountSettingValueFor<N>>;
|
|
181
|
-
export function safeParseAccountSettingValue(
|
|
182
|
-
name: StoredSettingName,
|
|
183
|
-
value: unknown,
|
|
184
|
-
): z.SafeParseReturnType<unknown, AnyAccountSettingValue>;
|
|
185
|
-
export function safeParseAccountSettingValue(
|
|
186
|
-
name: StoredSettingName,
|
|
187
|
-
value: unknown,
|
|
188
|
-
): z.SafeParseReturnType<unknown, AnyAccountSettingValue> {
|
|
189
|
-
const schema = accountSettingRegistry[baseSettingName(name)];
|
|
190
|
-
return schema.safeParse(value) as z.SafeParseReturnType<
|
|
191
|
-
unknown,
|
|
192
|
-
AnyAccountSettingValue
|
|
193
|
-
>;
|
|
194
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { after, before, describe, test } from "node:test";
|
|
3
|
-
import { AccountService, ConnectionState } from "@remit/domain-enums";
|
|
4
|
-
import type { IAccountRepository } from "../interfaces/account.js";
|
|
5
|
-
import type { CreateAccountInput } from "../types.js";
|
|
6
|
-
import type { RepositoryConformanceHarness } from "./harness.js";
|
|
7
|
-
|
|
8
|
-
const accountInput = (accountConfigId: string): CreateAccountInput => ({
|
|
9
|
-
accountConfigId,
|
|
10
|
-
username: "person@example.com",
|
|
11
|
-
email: "person@example.com",
|
|
12
|
-
imapHost: "imap.example.com",
|
|
13
|
-
imapPort: 993,
|
|
14
|
-
imapTls: true,
|
|
15
|
-
imapStartTls: false,
|
|
16
|
-
isActive: true,
|
|
17
|
-
connectionState: ConnectionState.NotAuthenticated,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
export function accountRepositoryConformance(
|
|
21
|
-
harness: RepositoryConformanceHarness<IAccountRepository>,
|
|
22
|
-
): void {
|
|
23
|
-
describe("IAccountRepository conformance", () => {
|
|
24
|
-
let repo: IAccountRepository;
|
|
25
|
-
|
|
26
|
-
before(async () => {
|
|
27
|
-
repo = await harness.createRepository();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
after(() => harness.teardown());
|
|
31
|
-
|
|
32
|
-
test("an account created without a service selection syncs mail", async () => {
|
|
33
|
-
const created = await repo.create(accountInput(harness.makeId()));
|
|
34
|
-
|
|
35
|
-
assert.deepEqual(created.syncedServices, [AccountService.Mail]);
|
|
36
|
-
assert.deepEqual((await repo.get(created.accountId)).syncedServices, [
|
|
37
|
-
AccountService.Mail,
|
|
38
|
-
]);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("an account keeps the selection it was created with", async () => {
|
|
42
|
-
const created = await repo.create({
|
|
43
|
-
...accountInput(harness.makeId()),
|
|
44
|
-
syncedServices: [AccountService.Calendar],
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
assert.deepEqual((await repo.get(created.accountId)).syncedServices, [
|
|
48
|
-
AccountService.Calendar,
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("an update replaces the selection outright", async () => {
|
|
53
|
-
const created = await repo.create(accountInput(harness.makeId()));
|
|
54
|
-
|
|
55
|
-
const updated = await repo.update(created.accountId, {
|
|
56
|
-
syncedServices: [AccountService.Mail, AccountService.Calendar],
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
assert.deepEqual(updated.syncedServices, [
|
|
60
|
-
AccountService.Mail,
|
|
61
|
-
AccountService.Calendar,
|
|
62
|
-
]);
|
|
63
|
-
assert.deepEqual((await repo.get(created.accountId)).syncedServices, [
|
|
64
|
-
AccountService.Mail,
|
|
65
|
-
AccountService.Calendar,
|
|
66
|
-
]);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("an update that leaves the selection out keeps it", async () => {
|
|
70
|
-
const created = await repo.create({
|
|
71
|
-
...accountInput(harness.makeId()),
|
|
72
|
-
syncedServices: [AccountService.Calendar],
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await repo.update(created.accountId, { lastError: "timeout" });
|
|
76
|
-
|
|
77
|
-
assert.deepEqual((await repo.get(created.accountId)).syncedServices, [
|
|
78
|
-
AccountService.Calendar,
|
|
79
|
-
]);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("the mail sources of a config leave out an account with mail off", async () => {
|
|
83
|
-
const accountConfigId = harness.makeId();
|
|
84
|
-
const mailOnly = await repo.create(accountInput(accountConfigId));
|
|
85
|
-
const both = await repo.create({
|
|
86
|
-
...accountInput(accountConfigId),
|
|
87
|
-
syncedServices: [AccountService.Mail, AccountService.Calendar],
|
|
88
|
-
});
|
|
89
|
-
await repo.create({
|
|
90
|
-
...accountInput(accountConfigId),
|
|
91
|
-
syncedServices: [AccountService.Calendar],
|
|
92
|
-
});
|
|
93
|
-
await repo.create(accountInput(harness.makeId()));
|
|
94
|
-
|
|
95
|
-
const sources =
|
|
96
|
-
await repo.listMailSourcesByAccountConfig(accountConfigId);
|
|
97
|
-
|
|
98
|
-
assert.deepEqual(
|
|
99
|
-
sources.map((account) => account.accountId).sort(),
|
|
100
|
-
[mailOnly.accountId, both.accountId].sort(),
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
}
|