From ff61eb44ac4895c1a014d492e02c8ac3202f7fe5 Mon Sep 17 00:00:00 2001 From: Hunter Tunnicliff Date: Sun, 24 Mar 2024 21:26:40 -0700 Subject: [PATCH] Add header field support --- src/index.ts | 3 +++ src/types/contracts.ts | 43 +++++++++++++++++++++++++++++++++--------- src/types/jmap-mail.ts | 22 +++++++++++++++------ src/utils.ts | 25 ++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 src/utils.ts diff --git a/src/index.ts b/src/index.ts index c12b049..524aa50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,9 @@ export * from "./client"; export { JamClient as default } from "./client"; +// Utils +export * from "./utils"; + // ------------------------------------------------- // Types // ------------------------------------------------- diff --git a/src/types/contracts.ts b/src/types/contracts.ts index 6989120..7e64d74 100644 --- a/src/types/contracts.ts +++ b/src/types/contracts.ts @@ -29,7 +29,8 @@ import type { EmailImport, EmailSubmission, EmailSubmissionFilterCondition, - EmailWithoutHeaderKeys, + GetValueFromHeaderKey, + HeaderFieldKey, Identity, Mailbox, MailboxFilterCondition, @@ -67,13 +68,7 @@ export type Requests = { "Thread/get": GetArguments; "Thread/changes": ChangesArguments; // Email ---------------------------------- - "Email/get": GetArguments & { - bodyProperties?: Array; - fetchTextBodyValues?: boolean; - fetchHTMLBodyValues?: boolean; - fetchAllBodyValues?: boolean; - maxBodyValueBytes?: number; - }; + "Email/get": GetEmailArguments; "Email/changes": ChangesArguments; "Email/query": QueryArguments & { collapseThreads?: boolean; @@ -163,7 +158,7 @@ export type Responses = HasAllKeysOfRelated< "Thread/get": GetResponse; "Thread/changes": ChangesResponse; // Email ---------------------------------- - "Email/get": GetResponse; + "Email/get": GetEmailResponse; "Email/changes": ChangesResponse; "Email/query": QueryResponse; "Email/queryChanges": QueryChangesResponse; @@ -280,3 +275,33 @@ export type ProxyAPI = { ) => Promise<[Responses[Method], Meta]>; }; }; + +export type GetEmailArguments = { + accountId: ID; + ids?: ReadonlyArray | null; + properties?: ReadonlyArray | null; + bodyProperties?: Array; + fetchTextBodyValues?: boolean; + fetchHTMLBodyValues?: boolean; + fetchAllBodyValues?: boolean; + maxBodyValueBytes?: number; +}; + +export type GetEmailResponse = Args extends GetEmailArguments + ? { + accountId: ID; + state: string; + list: ReadonlyArray< + Args["properties"] extends Array + ? { + [Key in P]: Key extends HeaderFieldKey + ? GetValueFromHeaderKey + : Key extends keyof Email + ? Email[Key] + : never; + } + : Email + >; + notFound: ReadonlyArray; + } + : never; diff --git a/src/types/jmap-mail.ts b/src/types/jmap-mail.ts index 8283404..61727f1 100644 --- a/src/types/jmap-mail.ts +++ b/src/types/jmap-mail.ts @@ -92,12 +92,7 @@ export type Thread = { */ export type Email = EmailMetadataFields & EmailHeaderFields & - EmailBodyPartFields & { - // TODO: Support header parsed forms - [K in HeaderFieldKey]: any; - }; - -export type EmailWithoutHeaderKeys = Omit; + EmailBodyPartFields; /** * [rfc8621 ยง 4.1.1](https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1) @@ -160,6 +155,21 @@ export type HeaderFieldKey = | `header:${string}:all` | `header:${string}`; +export type GetValueFromHeaderKey = + K extends `header:${string}:as${infer Form extends keyof HeaderParsedForm}:all` + ? HeaderParsedForm[Form] extends Array + ? HeaderParsedForm[Form] + : Array + : K extends `header:${string}:as${infer Form extends keyof HeaderParsedForm}` + ? HeaderParsedForm[Form] + : K extends `header:${string}:all` + ? string[] + : K extends `header:${infer Name extends string}` + ? Name extends `:${string}` | `${string}:` | `:${string}:` + ? never + : string + : never; + export type EmailHeader = { name: string; value: string; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..c5080f7 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,25 @@ +import type { HeaderParsedForm } from "./types/jmap-mail"; + +/** + * Generate a key to retrieve a header field + * + * @example + * ```ts + * headerField("Some-Header", "Addresses") // "header:Some-Header:asAddresses" + * ``` + */ +export function headerField< + Name extends string, + Form extends keyof HeaderParsedForm +>(name: Name, form: Form) { + return `header:${name}:as${form}` as const; +} + +export function allHeaderFields< + Name extends string, + Form extends keyof HeaderParsedForm +>(name: Name, form: Form) { + const header = headerField(name, form); + + return `${header}:all` as const; +}