@dxos/plugin-jmap 0.10.0

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.
Files changed (180) hide show
  1. package/LICENSE +105 -0
  2. package/dist/lib/JmapPlugin.mjs +16 -0
  3. package/dist/lib/JmapPlugin.mjs.map +1 -0
  4. package/dist/lib/apis.mjs +2 -0
  5. package/dist/lib/capabilities.mjs +17 -0
  6. package/dist/lib/capabilities.mjs.map +1 -0
  7. package/dist/lib/chunk-apis.mjs +756 -0
  8. package/dist/lib/chunk-apis.mjs.map +1 -0
  9. package/dist/lib/chunk-connector.mjs +111 -0
  10. package/dist/lib/chunk-connector.mjs.map +1 -0
  11. package/dist/lib/chunk-constants.mjs +23 -0
  12. package/dist/lib/chunk-constants.mjs.map +1 -0
  13. package/dist/lib/chunk-errors.mjs +42 -0
  14. package/dist/lib/chunk-errors.mjs.map +1 -0
  15. package/dist/lib/chunk-handler.mjs +27 -0
  16. package/dist/lib/chunk-handler.mjs.map +1 -0
  17. package/dist/lib/chunk-jmap-fixtures.mjs +102 -0
  18. package/dist/lib/chunk-jmap-fixtures.mjs.map +1 -0
  19. package/dist/lib/chunk-mail-send.mjs +17 -0
  20. package/dist/lib/chunk-mail-send.mjs.map +1 -0
  21. package/dist/lib/chunk-meta.mjs +37 -0
  22. package/dist/lib/chunk-meta.mjs.map +1 -0
  23. package/dist/lib/chunk-operation-handler.mjs +12 -0
  24. package/dist/lib/chunk-operation-handler.mjs.map +1 -0
  25. package/dist/lib/chunk-send.mjs +98 -0
  26. package/dist/lib/chunk-send.mjs.map +1 -0
  27. package/dist/lib/chunk-sync-provider.mjs +511 -0
  28. package/dist/lib/chunk-sync-provider.mjs.map +1 -0
  29. package/dist/lib/chunk-sync.mjs +37 -0
  30. package/dist/lib/chunk-sync.mjs.map +1 -0
  31. package/dist/lib/index.mjs +4 -0
  32. package/dist/lib/meta.mjs +2 -0
  33. package/dist/lib/operations.mjs +13 -0
  34. package/dist/lib/operations.mjs.map +1 -0
  35. package/dist/lib/plugin.mjs +9 -0
  36. package/dist/lib/plugin.mjs.map +1 -0
  37. package/dist/lib/services.mjs +139 -0
  38. package/dist/lib/services.mjs.map +1 -0
  39. package/dist/lib/testing/node.mjs +2 -0
  40. package/dist/lib/testing.mjs +19 -0
  41. package/dist/lib/testing.mjs.map +1 -0
  42. package/dist/lib/translations.mjs +7 -0
  43. package/dist/lib/translations.mjs.map +1 -0
  44. package/dist/types/dx.config.d.ts +28 -0
  45. package/dist/types/dx.config.d.ts.map +1 -0
  46. package/dist/types/src/JmapPlugin.d.ts +9 -0
  47. package/dist/types/src/JmapPlugin.d.ts.map +1 -0
  48. package/dist/types/src/apis/Jmap/api.d.ts +42 -0
  49. package/dist/types/src/apis/Jmap/api.d.ts.map +1 -0
  50. package/dist/types/src/apis/Jmap/index.d.ts +3 -0
  51. package/dist/types/src/apis/Jmap/index.d.ts.map +1 -0
  52. package/dist/types/src/apis/Jmap/types.d.ts +65 -0
  53. package/dist/types/src/apis/Jmap/types.d.ts.map +1 -0
  54. package/dist/types/src/apis/JmapMail/api.d.ts +223 -0
  55. package/dist/types/src/apis/JmapMail/api.d.ts.map +1 -0
  56. package/dist/types/src/apis/JmapMail/index.d.ts +4 -0
  57. package/dist/types/src/apis/JmapMail/index.d.ts.map +1 -0
  58. package/dist/types/src/apis/JmapMail/query.d.ts +26 -0
  59. package/dist/types/src/apis/JmapMail/query.d.ts.map +1 -0
  60. package/dist/types/src/apis/JmapMail/query.test.d.ts +2 -0
  61. package/dist/types/src/apis/JmapMail/query.test.d.ts.map +1 -0
  62. package/dist/types/src/apis/JmapMail/types.d.ts +290 -0
  63. package/dist/types/src/apis/JmapMail/types.d.ts.map +1 -0
  64. package/dist/types/src/apis/index.d.ts +3 -0
  65. package/dist/types/src/apis/index.d.ts.map +1 -0
  66. package/dist/types/src/apis/jmap-api.test.d.ts +2 -0
  67. package/dist/types/src/apis/jmap-api.test.d.ts.map +1 -0
  68. package/dist/types/src/capabilities/connector.d.ts +5 -0
  69. package/dist/types/src/capabilities/connector.d.ts.map +1 -0
  70. package/dist/types/src/capabilities/credential-form.d.ts +37 -0
  71. package/dist/types/src/capabilities/credential-form.d.ts.map +1 -0
  72. package/dist/types/src/capabilities/credential-form.test.d.ts +2 -0
  73. package/dist/types/src/capabilities/credential-form.test.d.ts.map +1 -0
  74. package/dist/types/src/capabilities/index.d.ts +5 -0
  75. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  76. package/dist/types/src/capabilities/mail-send.d.ts +6 -0
  77. package/dist/types/src/capabilities/mail-send.d.ts.map +1 -0
  78. package/dist/types/src/capabilities/operation-handler.d.ts +5 -0
  79. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  80. package/dist/types/src/constants.d.ts +19 -0
  81. package/dist/types/src/constants.d.ts.map +1 -0
  82. package/dist/types/src/errors.d.ts +103 -0
  83. package/dist/types/src/errors.d.ts.map +1 -0
  84. package/dist/types/src/index.d.ts +4 -0
  85. package/dist/types/src/index.d.ts.map +1 -0
  86. package/dist/types/src/meta.d.ts +33 -0
  87. package/dist/types/src/meta.d.ts.map +1 -0
  88. package/dist/types/src/operations/index.d.ts +3 -0
  89. package/dist/types/src/operations/index.d.ts.map +1 -0
  90. package/dist/types/src/operations/mail/mapper.d.ts +49 -0
  91. package/dist/types/src/operations/mail/mapper.d.ts.map +1 -0
  92. package/dist/types/src/operations/mail/materialize/handler.d.ts +11 -0
  93. package/dist/types/src/operations/mail/materialize/handler.d.ts.map +1 -0
  94. package/dist/types/src/operations/mail/send/handler.d.ts +4 -0
  95. package/dist/types/src/operations/mail/send/handler.d.ts.map +1 -0
  96. package/dist/types/src/operations/mail/send/index.d.ts +2 -0
  97. package/dist/types/src/operations/mail/send/index.d.ts.map +1 -0
  98. package/dist/types/src/operations/mail/sync/handler.d.ts +4 -0
  99. package/dist/types/src/operations/mail/sync/handler.d.ts.map +1 -0
  100. package/dist/types/src/operations/mail/sync/index.d.ts +3 -0
  101. package/dist/types/src/operations/mail/sync/index.d.ts.map +1 -0
  102. package/dist/types/src/operations/mail/sync/mapper.test.d.ts +2 -0
  103. package/dist/types/src/operations/mail/sync/mapper.test.d.ts.map +1 -0
  104. package/dist/types/src/operations/mail/sync/sync-e2e.test.d.ts +2 -0
  105. package/dist/types/src/operations/mail/sync/sync-e2e.test.d.ts.map +1 -0
  106. package/dist/types/src/operations/mail/sync/sync-provider.d.ts +11 -0
  107. package/dist/types/src/operations/mail/sync/sync-provider.d.ts.map +1 -0
  108. package/dist/types/src/operations/mail/sync/sync.test.d.ts +2 -0
  109. package/dist/types/src/operations/mail/sync/sync.test.d.ts.map +1 -0
  110. package/dist/types/src/operations/mail/sync/system-tags.d.ts +14 -0
  111. package/dist/types/src/operations/mail/sync/system-tags.d.ts.map +1 -0
  112. package/dist/types/src/operations/mail/tags.d.ts +12 -0
  113. package/dist/types/src/operations/mail/tags.d.ts.map +1 -0
  114. package/dist/types/src/plugin.d.ts +4 -0
  115. package/dist/types/src/plugin.d.ts.map +1 -0
  116. package/dist/types/src/services/index.d.ts +3 -0
  117. package/dist/types/src/services/index.d.ts.map +1 -0
  118. package/dist/types/src/services/jmap-credentials.d.ts +39 -0
  119. package/dist/types/src/services/jmap-credentials.d.ts.map +1 -0
  120. package/dist/types/src/services/jmap-mail-api.d.ts +109 -0
  121. package/dist/types/src/services/jmap-mail-api.d.ts.map +1 -0
  122. package/dist/types/src/testing/index.d.ts +5 -0
  123. package/dist/types/src/testing/index.d.ts.map +1 -0
  124. package/dist/types/src/testing/jmap-fixtures.d.ts +26 -0
  125. package/dist/types/src/testing/jmap-fixtures.d.ts.map +1 -0
  126. package/dist/types/src/testing/jmap-fixtures.test.d.ts +2 -0
  127. package/dist/types/src/testing/jmap-fixtures.test.d.ts.map +1 -0
  128. package/dist/types/src/testing/node.d.ts +4 -0
  129. package/dist/types/src/testing/node.d.ts.map +1 -0
  130. package/dist/types/src/testing/sync-fixture.d.ts +22 -0
  131. package/dist/types/src/testing/sync-fixture.d.ts.map +1 -0
  132. package/dist/types/src/translations.d.ts +8 -0
  133. package/dist/types/src/translations.d.ts.map +1 -0
  134. package/dist/types/tsconfig.tsbuildinfo +1 -0
  135. package/dx.config.ts +32 -0
  136. package/package.json +121 -0
  137. package/src/JmapPlugin.ts +25 -0
  138. package/src/apis/Jmap/api.ts +138 -0
  139. package/src/apis/Jmap/index.ts +6 -0
  140. package/src/apis/Jmap/types.ts +86 -0
  141. package/src/apis/JmapMail/api.ts +324 -0
  142. package/src/apis/JmapMail/index.ts +7 -0
  143. package/src/apis/JmapMail/query.test.ts +181 -0
  144. package/src/apis/JmapMail/query.ts +231 -0
  145. package/src/apis/JmapMail/types.ts +196 -0
  146. package/src/apis/index.ts +8 -0
  147. package/src/apis/jmap-api.test.ts +261 -0
  148. package/src/capabilities/connector.ts +44 -0
  149. package/src/capabilities/credential-form.test.ts +69 -0
  150. package/src/capabilities/credential-form.ts +113 -0
  151. package/src/capabilities/index.ts +24 -0
  152. package/src/capabilities/mail-send.ts +21 -0
  153. package/src/capabilities/operation-handler.ts +16 -0
  154. package/src/constants.ts +24 -0
  155. package/src/errors.ts +38 -0
  156. package/src/index.ts +7 -0
  157. package/src/meta.ts +9 -0
  158. package/src/operations/index.ts +13 -0
  159. package/src/operations/mail/mapper.ts +191 -0
  160. package/src/operations/mail/materialize/handler.ts +41 -0
  161. package/src/operations/mail/send/handler.ts +116 -0
  162. package/src/operations/mail/send/index.ts +5 -0
  163. package/src/operations/mail/sync/handler.ts +52 -0
  164. package/src/operations/mail/sync/index.ts +7 -0
  165. package/src/operations/mail/sync/mapper.test.ts +150 -0
  166. package/src/operations/mail/sync/sync-e2e.test.ts +63 -0
  167. package/src/operations/mail/sync/sync-provider.ts +481 -0
  168. package/src/operations/mail/sync/sync.test.ts +766 -0
  169. package/src/operations/mail/sync/system-tags.ts +24 -0
  170. package/src/operations/mail/tags.ts +29 -0
  171. package/src/plugin.ts +11 -0
  172. package/src/services/index.ts +6 -0
  173. package/src/services/jmap-credentials.ts +61 -0
  174. package/src/services/jmap-mail-api.ts +294 -0
  175. package/src/testing/index.ts +8 -0
  176. package/src/testing/jmap-fixtures.test.ts +107 -0
  177. package/src/testing/jmap-fixtures.ts +105 -0
  178. package/src/testing/node.ts +12 -0
  179. package/src/testing/sync-fixture.ts +39 -0
  180. package/src/translations.ts +15 -0
