Skip to content

Commit

Permalink
Add header field support
Browse files Browse the repository at this point in the history
  • Loading branch information
htunnicliff committed Mar 25, 2024
1 parent 7e21ffa commit ff61eb4
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 15 deletions.
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
export * from "./client";
export { JamClient as default } from "./client";

// Utils
export * from "./utils";

// -------------------------------------------------
// Types
// -------------------------------------------------
Expand Down
43 changes: 34 additions & 9 deletions src/types/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import type {
EmailImport,
EmailSubmission,
EmailSubmissionFilterCondition,
EmailWithoutHeaderKeys,
GetValueFromHeaderKey,
HeaderFieldKey,
Identity,
Mailbox,
MailboxFilterCondition,
Expand Down Expand Up @@ -67,13 +68,7 @@ export type Requests = {
"Thread/get": GetArguments<Thread>;
"Thread/changes": ChangesArguments;
// Email ----------------------------------
"Email/get": GetArguments<EmailWithoutHeaderKeys> & {
bodyProperties?: Array<keyof EmailWithoutHeaderKeys>;
fetchTextBodyValues?: boolean;
fetchHTMLBodyValues?: boolean;
fetchAllBodyValues?: boolean;
maxBodyValueBytes?: number;
};
"Email/get": GetEmailArguments;
"Email/changes": ChangesArguments;
"Email/query": QueryArguments<Email, EmailFilterCondition> & {
collapseThreads?: boolean;
Expand Down Expand Up @@ -163,7 +158,7 @@ export type Responses<A> = HasAllKeysOfRelated<
"Thread/get": GetResponse<Thread, A>;
"Thread/changes": ChangesResponse;
// Email ----------------------------------
"Email/get": GetResponse<Email, A>;
"Email/get": GetEmailResponse<A>;
"Email/changes": ChangesResponse;
"Email/query": QueryResponse;
"Email/queryChanges": QueryChangesResponse;
Expand Down Expand Up @@ -280,3 +275,33 @@ export type ProxyAPI = {
) => Promise<[Responses<A>[Method], Meta]>;
};
};

export type GetEmailArguments = {
accountId: ID;
ids?: ReadonlyArray<ID> | null;
properties?: ReadonlyArray<keyof Email | HeaderFieldKey> | null;
bodyProperties?: Array<keyof Email>;
fetchTextBodyValues?: boolean;
fetchHTMLBodyValues?: boolean;
fetchAllBodyValues?: boolean;
maxBodyValueBytes?: number;
};

export type GetEmailResponse<Args> = Args extends GetEmailArguments
? {
accountId: ID;
state: string;
list: ReadonlyArray<
Args["properties"] extends Array<infer P extends string>
? {
[Key in P]: Key extends HeaderFieldKey
? GetValueFromHeaderKey<Key>
: Key extends keyof Email
? Email[Key]
: never;
}
: Email
>;
notFound: ReadonlyArray<ID>;
}
: never;
22 changes: 16 additions & 6 deletions src/types/jmap-mail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Email, HeaderFieldKey>;
EmailBodyPartFields;

/**
* [rfc8621 § 4.1.1](https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1)
Expand Down Expand Up @@ -160,6 +155,21 @@ export type HeaderFieldKey =
| `header:${string}:all`
| `header:${string}`;

export type GetValueFromHeaderKey<K extends HeaderFieldKey> =
K extends `header:${string}:as${infer Form extends keyof HeaderParsedForm}:all`
? HeaderParsedForm[Form] extends Array<infer _>
? HeaderParsedForm[Form]
: Array<HeaderParsedForm[Form]>
: 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;
Expand Down
25 changes: 25 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit ff61eb4

Please sign in to comment.