@@ -0,0 +1,756 @@
1
+ import { t as JmapApiError } from "./chunk-errors.mjs";
2
+ import * as HttpClient from "@effect/platform/HttpClient";
3
+ import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
4
+ import * as HttpClientResponse from "@effect/platform/HttpClientResponse";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Schedule from "effect/Schedule";
7
+ import * as Schema from "effect/Schema";
8
+ import { withAuthorization } from "@dxos/compute-runtime";
9
+ import * as Context from "effect/Context";
10
+ import * as Layer from "effect/Layer";
11
+ import { Database } from "@dxos/echo";
12
+ import { log } from "@dxos/log";
13
+ import { subDays, subMonths, subWeeks, subYears } from "date-fns";
14
+ import * as Match from "effect/Match";
15
+ import * as Option from "effect/Option";
16
+ //#region \0rolldown/runtime.js
17
+ var __defProp = Object.defineProperty;
18
+ var __exportAll = (all, no_symbols) => {
19
+ let target = {};
20
+ for (var name in all) __defProp(target, name, {
21
+ get: all[name],
22
+ enumerable: true
23
+ });
24
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
25
+ return target;
26
+ };
27
+ //#endregion
28
+ //#region src/services/jmap-credentials.ts
29
+ var __dxlog_file = "/Users/burdon/Code/dxos/dxos/.claude/worktrees/confident-jones-b0b4bf/packages/plugins/plugin-jmap/src/services/jmap-credentials.ts";
30
+ /**
31
+ * Service for accessing JMAP credentials.
32
+ *
33
+ * Mirrors `GoogleCredentials`: an operation invoked with a `Connection` composes
34
+ * `fromConnection(ref)`; one invoked with an external-sync cursor composes
35
+ * `fromAccessToken(cursor.spec.source)` directly (the cursor no longer relates to `Connection`).
36
+ * Unlike Google, there is no database-credential fallback — a JMAP connection always carries host +
37
+ * token in its token record.
38
+ */
39
+ var JmapCredentials = class JmapCredentials extends Context.Tag("JmapCredentials")() {
40
+ /** Creates a credentials layer from an AccessToken ref. Loads its `source` (host), `account`, `token`. */
41
+ static fromAccessToken = (accessTokenRef) => Layer.effect(JmapCredentials, Effect.gen(function* () {
42
+ const accessToken = yield* Database.load(accessTokenRef);
43
+ log("using access token", { source: accessToken.source }, {
44
+ "~LogMeta": "~LogMeta",
45
+ F: __dxlog_file,
46
+ L: 41,
47
+ S: this
48
+ });
49
+ return {
50
+ host: accessToken.source,
51
+ account: accessToken.account,
52
+ token: accessToken.token
53
+ };
54
+ }));
55
+ /** Creates a credentials layer from a Connection ref. Loads its `accessToken`'s host/account/token. */
56
+ static fromConnection = (connectionRef) => Layer.effect(JmapCredentials, Effect.gen(function* () {
57
+ const connection = yield* Database.load(connectionRef);
58
+ const accessToken = yield* Database.load(connection.accessToken);
59
+ log("using connection access token", { source: accessToken.source }, {
60
+ "~LogMeta": "~LogMeta",
61
+ F: __dxlog_file,
62
+ L: 53,
63
+ S: this
64
+ });
65
+ return {
66
+ host: accessToken.source,
67
+ account: accessToken.account,
68
+ token: accessToken.token
69
+ };
70
+ }));
71
+ /** Creates a credentials layer from explicit values (credential-form validation and tests). */
72
+ static fromValues = (values) => Layer.succeed(JmapCredentials, {
73
+ host: values.host,
74
+ account: values.account,
75
+ token: values.token
76
+ });
77
+ };
78
+ //#endregion
79
+ //#region src/apis/Jmap/types.ts
80
+ /** Session object returned from `https://${host}/.well-known/jmap` (RFC 8620 §2). */
81
+ var Session = Schema.Struct({
82
+ /** Endpoint for all subsequent API requests. */
83
+ apiUrl: Schema.String,
84
+ /**
85
+ * URI Template (RFC 6570) for blob downloads, with `accountId`/`blobId`/`type`/`name` variables
86
+ * (RFC 8620 §6.2). Required, matching the spec — every real JMAP session advertises one.
87
+ */
88
+ downloadUrl: Schema.String,
89
+ /** Authenticated username (used to seed the connection account when the form leaves it blank). */
90
+ username: Schema.optional(Schema.String),
91
+ /** Capability urn → primary account id (e.g. `urn:ietf:params:jmap:mail`). */
92
+ primaryAccounts: Schema.Record({
93
+ key: Schema.String,
94
+ value: Schema.String
95
+ }),
96
+ accounts: Schema.optional(Schema.Record({
97
+ key: Schema.String,
98
+ value: Schema.Unknown
99
+ })),
100
+ capabilities: Schema.optional(Schema.Record({
101
+ key: Schema.String,
102
+ value: Schema.Unknown
103
+ })),
104
+ state: Schema.optional(Schema.String)
105
+ });
106
+ /** A method-level error response: `["error", { type, description }, callId]` (RFC 8620 §3.6.1). */
107
+ var MethodError = Schema.Struct({
108
+ type: Schema.String,
109
+ description: Schema.optional(Schema.NullOr(Schema.String))
110
+ });
111
+ /** A single `[name, args, callId]` invocation result; `args` is decoded per-method by the caller. */
112
+ var MethodResponse = Schema.Tuple(Schema.String, Schema.Unknown, Schema.String);
113
+ /** The top-level response envelope of a batched API request (RFC 8620 §3.3). */
114
+ var Response = Schema.Struct({
115
+ methodResponses: Schema.Array(MethodResponse),
116
+ sessionState: Schema.optional(Schema.String)
117
+ });
118
+ //#endregion
119
+ //#region src/apis/Jmap/api.ts
120
+ /** Shared per-attempt timeout/retry policy for JMAP HTTP requests (session, batched API, blob download). */
121
+ var REQUEST_TIMEOUT = "10 seconds";
122
+ var REQUEST_RETRY = Schedule.exponential(1e3).pipe(Schedule.compose(Schedule.recurs(3)));
123
+ var shouldRetry = (error) => !(error instanceof JmapApiError && error.status !== void 0 && error.status >= 400 && error.status < 500);
124
+ /** Capability set for read/write mail requests. */
125
+ var MAIL_CAPABILITIES = ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"];
126
+ /** Capability set including submission, required to send mail. */
127
+ var SUBMISSION_CAPABILITIES = [...MAIL_CAPABILITIES, "urn:ietf:params:jmap:submission"];
128
+ /**
129
+ * Discovers the JMAP session: `GET https://${host}/.well-known/jmap` (the well-known URL
130
+ * 302-redirects to the session resource, which `fetch` follows). Yields `apiUrl` +
131
+ * `primaryAccounts`. Tracer disabled (Effect + CORS: https://github.com/Effect-TS/effect/issues/4568).
132
+ */
133
+ var getSession = Effect.gen(function* () {
134
+ const { host, token } = yield* JmapCredentials;
135
+ const httpClientWithTracerDisabled = (yield* HttpClient.HttpClient.pipe(Effect.map(withAuthorization(token, "Bearer")))).pipe(HttpClient.withTracerDisabledWhen(() => true));
136
+ return yield* HttpClientRequest.get(`https://${host}/.well-known/jmap`).pipe(HttpClientRequest.setHeader("accept", "application/json"), httpClientWithTracerDisabled.execute, Effect.flatMap(decodeJsonOrFail(Session)), Effect.timeout(REQUEST_TIMEOUT), Effect.retry({
137
+ schedule: REQUEST_RETRY,
138
+ while: shouldRetry
139
+ }), Effect.scoped, Effect.withSpan("JmapGetSession"), Effect.mapError(asJmapApiError));
140
+ });
141
+ /**
142
+ * Issues a batched JMAP API request (single POST to the session `apiUrl`) and decodes the response
143
+ * envelope. Read requests retry on transient failure; pass `{ retry: false }` for non-idempotent
144
+ * writes (draft create + submission) so a retry can't double-send.
145
+ */
146
+ var jmapRequest = (apiUrl, body, options = {}) => Effect.gen(function* () {
147
+ const { token } = yield* JmapCredentials;
148
+ const httpClientWithTracerDisabled = (yield* HttpClient.HttpClient.pipe(Effect.map(withAuthorization(token, "Bearer")))).pipe(HttpClient.withTracerDisabledWhen(() => true));
149
+ const executed = HttpClientRequest.post(apiUrl).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.bodyUnsafeJson(body), httpClientWithTracerDisabled.execute, Effect.flatMap(decodeJsonOrFail(Response)), Effect.timeout(REQUEST_TIMEOUT));
150
+ return yield* (options.retry === false ? executed : executed.pipe(Effect.retry({
151
+ schedule: REQUEST_RETRY,
152
+ while: shouldRetry
153
+ }))).pipe(Effect.scoped, Effect.withSpan("JmapApiRequest"), Effect.mapError(asJmapApiError));
154
+ });
155
+ /**
156
+ * Extracts and decodes the result for a given `callId` from a response envelope. A method-level
157
+ * `["error", …]` response (or a decode failure) becomes a typed {@link JmapApiError}.
158
+ */
159
+ var getMethodResponse = (response, callId, schema) => {
160
+ const entry = response.methodResponses.find((methodResponse) => methodResponse[2] === callId);
161
+ if (!entry) return Effect.fail(new JmapApiError(void 0, `No method response for call ${callId}.`));
162
+ const [name, args] = entry;
163
+ if (name === "error") return Schema.decodeUnknown(MethodError)(args).pipe(Effect.matchEffect({
164
+ onFailure: () => Effect.fail(new JmapApiError(void 0, `JMAP method error (${callId}).`)),
165
+ onSuccess: (error) => Effect.fail(new JmapApiError(void 0, error.description ?? error.type, error.type))
166
+ }));
167
+ return Schema.decodeUnknown(schema)(args).pipe(Effect.mapError(asJmapApiError));
168
+ };
169
+ /** Decodes a 2xx JSON body against `schema`; a >=400 status fails with the (truncated) body text. */
170
+ var decodeJsonOrFail = (schema) => (response) => Effect.gen(function* () {
171
+ if (response.status >= 400) {
172
+ const body = yield* response.text;
173
+ return yield* Effect.fail(new JmapApiError(response.status, truncate(body)));
174
+ }
175
+ return yield* HttpClientResponse.schemaBodyJson(schema)(response);
176
+ }).pipe(Effect.mapError(asJmapApiError));
177
+ /** Collapses transport/decode failures (HttpClientError, ParseError, timeout) into a typed JmapApiError. */
178
+ var asJmapApiError = (error) => error instanceof JmapApiError ? error : new JmapApiError(void 0, error instanceof Error ? error.message : String(error));
179
+ var truncate = (text, max = 500) => text.length > max ? `${text.slice(0, max)}…` : text;
180
+ //#endregion
181
+ //#region src/apis/Jmap/index.ts
182
+ var Jmap_exports = /* @__PURE__ */ __exportAll({
183
+ MAIL_CAPABILITIES: () => MAIL_CAPABILITIES,
184
+ MethodError: () => MethodError,
185
+ MethodResponse: () => MethodResponse,
186
+ REQUEST_RETRY: () => REQUEST_RETRY,
187
+ REQUEST_TIMEOUT: () => REQUEST_TIMEOUT,
188
+ Response: () => Response,
189
+ SUBMISSION_CAPABILITIES: () => SUBMISSION_CAPABILITIES,
190
+ Session: () => Session,
191
+ getMethodResponse: () => getMethodResponse,
192
+ getSession: () => getSession,
193
+ jmapRequest: () => jmapRequest,
194
+ shouldRetry: () => shouldRetry
195
+ });
196
+ //#endregion
197
+ //#region src/apis/JmapMail/types.ts
198
+ /** Structured email address — JMAP gives `{ name, email }` directly (no header parsing). */
199
+ var EmailAddress = Schema.Struct({
200
+ name: Schema.optional(Schema.NullOr(Schema.String)),
201
+ email: Schema.String
202
+ });
203
+ /** A folder. `role` is a well-known role (`inbox`, `drafts`, `sent`, `trash`, …) or null for custom folders. */
204
+ var Mailbox = Schema.Struct({
205
+ id: Schema.String,
206
+ name: Schema.String,
207
+ role: Schema.optional(Schema.NullOr(Schema.String)),
208
+ parentId: Schema.optional(Schema.NullOr(Schema.String)),
209
+ sortOrder: Schema.optional(Schema.Number),
210
+ totalEmails: Schema.optional(Schema.Number),
211
+ unreadEmails: Schema.optional(Schema.Number)
212
+ });
213
+ /** A decoded body part value (when fetched with `fetchTextBodyValues`). */
214
+ var BodyValue = Schema.Struct({
215
+ value: Schema.String,
216
+ isEncodingProblem: Schema.optional(Schema.Boolean),
217
+ isTruncated: Schema.optional(Schema.Boolean)
218
+ });
219
+ /** A body part reference (the `partId` keys into `Email.bodyValues`; `blobId` fetches raw bytes). */
220
+ var EmailBodyPart = Schema.Struct({
221
+ partId: Schema.optional(Schema.NullOr(Schema.String)),
222
+ blobId: Schema.optional(Schema.NullOr(Schema.String)),
223
+ size: Schema.optional(Schema.Number),
224
+ type: Schema.optional(Schema.String),
225
+ charset: Schema.optional(Schema.NullOr(Schema.String)),
226
+ /** Attachment filename, present on parts listed in `Email.attachments`. */
227
+ name: Schema.optional(Schema.NullOr(Schema.String)),
228
+ /** MIME `Content-Disposition` (`attachment` or `inline`), present on parts listed in `Email.attachments`. */
229
+ disposition: Schema.optional(Schema.NullOr(Schema.String)),
230
+ /** The part's `Content-ID` header (RFC 8621 §4.1.4), if any — matches a `cid:` reference in an HTML body. */
231
+ cid: Schema.optional(Schema.NullOr(Schema.String))
232
+ });
233
+ /** An email (RFC 8621 §4). Only the properties requested in `Email/get` are present. */
234
+ var Email = Schema.Struct({
235
+ id: Schema.String,
236
+ blobId: Schema.optional(Schema.String),
237
+ threadId: Schema.String,
238
+ mailboxIds: Schema.optional(Schema.Record({
239
+ key: Schema.String,
240
+ value: Schema.Boolean
241
+ })),
242
+ keywords: Schema.optional(Schema.Record({
243
+ key: Schema.String,
244
+ value: Schema.Boolean
245
+ })),
246
+ from: Schema.optional(Schema.NullOr(Schema.Array(EmailAddress))),
247
+ to: Schema.optional(Schema.NullOr(Schema.Array(EmailAddress))),
248
+ cc: Schema.optional(Schema.NullOr(Schema.Array(EmailAddress))),
249
+ bcc: Schema.optional(Schema.NullOr(Schema.Array(EmailAddress))),
250
+ subject: Schema.optional(Schema.NullOr(Schema.String)),
251
+ receivedAt: Schema.String,
252
+ sentAt: Schema.optional(Schema.NullOr(Schema.String)),
253
+ preview: Schema.optional(Schema.NullOr(Schema.String)),
254
+ messageId: Schema.optional(Schema.NullOr(Schema.Array(Schema.String))),
255
+ inReplyTo: Schema.optional(Schema.NullOr(Schema.Array(Schema.String))),
256
+ references: Schema.optional(Schema.NullOr(Schema.Array(Schema.String))),
257
+ bodyValues: Schema.optional(Schema.Record({
258
+ key: Schema.String,
259
+ value: BodyValue
260
+ })),
261
+ textBody: Schema.optional(Schema.Array(EmailBodyPart)),
262
+ htmlBody: Schema.optional(Schema.Array(EmailBodyPart)),
263
+ /** Non-body parts (RFC 8621 §4.1.4) — the attachments proper, distinct from `textBody`/`htmlBody`. */
264
+ attachments: Schema.optional(Schema.Array(EmailBodyPart))
265
+ });
266
+ /** A sending identity (RFC 8621 §6); `email` is an address the server permits as `from`. */
267
+ var Identity = Schema.Struct({
268
+ id: Schema.String,
269
+ name: Schema.optional(Schema.NullOr(Schema.String)),
270
+ email: Schema.String
271
+ });
272
+ var MailboxGetResult = Schema.Struct({
273
+ accountId: Schema.optional(Schema.String),
274
+ state: Schema.optional(Schema.String),
275
+ list: Schema.Array(Mailbox),
276
+ notFound: Schema.optional(Schema.NullOr(Schema.Array(Schema.String)))
277
+ });
278
+ var EmailQueryResult = Schema.Struct({
279
+ accountId: Schema.optional(Schema.String),
280
+ queryState: Schema.optional(Schema.String),
281
+ position: Schema.optional(Schema.Number),
282
+ total: Schema.optional(Schema.Number),
283
+ limit: Schema.optional(Schema.Number),
284
+ ids: Schema.Array(Schema.String)
285
+ });
286
+ var EmailGetResult = Schema.Struct({
287
+ accountId: Schema.optional(Schema.String),
288
+ state: Schema.optional(Schema.String),
289
+ list: Schema.Array(Email),
290
+ notFound: Schema.optional(Schema.NullOr(Schema.Array(Schema.String)))
291
+ });
292
+ /** `Email/changes` delta since an opaque `sinceState` token (RFC 8621 §4.3 / RFC 8620 §5.2). */
293
+ var EmailChangesResult = Schema.Struct({
294
+ accountId: Schema.optional(Schema.String),
295
+ oldState: Schema.String,
296
+ newState: Schema.String,
297
+ hasMoreChanges: Schema.Boolean,
298
+ created: Schema.Array(Schema.String),
299
+ updated: Schema.Array(Schema.String),
300
+ destroyed: Schema.Array(Schema.String)
301
+ });
302
+ var IdentityGetResult = Schema.Struct({
303
+ accountId: Schema.optional(Schema.String),
304
+ state: Schema.optional(Schema.String),
305
+ list: Schema.Array(Identity),
306
+ notFound: Schema.optional(Schema.NullOr(Schema.Array(Schema.String)))
307
+ });
308
+ var EmailSetResult = Schema.Struct({
309
+ accountId: Schema.optional(Schema.String),
310
+ oldState: Schema.optional(Schema.NullOr(Schema.String)),
311
+ newState: Schema.optional(Schema.String),
312
+ created: Schema.optional(Schema.NullOr(Schema.Record({
313
+ key: Schema.String,
314
+ value: Schema.Struct({
315
+ id: Schema.String,
316
+ blobId: Schema.optional(Schema.String),
317
+ threadId: Schema.optional(Schema.String),
318
+ size: Schema.optional(Schema.Number)
319
+ })
320
+ }))),
321
+ updated: Schema.optional(Schema.NullOr(Schema.Record({
322
+ key: Schema.String,
323
+ value: Schema.Unknown
324
+ }))),
325
+ destroyed: Schema.optional(Schema.NullOr(Schema.Array(Schema.String))),
326
+ notCreated: Schema.optional(Schema.NullOr(Schema.Record({
327
+ key: Schema.String,
328
+ value: Schema.Struct({
329
+ type: Schema.String,
330
+ description: Schema.optional(Schema.NullOr(Schema.String))
331
+ })
332
+ }))),
333
+ notUpdated: Schema.optional(Schema.NullOr(Schema.Record({
334
+ key: Schema.String,
335
+ value: Schema.Struct({
336
+ type: Schema.String,
337
+ description: Schema.optional(Schema.NullOr(Schema.String))
338
+ })
339
+ }))),
340
+ notDestroyed: Schema.optional(Schema.NullOr(Schema.Record({
341
+ key: Schema.String,
342
+ value: Schema.Struct({
343
+ type: Schema.String,
344
+ description: Schema.optional(Schema.NullOr(Schema.String))
345
+ })
346
+ })))
347
+ });
348
+ var EmailSubmissionSetResult = Schema.Struct({
349
+ accountId: Schema.optional(Schema.String),
350
+ newState: Schema.optional(Schema.String),
351
+ created: Schema.optional(Schema.NullOr(Schema.Record({
352
+ key: Schema.String,
353
+ value: Schema.Struct({ id: Schema.String })
354
+ }))),
355
+ notCreated: Schema.optional(Schema.NullOr(Schema.Record({
356
+ key: Schema.String,
357
+ value: Schema.Struct({
358
+ type: Schema.String,
359
+ description: Schema.optional(Schema.NullOr(Schema.String))
360
+ })
361
+ })))
362
+ });
363
+ //#endregion
364
+ //#region src/apis/JmapMail/api.ts
365
+ /** Expands a JMAP `downloadUrl` URI Template (RFC 6570 level 1) with the given variables. */
366
+ var expandDownloadUrl = (template, variables) => template.replace(/\{(\w+)\}/g, (_, key) => key in variables ? encodeURIComponent(variables[key]) : "");
367
+ /**
368
+ * Downloads a blob's raw bytes via the session's `downloadUrl` template. `name`/`type` are advisory
369
+ * (they only affect the response's `Content-Disposition`/`Accept`), not required for correctness.
370
+ */
371
+ var downloadBlob = Effect.fn("downloadBlob")(function* (target, blobId, options = {}) {
372
+ if (!target.downloadUrl) return yield* Effect.fail(new JmapApiError(void 0, "Session has no downloadUrl."));
373
+ const url = expandDownloadUrl(target.downloadUrl, {
374
+ accountId: target.accountId,
375
+ blobId,
376
+ name: options.name ?? blobId,
377
+ type: options.type ?? "application/octet-stream"
378
+ });
379
+ const { token } = yield* JmapCredentials;
380
+ const httpClient = yield* HttpClient.HttpClient.pipe(Effect.map(withAuthorization(token, "Bearer")));
381
+ const buffer = yield* HttpClientRequest.get(url).pipe(httpClient.execute, Effect.flatMap((response) => response.arrayBuffer), Effect.mapError(asJmapDownloadError), Effect.timeout(REQUEST_TIMEOUT), Effect.retry({
382
+ schedule: REQUEST_RETRY,
383
+ while: shouldRetry
384
+ }), Effect.scoped, Effect.mapError(asJmapDownloadError));
385
+ return new Uint8Array(buffer);
386
+ });
387
+ /** Collapses transport/decode failures into a typed {@link JmapApiError} (mirrors `Jmap/api.ts`). */
388
+ var asJmapDownloadError = (error) => error instanceof JmapApiError ? error : new JmapApiError(void 0, error instanceof Error ? error.message : String(error));
389
+ /** Email properties fetched by {@link emailGet} — enough for the mapper to build a Message. */
390
+ var EMAIL_PROPERTIES = [
391
+ "id",
392
+ "blobId",
393
+ "threadId",
394
+ "mailboxIds",
395
+ "keywords",
396
+ "from",
397
+ "to",
398
+ "cc",
399
+ "subject",
400
+ "receivedAt",
401
+ "sentAt",
402
+ "preview",
403
+ "messageId",
404
+ "inReplyTo",
405
+ "references",
406
+ "bodyValues",
407
+ "textBody",
408
+ "htmlBody",
409
+ "attachments"
410
+ ];
411
+ /** Lists all folders (mailboxes) in the account (RFC 8621 §2.3). */
412
+ var mailboxGet = Effect.fn("mailboxGet")(function* (target) {
413
+ return yield* getMethodResponse(yield* jmapRequest(target.apiUrl, {
414
+ using: MAIL_CAPABILITIES,
415
+ methodCalls: [[
416
+ "Mailbox/get",
417
+ {
418
+ accountId: target.accountId,
419
+ ids: null
420
+ },
421
+ "0"
422
+ ]]
423
+ }), "0", MailboxGetResult);
424
+ });
425
+ /** Queries email ids matching a filter (RFC 8621 §4.4). */
426
+ var emailQuery = Effect.fn("emailQuery")(function* (target, options = {}) {
427
+ const args = { accountId: target.accountId };
428
+ if (options.filter !== void 0) args.filter = options.filter;
429
+ if (options.sort !== void 0) args.sort = options.sort;
430
+ if (options.position !== void 0) args.position = options.position;
431
+ if (options.limit !== void 0) args.limit = options.limit;
432
+ if (options.calculateTotal !== void 0) args.calculateTotal = options.calculateTotal;
433
+ return yield* getMethodResponse(yield* jmapRequest(target.apiUrl, {
434
+ using: MAIL_CAPABILITIES,
435
+ methodCalls: [[
436
+ "Email/query",
437
+ args,
438
+ "0"
439
+ ]]
440
+ }), "0", EmailQueryResult);
441
+ });
442
+ /** Fetches emails by id with decoded HTML and text body values (RFC 8621 §4.2). */
443
+ var emailGet = Effect.fn("emailGet")(function* (target, ids, properties = EMAIL_PROPERTIES) {
444
+ return yield* getMethodResponse(yield* jmapRequest(target.apiUrl, {
445
+ using: MAIL_CAPABILITIES,
446
+ methodCalls: [[
447
+ "Email/get",
448
+ {
449
+ accountId: target.accountId,
450
+ ids,
451
+ properties,
452
+ fetchTextBodyValues: true,
453
+ fetchHTMLBodyValues: true
454
+ },
455
+ "0"
456
+ ]]
457
+ }), "0", EmailGetResult);
458
+ });
459
+ /**
460
+ * Fetches the delta of email changes since an opaque `sinceState` token (RFC 8621 §4.3). A server that
461
+ * can't compute the delta (state too old / evicted) returns a `cannotCalculateChanges` method error,
462
+ * surfaced as a {@link JmapApiError} with that `type` — the caller's cue to fall back to a full scan.
463
+ * The token to pass comes from a prior `Email/get` `state` (NOT `Email/query` `queryState`).
464
+ */
465
+ var emailChanges = Effect.fn("emailChanges")(function* (target, sinceState, maxChanges) {
466
+ const args = {
467
+ accountId: target.accountId,
468
+ sinceState
469
+ };
470
+ if (maxChanges !== void 0) args.maxChanges = maxChanges;
471
+ return yield* getMethodResponse(yield* jmapRequest(target.apiUrl, {
472
+ using: MAIL_CAPABILITIES,
473
+ methodCalls: [[
474
+ "Email/changes",
475
+ args,
476
+ "0"
477
+ ]]
478
+ }), "0", EmailChangesResult);
479
+ });
480
+ /** Lists the account's sending identities (RFC 8621 §6.2). */
481
+ var identityGet = Effect.fn("identityGet")(function* (target) {
482
+ return yield* getMethodResponse(yield* jmapRequest(target.apiUrl, {
483
+ using: SUBMISSION_CAPABILITIES,
484
+ methodCalls: [[
485
+ "Identity/get",
486
+ {
487
+ accountId: target.accountId,
488
+ ids: null
489
+ },
490
+ "0"
491
+ ]]
492
+ }), "0", IdentityGetResult);
493
+ });
494
+ /** Patches a single email (e.g. replace `mailboxIds` to move to Trash). Idempotent, so retryable. */
495
+ var emailSetUpdate = Effect.fn("emailSetUpdate")(function* (target, emailId, patch) {
496
+ const result = yield* getMethodResponse(yield* jmapRequest(target.apiUrl, {
497
+ using: MAIL_CAPABILITIES,
498
+ methodCalls: [[
499
+ "Email/set",
500
+ {
501
+ accountId: target.accountId,
502
+ update: { [emailId]: patch }
503
+ },
504
+ "0"
505
+ ]]
506
+ }), "0", EmailSetResult);
507
+ const error = result.notUpdated?.[emailId];
508
+ if (error) return yield* Effect.fail(new JmapApiError(void 0, error.description ?? error.type, error.type));
509
+ return result;
510
+ });
511
+ /**
512
+ * Creates a draft and submits it for delivery in a single batched request. Not retried (a retry
513
+ * could double-send). Returns the created email's id and thread id.
514
+ */
515
+ var submitEmail = Effect.fn("submitEmail")(function* (target, args) {
516
+ const { identityId, draftsMailboxId, sentMailboxId, draft } = args;
517
+ const draftCreate = {
518
+ mailboxIds: { [draftsMailboxId]: true },
519
+ keywords: {
520
+ $draft: true,
521
+ $seen: true
522
+ },
523
+ from: draft.from,
524
+ to: draft.to,
525
+ bodyValues: { body: { value: draft.text } },
526
+ textBody: [{
527
+ partId: "body",
528
+ type: "text/plain"
529
+ }]
530
+ };
531
+ if (draft.cc && draft.cc.length > 0) draftCreate.cc = draft.cc;
532
+ if (draft.bcc && draft.bcc.length > 0) draftCreate.bcc = draft.bcc;
533
+ if (draft.subject !== void 0) draftCreate.subject = draft.subject;
534
+ if (draft.inReplyTo && draft.inReplyTo.length > 0) draftCreate.inReplyTo = draft.inReplyTo;
535
+ if (draft.references && draft.references.length > 0) draftCreate.references = draft.references;
536
+ const response = yield* jmapRequest(target.apiUrl, {
537
+ using: SUBMISSION_CAPABILITIES,
538
+ methodCalls: [[
539
+ "Email/set",
540
+ {
541
+ accountId: target.accountId,
542
+ create: { draft: draftCreate }
543
+ },
544
+ "0"
545
+ ], [
546
+ "EmailSubmission/set",
547
+ {
548
+ accountId: target.accountId,
549
+ create: { sub: {
550
+ emailId: "#draft",
551
+ identityId
552
+ } },
553
+ onSuccessUpdateEmail: { "#sub": {
554
+ [`mailboxIds/${draftsMailboxId}`]: null,
555
+ [`mailboxIds/${sentMailboxId}`]: true,
556
+ "keywords/$draft": null
557
+ } }
558
+ },
559
+ "1"
560
+ ]]
561
+ }, { retry: false });
562
+ const setResult = yield* getMethodResponse(response, "0", EmailSetResult);
563
+ const created = setResult.created?.draft;
564
+ if (!created) {
565
+ const error = setResult.notCreated?.draft;
566
+ return yield* Effect.fail(new JmapApiError(void 0, error?.description ?? error?.type ?? "Draft was not created.", error?.type));
567
+ }
568
+ const submissionResult = yield* getMethodResponse(response, "1", EmailSubmissionSetResult);
569
+ if (!submissionResult.created?.sub) {
570
+ const error = submissionResult.notCreated?.sub;
571
+ return yield* Effect.fail(new JmapApiError(void 0, error?.description ?? error?.type ?? "Email submission failed.", error?.type));
572
+ }
573
+ return {
574
+ id: created.id,
575
+ threadId: created.threadId
576
+ };
577
+ });
578
+ //#endregion
579
+ //#region src/apis/JmapMail/query.ts
580
+ /**
581
+ * Translates a Gmail-like query string into a JMAP `Email/query` filter (RFC 8621 §4.4.1).
582
+ *
583
+ * Supported operators: `from:`/`to:`/`cc:`/`bcc:`/`subject:`/`body:`, `label:`/`in:` (resolved to a
584
+ * mailbox id), `is:unread|read|starred|flagged|unstarred|draft|answered`, `has:attachment`,
585
+ * `after:`/`before:` (`YYYY/MM/DD` or `YYYY-MM-DD`), `newer_than:`/`older_than:` (e.g. `7d`, `2w`,
586
+ * `3m`, `1y`), `larger:`/`smaller:` (e.g. `10M`, `500k`), `list:`/`deliveredto:` (header match),
587
+ * quoted phrases, `-` negation, `OR` groups, and bare words (full-text). Space-separated terms are
588
+ * AND-ed. Returns `None` for an empty query, or one whose terms all fail to resolve (e.g. an unknown
589
+ * label).
590
+ */
591
+ var parseMailQuery = (query, ctx) => buildFilter(tokenize(query), ctx);
592
+ /** Resolves a label/folder name (case-insensitive) or a system role/alias to a mailbox id. */
593
+ var resolveMailboxByNameOrRole = (folders, nameOrRole) => {
594
+ const lowered = nameOrRole.toLowerCase();
595
+ const role = ROLE_ALIASES[lowered] ?? lowered;
596
+ const match = folders.find((folder) => folder.role === role) ?? folders.find((folder) => folder.name.toLowerCase() === lowered);
597
+ return Option.fromNullable(match?.id);
598
+ };
599
+ var TOKEN_RE = /-?(?:[A-Za-z_]+:)?"[^"]*"|\S+/g;
600
+ var tokenize = (query) => (query.match(TOKEN_RE) ?? []).map(parseToken);
601
+ var parseToken = (raw) => {
602
+ if (raw === "OR") return { _tag: "or" };
603
+ const negate = raw.startsWith("-");
604
+ const rest = negate ? raw.slice(1) : raw;
605
+ const colon = rest.indexOf(":");
606
+ if (colon > 0 && /^[A-Za-z_]+$/.test(rest.slice(0, colon))) return {
607
+ _tag: "term",
608
+ negate,
609
+ op: Option.some(rest.slice(0, colon).toLowerCase()),
610
+ value: stripQuotes(rest.slice(colon + 1))
611
+ };
612
+ return {
613
+ _tag: "term",
614
+ negate,
615
+ op: Option.none(),
616
+ value: stripQuotes(rest)
617
+ };
618
+ };
619
+ var stripQuotes = (value) => value.length >= 2 && value.startsWith("\"") && value.endsWith("\"") ? value.slice(1, -1) : value;
620
+ var buildFilter = (tokens, ctx) => {
621
+ const { operands } = tokens.reduce((state, token) => {
622
+ if (token._tag === "or") return {
623
+ ...state,
624
+ pendingOr: state.operands.length > 0
625
+ };
626
+ const leaf = buildTerm(token, ctx);
627
+ if (Option.isNone(leaf)) return {
628
+ ...state,
629
+ pendingOr: false
630
+ };
631
+ const previous = state.pendingOr ? state.operands[state.operands.length - 1] : void 0;
632
+ return previous !== void 0 ? {
633
+ operands: [...state.operands.slice(0, -1), mergeOr(previous, leaf.value)],
634
+ pendingOr: false
635
+ } : {
636
+ operands: [...state.operands, leaf.value],
637
+ pendingOr: false
638
+ };
639
+ }, {
640
+ operands: [],
641
+ pendingOr: false
642
+ });
643
+ return Match.value(operands.length).pipe(Match.when(0, () => Option.none()), Match.when(1, () => Option.fromNullable(operands[0])), Match.orElse(() => Option.some({
644
+ operator: "AND",
645
+ conditions: operands
646
+ })));
647
+ };
648
+ var mergeOr = (previous, next) => isOperator(previous) && previous.operator === "OR" ? {
649
+ operator: "OR",
650
+ conditions: [...previous.conditions, next]
651
+ } : {
652
+ operator: "OR",
653
+ conditions: [previous, next]
654
+ };
655
+ var isOperator = (filter) => "operator" in filter;
656
+ /** Whether a filter restricts the mailbox scope (`inMailbox`/`inMailboxOtherThan`), recursively. */
657
+ var filterScopesMailbox = (filter) => isOperator(filter) ? filter.conditions.some(filterScopesMailbox) : filter.inMailbox !== void 0 || filter.inMailboxOtherThan !== void 0;
658
+ var buildTerm = (token, ctx) => buildLeaf(token.op, token.value, ctx).pipe(Option.map((leaf) => token.negate ? negate(leaf) : leaf));
659
+ var negate = (filter) => ({
660
+ operator: "NOT",
661
+ conditions: [filter]
662
+ });
663
+ var buildLeaf = (op, value, ctx) => op.pipe(Option.match({
664
+ onNone: () => textLeaf(value),
665
+ onSome: (operator) => buildOperatorLeaf(operator, value, ctx)
666
+ }));
667
+ var buildOperatorLeaf = (operator, value, ctx) => Match.value(operator).pipe(Match.whenOr("from", "to", "cc", "bcc", "subject", "body", (field) => condLeaf(value, STRING_FIELD_LEAVES[field](value))), Match.whenOr("label", "in", () => ctx.resolveMailbox(value).pipe(Option.map((id) => ({ inMailbox: id })))), Match.when("is", () => Option.fromNullable(IS_LEAVES[value.toLowerCase()])), Match.when("has", () => value.toLowerCase() === "attachment" ? Option.some({ hasAttachment: true }) : Option.none()), Match.whenOr("after", "newer", () => dateLeaf(value, "after")), Match.whenOr("before", "older", () => dateLeaf(value, "before")), Match.when("newer_than", () => relativeDateLeaf(value, ctx.now, "after")), Match.when("older_than", () => relativeDateLeaf(value, ctx.now, "before")), Match.when("larger", () => sizeLeaf(value, "minSize")), Match.when("smaller", () => sizeLeaf(value, "maxSize")), Match.when("list", () => headerLeaf("List-Id", value)), Match.when("deliveredto", () => headerLeaf("Delivered-To", value)), Match.orElse(() => textLeaf(`${operator}:${value}`)));
668
+ var STRING_FIELD_LEAVES = {
669
+ from: (value) => ({ from: value }),
670
+ to: (value) => ({ to: value }),
671
+ cc: (value) => ({ cc: value }),
672
+ bcc: (value) => ({ bcc: value }),
673
+ subject: (value) => ({ subject: value }),
674
+ body: (value) => ({ body: value })
675
+ };
676
+ var IS_LEAVES = {
677
+ unread: { notKeyword: "$seen" },
678
+ read: { hasKeyword: "$seen" },
679
+ starred: { hasKeyword: "$flagged" },
680
+ flagged: { hasKeyword: "$flagged" },
681
+ unstarred: { notKeyword: "$flagged" },
682
+ unflagged: { notKeyword: "$flagged" },
683
+ draft: { hasKeyword: "$draft" },
684
+ answered: { hasKeyword: "$answered" },
685
+ replied: { hasKeyword: "$answered" }
686
+ };
687
+ var ROLE_ALIASES = {
688
+ spam: "junk",
689
+ bin: "trash",
690
+ deleted: "trash"
691
+ };
692
+ var SIZE_UNITS = {
693
+ "": 1,
694
+ "k": 1024,
695
+ "m": 1024 * 1024,
696
+ "g": 1024 * 1024 * 1024
697
+ };
698
+ var DATE_RE = /^(\d{4})[/-](\d{1,2})[/-](\d{1,2})$/;
699
+ var RELATIVE_RE = /^(\d+)([dwmy])$/;
700
+ var SIZE_RE = /^(\d+)([kmg]?)b?$/i;
701
+ /** Wraps a built condition in `Some`, or `None` when the value is blank. */
702
+ var condLeaf = (value, condition) => value.trim().length === 0 ? Option.none() : Option.some(condition);
703
+ var textLeaf = (value) => condLeaf(value, { text: value });
704
+ var headerLeaf = (name, value) => condLeaf(value, { header: [name, value] });
705
+ var dateLeaf = (value, bound) => {
706
+ const match = DATE_RE.exec(value);
707
+ if (!match) return Option.none();
708
+ const iso = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))).toISOString();
709
+ return Option.some(bound === "after" ? { after: iso } : { before: iso });
710
+ };
711
+ var relativeDateLeaf = (value, now, bound) => {
712
+ const match = RELATIVE_RE.exec(value.toLowerCase());
713
+ if (!match) return Option.none();
714
+ const amount = Number(match[1]);
715
+ const iso = Match.value(match[2]).pipe(Match.when("w", () => subWeeks(now, amount)), Match.when("m", () => subMonths(now, amount)), Match.when("y", () => subYears(now, amount)), Match.orElse(() => subDays(now, amount))).toISOString();
716
+ return Option.some(bound === "after" ? { after: iso } : { before: iso });
717
+ };
718
+ var sizeLeaf = (value, bound) => {
719
+ const match = SIZE_RE.exec(value.toLowerCase());
720
+ if (!match) return Option.none();
721
+ const bytes = Number(match[1]) * (SIZE_UNITS[match[2] ?? ""] ?? 1);
722
+ return Option.some(bound === "minSize" ? { minSize: bytes } : { maxSize: bytes });
723
+ };
724
+ //#endregion
725
+ //#region src/apis/JmapMail/index.ts
726
+ var JmapMail_exports = /* @__PURE__ */ __exportAll({
727
+ BodyValue: () => BodyValue,
728
+ EMAIL_PROPERTIES: () => EMAIL_PROPERTIES,
729
+ Email: () => Email,
730
+ EmailAddress: () => EmailAddress,
731
+ EmailBodyPart: () => EmailBodyPart,
732
+ EmailChangesResult: () => EmailChangesResult,
733
+ EmailGetResult: () => EmailGetResult,
734
+ EmailQueryResult: () => EmailQueryResult,
735
+ EmailSetResult: () => EmailSetResult,
736
+ EmailSubmissionSetResult: () => EmailSubmissionSetResult,
737
+ Identity: () => Identity,
738
+ IdentityGetResult: () => IdentityGetResult,
739
+ Mailbox: () => Mailbox,
740
+ MailboxGetResult: () => MailboxGetResult,
741
+ downloadBlob: () => downloadBlob,
742
+ emailChanges: () => emailChanges,
743
+ emailGet: () => emailGet,
744
+ emailQuery: () => emailQuery,
745
+ emailSetUpdate: () => emailSetUpdate,
746
+ filterScopesMailbox: () => filterScopesMailbox,
747
+ identityGet: () => identityGet,
748
+ mailboxGet: () => mailboxGet,
749
+ parseMailQuery: () => parseMailQuery,
750
+ resolveMailboxByNameOrRole: () => resolveMailboxByNameOrRole,
751
+ submitEmail: () => submitEmail
752
+ });
753
+ //#endregion
754
+ export { downloadBlob as a, emailQuery as c, mailboxGet as d, submitEmail as f, JmapCredentials as h, resolveMailboxByNameOrRole as i, emailSetUpdate as l, getSession as m, filterScopesMailbox as n, emailChanges as o, Jmap_exports as p, parseMailQuery as r, emailGet as s, JmapMail_exports as t, identityGet as u };
755
+
756
+ //# sourceMappingURL=chunk-apis.mjs.map