@yolk-sdk/emulators 0.1.0-canary.96

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 (333) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2135 -0
  3. package/dist/anthropic.d.mts +269 -0
  4. package/dist/anthropic.d.mts.map +1 -0
  5. package/dist/anthropic.mjs +177 -0
  6. package/dist/anthropic.mjs.map +1 -0
  7. package/dist/chat-completions.d.mts +288 -0
  8. package/dist/chat-completions.d.mts.map +1 -0
  9. package/dist/chat-completions.mjs +451 -0
  10. package/dist/chat-completions.mjs.map +1 -0
  11. package/dist/codex.d.mts +265 -0
  12. package/dist/codex.d.mts.map +1 -0
  13. package/dist/codex.mjs +199 -0
  14. package/dist/codex.mjs.map +1 -0
  15. package/dist/dropbox/api.d.mts +63 -0
  16. package/dist/dropbox/api.d.mts.map +1 -0
  17. package/dist/dropbox/api.mjs +565 -0
  18. package/dist/dropbox/api.mjs.map +1 -0
  19. package/dist/dropbox/state.d.mts +136 -0
  20. package/dist/dropbox/state.d.mts.map +1 -0
  21. package/dist/dropbox/state.mjs +209 -0
  22. package/dist/dropbox/state.mjs.map +1 -0
  23. package/dist/dropbox.d.mts +79 -0
  24. package/dist/dropbox.d.mts.map +1 -0
  25. package/dist/dropbox.mjs +124 -0
  26. package/dist/dropbox.mjs.map +1 -0
  27. package/dist/email-fixtures.d.mts +36 -0
  28. package/dist/email-fixtures.d.mts.map +1 -0
  29. package/dist/email-fixtures.mjs +1080 -0
  30. package/dist/email-fixtures.mjs.map +1 -0
  31. package/dist/email.d.mts +160 -0
  32. package/dist/email.d.mts.map +1 -0
  33. package/dist/email.mjs +608 -0
  34. package/dist/email.mjs.map +1 -0
  35. package/dist/emulator-compose.d.mts +40 -0
  36. package/dist/emulator-compose.d.mts.map +1 -0
  37. package/dist/emulator-compose.mjs +91 -0
  38. package/dist/emulator-compose.mjs.map +1 -0
  39. package/dist/emulator-http.d.mts +53 -0
  40. package/dist/emulator-http.d.mts.map +1 -0
  41. package/dist/emulator-http.mjs +117 -0
  42. package/dist/emulator-http.mjs.map +1 -0
  43. package/dist/emulator-kernel.d.mts +176 -0
  44. package/dist/emulator-kernel.d.mts.map +1 -0
  45. package/dist/emulator-kernel.mjs +413 -0
  46. package/dist/emulator-kernel.mjs.map +1 -0
  47. package/dist/fixture-route.d.mts +129 -0
  48. package/dist/fixture-route.d.mts.map +1 -0
  49. package/dist/fixture-route.mjs +257 -0
  50. package/dist/fixture-route.mjs.map +1 -0
  51. package/dist/fortnox/api.d.mts +92 -0
  52. package/dist/fortnox/api.d.mts.map +1 -0
  53. package/dist/fortnox/api.mjs +750 -0
  54. package/dist/fortnox/api.mjs.map +1 -0
  55. package/dist/fortnox/state.d.mts +533 -0
  56. package/dist/fortnox/state.d.mts.map +1 -0
  57. package/dist/fortnox/state.mjs +612 -0
  58. package/dist/fortnox/state.mjs.map +1 -0
  59. package/dist/fortnox.d.mts +128 -0
  60. package/dist/fortnox.d.mts.map +1 -0
  61. package/dist/fortnox.mjs +403 -0
  62. package/dist/fortnox.mjs.map +1 -0
  63. package/dist/gateway-evaluate-recordings.d.mts +12 -0
  64. package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
  65. package/dist/gateway-evaluate-recordings.mjs +128 -0
  66. package/dist/gateway-evaluate-recordings.mjs.map +1 -0
  67. package/dist/gateway.d.mts +272 -0
  68. package/dist/gateway.d.mts.map +1 -0
  69. package/dist/gateway.mjs +400 -0
  70. package/dist/gateway.mjs.map +1 -0
  71. package/dist/github/api.d.mts +63 -0
  72. package/dist/github/api.d.mts.map +1 -0
  73. package/dist/github/api.mjs +527 -0
  74. package/dist/github/api.mjs.map +1 -0
  75. package/dist/github/state.d.mts +272 -0
  76. package/dist/github/state.d.mts.map +1 -0
  77. package/dist/github/state.mjs +303 -0
  78. package/dist/github/state.mjs.map +1 -0
  79. package/dist/github.d.mts +77 -0
  80. package/dist/github.d.mts.map +1 -0
  81. package/dist/github.mjs +180 -0
  82. package/dist/github.mjs.map +1 -0
  83. package/dist/google/calendar.d.mts +10 -0
  84. package/dist/google/calendar.d.mts.map +1 -0
  85. package/dist/google/calendar.mjs +252 -0
  86. package/dist/google/calendar.mjs.map +1 -0
  87. package/dist/google/drive.d.mts +8 -0
  88. package/dist/google/drive.d.mts.map +1 -0
  89. package/dist/google/drive.mjs +252 -0
  90. package/dist/google/drive.mjs.map +1 -0
  91. package/dist/google/gmail.d.mts +10 -0
  92. package/dist/google/gmail.d.mts.map +1 -0
  93. package/dist/google/gmail.mjs +591 -0
  94. package/dist/google/gmail.mjs.map +1 -0
  95. package/dist/google/shared.d.mts +107 -0
  96. package/dist/google/shared.d.mts.map +1 -0
  97. package/dist/google/shared.mjs +146 -0
  98. package/dist/google/shared.mjs.map +1 -0
  99. package/dist/google/state.d.mts +385 -0
  100. package/dist/google/state.d.mts.map +1 -0
  101. package/dist/google/state.mjs +496 -0
  102. package/dist/google/state.mjs.map +1 -0
  103. package/dist/google.d.mts +75 -0
  104. package/dist/google.d.mts.map +1 -0
  105. package/dist/google.mjs +210 -0
  106. package/dist/google.mjs.map +1 -0
  107. package/dist/linkedin-search/api.d.mts +45 -0
  108. package/dist/linkedin-search/api.d.mts.map +1 -0
  109. package/dist/linkedin-search/api.mjs +181 -0
  110. package/dist/linkedin-search/api.mjs.map +1 -0
  111. package/dist/linkedin-search/state.d.mts +123 -0
  112. package/dist/linkedin-search/state.d.mts.map +1 -0
  113. package/dist/linkedin-search/state.mjs +261 -0
  114. package/dist/linkedin-search/state.mjs.map +1 -0
  115. package/dist/linkedin-search.d.mts +69 -0
  116. package/dist/linkedin-search.d.mts.map +1 -0
  117. package/dist/linkedin-search.mjs +158 -0
  118. package/dist/linkedin-search.mjs.map +1 -0
  119. package/dist/mcp/api.d.mts +44 -0
  120. package/dist/mcp/api.d.mts.map +1 -0
  121. package/dist/mcp/api.mjs +557 -0
  122. package/dist/mcp/api.mjs.map +1 -0
  123. package/dist/mcp/recordings.d.mts +40 -0
  124. package/dist/mcp/recordings.d.mts.map +1 -0
  125. package/dist/mcp/recordings.mjs +2390 -0
  126. package/dist/mcp/recordings.mjs.map +1 -0
  127. package/dist/mcp/state.d.mts +71 -0
  128. package/dist/mcp/state.d.mts.map +1 -0
  129. package/dist/mcp/state.mjs +83 -0
  130. package/dist/mcp/state.mjs.map +1 -0
  131. package/dist/mcp.d.mts +84 -0
  132. package/dist/mcp.d.mts.map +1 -0
  133. package/dist/mcp.mjs +241 -0
  134. package/dist/mcp.mjs.map +1 -0
  135. package/dist/messages.d.mts +183 -0
  136. package/dist/messages.d.mts.map +1 -0
  137. package/dist/messages.mjs +532 -0
  138. package/dist/messages.mjs.map +1 -0
  139. package/dist/microsoft/api.d.mts +47 -0
  140. package/dist/microsoft/api.d.mts.map +1 -0
  141. package/dist/microsoft/api.mjs +178 -0
  142. package/dist/microsoft/api.mjs.map +1 -0
  143. package/dist/microsoft/calendar.d.mts +30 -0
  144. package/dist/microsoft/calendar.d.mts.map +1 -0
  145. package/dist/microsoft/calendar.mjs +271 -0
  146. package/dist/microsoft/calendar.mjs.map +1 -0
  147. package/dist/microsoft/drive.d.mts +36 -0
  148. package/dist/microsoft/drive.d.mts.map +1 -0
  149. package/dist/microsoft/drive.mjs +298 -0
  150. package/dist/microsoft/drive.mjs.map +1 -0
  151. package/dist/microsoft/graph.d.mts +198 -0
  152. package/dist/microsoft/graph.d.mts.map +1 -0
  153. package/dist/microsoft/graph.mjs +264 -0
  154. package/dist/microsoft/graph.mjs.map +1 -0
  155. package/dist/microsoft/mail.d.mts +48 -0
  156. package/dist/microsoft/mail.d.mts.map +1 -0
  157. package/dist/microsoft/mail.mjs +488 -0
  158. package/dist/microsoft/mail.mjs.map +1 -0
  159. package/dist/microsoft/state.d.mts +480 -0
  160. package/dist/microsoft/state.d.mts.map +1 -0
  161. package/dist/microsoft/state.mjs +625 -0
  162. package/dist/microsoft/state.mjs.map +1 -0
  163. package/dist/microsoft.d.mts +168 -0
  164. package/dist/microsoft.d.mts.map +1 -0
  165. package/dist/microsoft.mjs +483 -0
  166. package/dist/microsoft.mjs.map +1 -0
  167. package/dist/node.d.mts +47 -0
  168. package/dist/node.d.mts.map +1 -0
  169. package/dist/node.mjs +178 -0
  170. package/dist/node.mjs.map +1 -0
  171. package/dist/notion/api.d.mts +51 -0
  172. package/dist/notion/api.d.mts.map +1 -0
  173. package/dist/notion/api.mjs +507 -0
  174. package/dist/notion/api.mjs.map +1 -0
  175. package/dist/notion/state.d.mts +329 -0
  176. package/dist/notion/state.d.mts.map +1 -0
  177. package/dist/notion/state.mjs +432 -0
  178. package/dist/notion/state.mjs.map +1 -0
  179. package/dist/notion.d.mts +72 -0
  180. package/dist/notion.d.mts.map +1 -0
  181. package/dist/notion.mjs +127 -0
  182. package/dist/notion.mjs.map +1 -0
  183. package/dist/openai.d.mts +165 -0
  184. package/dist/openai.d.mts.map +1 -0
  185. package/dist/openai.mjs +141 -0
  186. package/dist/openai.mjs.map +1 -0
  187. package/dist/opencode-recordings.d.mts +7 -0
  188. package/dist/opencode-recordings.d.mts.map +1 -0
  189. package/dist/opencode-recordings.mjs +222 -0
  190. package/dist/opencode-recordings.mjs.map +1 -0
  191. package/dist/opencode.d.mts +70 -0
  192. package/dist/opencode.d.mts.map +1 -0
  193. package/dist/opencode.mjs +217 -0
  194. package/dist/opencode.mjs.map +1 -0
  195. package/dist/r2-fixtures.d.mts +36 -0
  196. package/dist/r2-fixtures.d.mts.map +1 -0
  197. package/dist/r2-fixtures.mjs +245 -0
  198. package/dist/r2-fixtures.mjs.map +1 -0
  199. package/dist/r2-guard.d.mts +43 -0
  200. package/dist/r2-guard.d.mts.map +1 -0
  201. package/dist/r2-guard.mjs +215 -0
  202. package/dist/r2-guard.mjs.map +1 -0
  203. package/dist/r2.d.mts +166 -0
  204. package/dist/r2.d.mts.map +1 -0
  205. package/dist/r2.mjs +517 -0
  206. package/dist/r2.mjs.map +1 -0
  207. package/dist/responses.d.mts +231 -0
  208. package/dist/responses.d.mts.map +1 -0
  209. package/dist/responses.mjs +556 -0
  210. package/dist/responses.mjs.map +1 -0
  211. package/dist/route-evidence.d.mts +45 -0
  212. package/dist/route-evidence.d.mts.map +1 -0
  213. package/dist/route-evidence.mjs +56 -0
  214. package/dist/route-evidence.mjs.map +1 -0
  215. package/dist/router.d.mts +93 -0
  216. package/dist/router.d.mts.map +1 -0
  217. package/dist/router.mjs +259 -0
  218. package/dist/router.mjs.map +1 -0
  219. package/dist/stateful-core.d.mts +17 -0
  220. package/dist/stateful-core.d.mts.map +1 -0
  221. package/dist/stateful-core.mjs +41 -0
  222. package/dist/stateful-core.mjs.map +1 -0
  223. package/dist/stateful-emulator.d.mts +565 -0
  224. package/dist/stateful-emulator.d.mts.map +1 -0
  225. package/dist/stateful-emulator.mjs +1228 -0
  226. package/dist/stateful-emulator.mjs.map +1 -0
  227. package/dist/stateful-secrets.d.mts +84 -0
  228. package/dist/stateful-secrets.d.mts.map +1 -0
  229. package/dist/stateful-secrets.mjs +216 -0
  230. package/dist/stateful-secrets.mjs.map +1 -0
  231. package/dist/subscription-usage-recordings.d.mts +9 -0
  232. package/dist/subscription-usage-recordings.d.mts.map +1 -0
  233. package/dist/subscription-usage-recordings.mjs +53 -0
  234. package/dist/subscription-usage-recordings.mjs.map +1 -0
  235. package/dist/subscription-usage.d.mts +53 -0
  236. package/dist/subscription-usage.d.mts.map +1 -0
  237. package/dist/subscription-usage.mjs +21 -0
  238. package/dist/subscription-usage.mjs.map +1 -0
  239. package/dist/telegram/api.d.mts +34 -0
  240. package/dist/telegram/api.d.mts.map +1 -0
  241. package/dist/telegram/api.mjs +257 -0
  242. package/dist/telegram/api.mjs.map +1 -0
  243. package/dist/telegram/state.d.mts +128 -0
  244. package/dist/telegram/state.d.mts.map +1 -0
  245. package/dist/telegram/state.mjs +154 -0
  246. package/dist/telegram/state.mjs.map +1 -0
  247. package/dist/telegram.d.mts +62 -0
  248. package/dist/telegram.d.mts.map +1 -0
  249. package/dist/telegram.mjs +127 -0
  250. package/dist/telegram.mjs.map +1 -0
  251. package/dist/todoist/api.d.mts +55 -0
  252. package/dist/todoist/api.d.mts.map +1 -0
  253. package/dist/todoist/api.mjs +415 -0
  254. package/dist/todoist/api.mjs.map +1 -0
  255. package/dist/todoist/state.d.mts +275 -0
  256. package/dist/todoist/state.d.mts.map +1 -0
  257. package/dist/todoist/state.mjs +345 -0
  258. package/dist/todoist/state.mjs.map +1 -0
  259. package/dist/todoist.d.mts +68 -0
  260. package/dist/todoist.d.mts.map +1 -0
  261. package/dist/todoist.mjs +181 -0
  262. package/dist/todoist.mjs.map +1 -0
  263. package/dist/xai.d.mts +267 -0
  264. package/dist/xai.d.mts.map +1 -0
  265. package/dist/xai.mjs +252 -0
  266. package/dist/xai.mjs.map +1 -0
  267. package/package.json +157 -0
  268. package/src/anthropic.ts +289 -0
  269. package/src/chat-completions.ts +924 -0
  270. package/src/codex.ts +296 -0
  271. package/src/dropbox/api.ts +1084 -0
  272. package/src/dropbox/state.ts +364 -0
  273. package/src/dropbox.ts +203 -0
  274. package/src/email-fixtures.ts +1297 -0
  275. package/src/email.ts +1108 -0
  276. package/src/emulator-compose.ts +147 -0
  277. package/src/emulator-http.ts +184 -0
  278. package/src/emulator-kernel.ts +844 -0
  279. package/src/fixture-route.ts +561 -0
  280. package/src/fortnox/api.ts +1352 -0
  281. package/src/fortnox/state.ts +798 -0
  282. package/src/fortnox.ts +801 -0
  283. package/src/gateway-evaluate-recordings.ts +153 -0
  284. package/src/gateway.ts +530 -0
  285. package/src/github/api.ts +986 -0
  286. package/src/github/state.ts +439 -0
  287. package/src/github.ts +271 -0
  288. package/src/google/calendar.ts +486 -0
  289. package/src/google/drive.ts +471 -0
  290. package/src/google/gmail.ts +1011 -0
  291. package/src/google/shared.ts +321 -0
  292. package/src/google/state.ts +686 -0
  293. package/src/google.ts +298 -0
  294. package/src/linkedin-search/api.ts +363 -0
  295. package/src/linkedin-search/state.ts +373 -0
  296. package/src/linkedin-search.ts +241 -0
  297. package/src/mcp/api.ts +995 -0
  298. package/src/mcp/recordings.ts +2665 -0
  299. package/src/mcp/state.ts +125 -0
  300. package/src/mcp.ts +319 -0
  301. package/src/messages.ts +844 -0
  302. package/src/microsoft/api.ts +426 -0
  303. package/src/microsoft/calendar.ts +491 -0
  304. package/src/microsoft/drive.ts +541 -0
  305. package/src/microsoft/graph.ts +550 -0
  306. package/src/microsoft/mail.ts +833 -0
  307. package/src/microsoft/state.ts +822 -0
  308. package/src/microsoft.ts +982 -0
  309. package/src/node.ts +293 -0
  310. package/src/notion/api.ts +976 -0
  311. package/src/notion/state.ts +512 -0
  312. package/src/notion.ts +210 -0
  313. package/src/openai.ts +198 -0
  314. package/src/opencode-recordings.ts +257 -0
  315. package/src/opencode.ts +271 -0
  316. package/src/r2-fixtures.ts +295 -0
  317. package/src/r2-guard.ts +323 -0
  318. package/src/r2.ts +890 -0
  319. package/src/responses.ts +901 -0
  320. package/src/route-evidence.ts +90 -0
  321. package/src/router.ts +490 -0
  322. package/src/stateful-core.ts +70 -0
  323. package/src/stateful-emulator.ts +2571 -0
  324. package/src/stateful-secrets.ts +299 -0
  325. package/src/subscription-usage-recordings.ts +77 -0
  326. package/src/subscription-usage.ts +68 -0
  327. package/src/telegram/api.ts +437 -0
  328. package/src/telegram/state.ts +229 -0
  329. package/src/telegram.ts +227 -0
  330. package/src/todoist/api.ts +736 -0
  331. package/src/todoist/state.ts +456 -0
  332. package/src/todoist.ts +316 -0
  333. package/src/xai.ts +341 -0
@@ -0,0 +1,591 @@
1
+ import { DecodedViewRefusal, exactBodyKeys, exactQuery, isJsonObject, isNotEmulated, mediaType, notEmulated, parseJsonText, statefulRoute } from "../stateful-emulator.mjs";
2
+ import { answer, evidence, gmailIdSegment, googleEmulatorGmailOrigin, googleErrorBody, googleJson, googleNoContent, isRunText, listPage, pageSize, param, recordedInternalDate, recordedValue, runIdOf, withoutQuery } from "./shared.mjs";
3
+ import { base64UrlOfText, mintedDraftId, mintedLabelIdPattern, mintedMessageId, mintedMessageIdPattern, pad } from "./state.mjs";
4
+ import { Predicate } from "effect";
5
+ //#region src/google/gmail.ts
6
+ /**
7
+ * Gmail routes of the Google emulator (internal): message listing by label with page tokens,
8
+ * message reads in the recorded formats, attachments, the label lifecycle, label changes, trash and
9
+ * untrash, the draft lifecycle with its thread, and the multipart practice send.
10
+ *
11
+ * Every answer comes from the Gmail fixtures, through the seed or the request, or is minted. The
12
+ * practice send is irreversible on the real service: here it only records the sent message in the
13
+ * state (nothing is delivered anywhere), and only a message whose sole recipient is the seeded
14
+ * practice address, exactly as the send fixture records it, is accepted.
15
+ *
16
+ * @experimental
17
+ */
18
+ /** Path prefix of every Gmail API route (`/gmail/v1/users/me`). */
19
+ const gmailApiPath = "/gmail/v1/users/me";
20
+ const listCase = "google.gmail.list-page-token";
21
+ const attachmentCase = "google.gmail.attachment-base64url";
22
+ const notFoundCase = "google.gmail.not-found-envelope";
23
+ const labelCase = "google.gmail.label-create-apply-delete";
24
+ const draftCase = "google.gmail.draft-compose-update-delete";
25
+ const trashCase = "google.gmail.trash-untrash";
26
+ const sendCase = "google.gmail.send-practice-address";
27
+ const gmail = (method, path, write, caseIds, params = []) => evidence(googleEmulatorGmailOrigin, method, `${gmailApiPath}${path}`, write, caseIds, Object.fromEntries(params.map((name) => [name, gmailIdSegment])));
28
+ /** The Gmail not-found envelope (`messages`, `labels`, and `drafts` reads and deletes). */
29
+ const notFound = (env) => googleJson(404, env.drills.notFoundWithoutMessage ? { error: {
30
+ code: 404,
31
+ status: "NOT_FOUND"
32
+ } } : googleErrorBody(404, "Requested entity was not found.", "notFound", "NOT_FOUND"));
33
+ /** A message id of the recorded absent form (16 lower-case hex digits). */
34
+ const absentMessageIdPattern = /^[0-9a-f]{16}$/;
35
+ const draftIdPattern = /^r-\d{1,20}$/;
36
+ /** The draft texts the draft fixture records (non-ASCII on purpose). */
37
+ const draftText = "Synthetic conformance draft, safe to delete: grüße ✓";
38
+ const updatedDraftText = "Updated synthetic conformance draft: ¡hola! ✓";
39
+ const draftSubjectRest = "synthetic conformance draft, safe to delete";
40
+ const sendSubjectRest = "synthetic conformance message, safe to delete";
41
+ /** The practice address the send fixture records (the sender and the only recipient). */
42
+ const recordedPracticeAddress = "practice@example.test";
43
+ /** Why a draft or send subject with a run id of another length is not emulated. */
44
+ const sizedRunIdReason = (what) => `a ${what} subject whose run id is not 13 characters long (the fixtures' run-synthetic) is not emulated: the recorded sizeEstimate covers the subject`;
45
+ /** The 7-bit MIME the send fixture records for `practiceAddress` and `subject`. */
46
+ const practiceMime = (practiceAddress, subject) => [
47
+ `To: ${practiceAddress}`,
48
+ `Subject: ${subject}`,
49
+ "MIME-Version: 1.0",
50
+ "Content-Type: text/plain; charset=us-ascii",
51
+ "",
52
+ "Synthetic conformance message sent to the seeded practice address only. Safe to delete.",
53
+ ""
54
+ ].join("\r\n");
55
+ /** The `multipart/related` upload body the send fixture records. */
56
+ const uploadBody = (boundary, mime) => [
57
+ `--${boundary}`,
58
+ "Content-Type: application/json; charset=UTF-8",
59
+ "",
60
+ "{}",
61
+ `--${boundary}`,
62
+ "Content-Type: message/rfc822",
63
+ "",
64
+ mime,
65
+ `--${boundary}--`
66
+ ].join("\r\n");
67
+ const head = (message) => ({
68
+ id: message.id,
69
+ threadId: message.threadId,
70
+ labelIds: [...message.labelIds],
71
+ snippet: message.snippet,
72
+ sizeEstimate: message.sizeEstimate,
73
+ historyId: message.historyId,
74
+ internalDate: message.internalDate
75
+ });
76
+ /** The recorded rendering of `message` in `format`, or `undefined` when none is recorded. */
77
+ const renderMessage = (message, format) => {
78
+ switch (format) {
79
+ case "minimal": return message.minimal ? head(message) : void 0;
80
+ case "metadata": return message.metadataPayload === null ? void 0 : {
81
+ ...head(message),
82
+ payload: message.metadataPayload
83
+ };
84
+ case "full": return message.fullPayload === null ? void 0 : {
85
+ ...head(message),
86
+ payload: message.fullPayload
87
+ };
88
+ }
89
+ };
90
+ /** The `{ id, threadId, labelIds }` answer of a label change, trash, or untrash. */
91
+ const labelsAnswer = (message, labelIds) => googleJson(200, {
92
+ id: message.id,
93
+ threadId: message.threadId,
94
+ labelIds: [...labelIds]
95
+ });
96
+ const findMessage = (state, id) => state.messages.find((message) => message.id === id);
97
+ const isImpliedMessage = (state, id) => state.impliedMessages.some((message) => message.id === id);
98
+ const isDraftMessage = (message) => message.labelIds.includes("DRAFT");
99
+ const replaceMessage = (state, next) => state.messages.map((message) => message.id === next.id ? next : message);
100
+ const listMessages = statefulRoute(gmail("GET", "/messages", false, [listCase]), "none", (request) => {
101
+ const query = exactQuery(request, ["labelIds", "maxResults"], ["pageToken"]);
102
+ if (isNotEmulated(query)) return query;
103
+ const labelId = query["labelIds"] ?? "";
104
+ if (!gmailIdSegment.test(labelId)) return notEmulated("labelIds must be one label id");
105
+ const maxResults = pageSize(query["maxResults"], "maxResults", 1, 500);
106
+ if (isNotEmulated(maxResults)) return maxResults;
107
+ const pageToken = query["pageToken"];
108
+ return pageToken === "" ? notEmulated("an empty pageToken is not emulated") : {
109
+ labelId,
110
+ maxResults,
111
+ pageToken
112
+ };
113
+ }, (state, input, { env }) => {
114
+ if (!(state.impliedLabelIds.includes(input.labelId) || state.labels.some((label) => label.id === input.labelId))) return notEmulated("listing a label the state does not hold is not emulated");
115
+ const matches = [...state.messages, ...state.impliedMessages].filter((message) => message.labelIds.includes(input.labelId));
116
+ if (matches.length === 0) return notEmulated("a listing without messages is not emulated (no fixture records one)");
117
+ if (matches.some((message) => message.labelIds.some((id) => id === "TRASH" || id === "SPAM"))) return notEmulated("a listing that would leave out messages in Trash or Spam is not emulated");
118
+ return listPage(env, "gmail", `gmail\u0000${input.labelId}\u0000${input.maxResults}`, matches.map((message) => ({
119
+ id: message.id,
120
+ threadId: message.threadId
121
+ })), input.maxResults, input.pageToken, env.drills.gmailPageRepeats, (page, nextPageToken) => {
122
+ const head = { messages: [...page] };
123
+ if (nextPageToken !== void 0) head.nextPageToken = nextPageToken;
124
+ return googleJson(200, {
125
+ ...head,
126
+ resultSizeEstimate: matches.length
127
+ });
128
+ });
129
+ });
130
+ const formats = [
131
+ "minimal",
132
+ "metadata",
133
+ "full"
134
+ ];
135
+ const isFormat = (value) => value !== void 0 && formats.includes(value);
136
+ const getMessage = statefulRoute(gmail("GET", "/messages/{messageId}", false, [
137
+ attachmentCase,
138
+ notFoundCase,
139
+ labelCase,
140
+ draftCase,
141
+ trashCase,
142
+ sendCase
143
+ ], ["messageId"]), "none", (request) => {
144
+ const query = exactQuery(request, ["format"]);
145
+ if (isNotEmulated(query)) return query;
146
+ const format = query["format"];
147
+ return isFormat(format) ? {
148
+ id: param(request, "messageId"),
149
+ format
150
+ } : notEmulated("format must be minimal, metadata, or full");
151
+ }, (state, input, { env }) => {
152
+ const message = findMessage(state, input.id);
153
+ if (message !== void 0) {
154
+ const rendered = renderMessage(message, input.format);
155
+ return rendered === void 0 ? notEmulated(`no fixture records this message with format=${input.format}`) : answer(() => googleJson(200, rendered));
156
+ }
157
+ if (isImpliedMessage(state, input.id)) return notEmulated("reading a message a fixture only names in a listing is not emulated");
158
+ return input.format === "minimal" && absentMessageIdPattern.test(input.id) ? answer(() => notFound(env)) : notEmulated("only a format=minimal read of an absent 16-hex-digit id is recorded");
159
+ });
160
+ const getAttachment = statefulRoute(gmail("GET", "/messages/{messageId}/attachments/{attachmentId}", false, [attachmentCase], ["messageId", "attachmentId"]), "none", (request) => withoutQuery(request) ?? {
161
+ messageId: param(request, "messageId"),
162
+ attachmentId: param(request, "attachmentId")
163
+ }, (state, input, { env }) => {
164
+ const attachment = state.attachments.find((item) => item.messageId === input.messageId && item.attachmentId === input.attachmentId);
165
+ if (attachment === void 0 || findMessage(state, input.messageId) === void 0) return notEmulated("an attachment the state does not hold is not emulated");
166
+ const data = env.drills.attachmentStandardBase64 ? attachment.data.replaceAll("-", "+").replaceAll("_", "/") : attachment.data;
167
+ return answer(() => googleJson(200, {
168
+ size: attachment.size,
169
+ data
170
+ }));
171
+ });
172
+ const createLabel = statefulRoute(gmail("POST", "/labels", true, [labelCase]), "json", (request) => {
173
+ const body = withoutQuery(request) ?? exactBodyKeys(request.json, "the label body", ["name"]);
174
+ if (isNotEmulated(body)) return body;
175
+ return Predicate.isString(body.name) && isRunText(body.name, "label") ? body.name : notEmulated("a label name other than \"yolk-conformance <runId> label\" is not emulated");
176
+ }, (state, name) => {
177
+ if (state.labels.some((label) => label.name === name)) return notEmulated("creating a label whose name exists is not emulated");
178
+ if (state.counters.nextLabelNumber > 999999999) return notEmulated("creating a label when no Label_<n> id is left is not emulated");
179
+ return () => {
180
+ const number = state.counters.nextLabelNumber;
181
+ const label = {
182
+ id: `Label_${number}`,
183
+ name,
184
+ messageListVisibility: "show",
185
+ labelListVisibility: "labelShow",
186
+ type: "user"
187
+ };
188
+ state.counters = {
189
+ ...state.counters,
190
+ nextLabelNumber: number + 1
191
+ };
192
+ state.labels = [...state.labels, label];
193
+ return googleJson(200, { ...label });
194
+ };
195
+ });
196
+ const getLabel = statefulRoute(gmail("GET", "/labels/{labelId}", false, [labelCase], ["labelId"]), "none", (request) => withoutQuery(request) ?? param(request, "labelId"), (state, id, { env }) => {
197
+ if (state.labels.some((label) => label.id === id) || state.impliedLabelIds.includes(id)) return notEmulated("no fixture records a label read of an existing label");
198
+ return mintedLabelIdPattern.test(id) ? answer(() => notFound(env)) : notEmulated("only a read of an absent Label_<n> id is recorded");
199
+ });
200
+ const deleteLabel = statefulRoute(gmail("DELETE", "/labels/{labelId}", true, [labelCase], ["labelId"]), "none", (request) => withoutQuery(request) ?? param(request, "labelId"), (state, id, { env }) => {
201
+ if (!state.labels.some((label) => label.id === id)) return notEmulated("only deleting a label created here is recorded");
202
+ return () => {
203
+ const without = (message) => message.labelIds.includes(id) ? {
204
+ ...message,
205
+ labelIds: message.labelIds.filter((label) => label !== id)
206
+ } : message;
207
+ state.labels = state.labels.filter((label) => label.id !== id);
208
+ if (!env.drills.labelDeleteKeepsOnMessages) {
209
+ state.messages = state.messages.map(without);
210
+ state.impliedMessages = state.impliedMessages.map(without);
211
+ }
212
+ return googleNoContent();
213
+ };
214
+ });
215
+ const modifyMessage = statefulRoute(gmail("POST", "/messages/{messageId}/modify", true, [labelCase], ["messageId"]), "json", (request) => {
216
+ const body = withoutQuery(request) ?? exactBodyKeys(request.json, "the modify body", ["addLabelIds"]);
217
+ if (isNotEmulated(body)) return body;
218
+ const added = body.addLabelIds;
219
+ if (!Array.isArray(added) || added.length !== 1 || !Predicate.isString(added[0])) return notEmulated("a modify adding other than one label is not emulated");
220
+ return {
221
+ messageId: param(request, "messageId"),
222
+ labelId: added[0]
223
+ };
224
+ }, (state, input) => {
225
+ const message = findMessage(state, input.messageId);
226
+ if (message === void 0 || isDraftMessage(message)) return notEmulated("changing the labels of a message the state does not hold is not emulated");
227
+ if (!state.labels.some((label) => label.id === input.labelId)) return notEmulated("adding a label other than one created here is not emulated");
228
+ if (message.labelIds.includes(input.labelId)) return notEmulated("adding a label the message carries already is not emulated");
229
+ return () => {
230
+ const next = {
231
+ ...message,
232
+ labelIds: [...message.labelIds, input.labelId]
233
+ };
234
+ state.messages = replaceMessage(state, next);
235
+ return labelsAnswer(next, next.labelIds);
236
+ };
237
+ });
238
+ const trashRoute = (action) => statefulRoute(gmail("POST", `/messages/{messageId}/${action}`, true, [trashCase], ["messageId"]), "none", (request) => withoutQuery(request) ?? param(request, "messageId"), (state, id, { env }) => {
239
+ const message = findMessage(state, id);
240
+ if (message === void 0 || isDraftMessage(message)) return notEmulated(`${action} of a message the state does not hold is not emulated`);
241
+ const inTrash = message.labelIds.includes("TRASH");
242
+ if (action === "trash" ? inTrash : !inTrash) return notEmulated(action === "trash" ? "trashing a message already in Trash is not emulated" : "untrashing a message not in Trash is not emulated");
243
+ return () => {
244
+ const labelIds = action === "trash" ? [...message.labelIds, "TRASH"] : message.labelIds.filter((label) => label !== "TRASH");
245
+ const next = {
246
+ ...message,
247
+ labelIds
248
+ };
249
+ state.messages = replaceMessage(state, next);
250
+ return labelsAnswer(next, action === "trash" && env.drills.trashAnswerOmitsTrash ? labelIds.filter((label) => label !== "TRASH") : labelIds);
251
+ };
252
+ });
253
+ const base64UrlPattern = /^[A-Za-z0-9_-]*$/;
254
+ /** The UTF-8 text of canonical unpadded base64url, or `undefined`. */
255
+ const decodeBase64UrlText = (raw) => {
256
+ if (!base64UrlPattern.test(raw) || raw.length % 4 === 1) return void 0;
257
+ try {
258
+ const binary = atob(raw.replaceAll("-", "+").replaceAll("_", "/"));
259
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
260
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
261
+ return base64UrlOfText(text) === raw ? text : void 0;
262
+ } catch {
263
+ return;
264
+ }
265
+ };
266
+ const draftMimePattern = /^Subject: ([^\r\n]*)\r\nContent-Type: text\/plain; charset=utf-8\r\n\r\n([\s\S]*)$/;
267
+ /** The reasons a draft `message.raw` is refused with (constants; never derived from a request). */
268
+ const draftKeysReason = "message has a key this route does not take";
269
+ const canonicalRawReason = "message.raw must be canonical base64url UTF-8 MIME";
270
+ const otherDraftReason = (kind) => `a draft ${kind} other than the recorded run draft is not emulated`;
271
+ /**
272
+ * Every reason `readDraftRaw` refuses a draft of `kind` with: the route's declared
273
+ * `viewRefusalReasons`, so the wrapper ledgers exactly these (and nothing derived from a request).
274
+ */
275
+ const draftRefusalReasons = (kind) => [
276
+ draftKeysReason,
277
+ canonicalRawReason,
278
+ otherDraftReason(kind),
279
+ sizedRunIdReason("draft")
280
+ ];
281
+ /** The recorded draft MIME (no recipient) of a compose or an update, decoded once. */
282
+ const readDraftRaw = (message, kind) => {
283
+ const raw = Predicate.hasProperty(message, "raw") && !Array.isArray(message) ? message.raw : void 0;
284
+ const decoded = Predicate.isString(raw) ? decodeBase64UrlText(raw) : void 0;
285
+ const fields = exactBodyKeys(message, "message", ["raw"]);
286
+ if (isNotEmulated(fields)) return {
287
+ decoded,
288
+ content: fields
289
+ };
290
+ if (decoded === void 0) return {
291
+ decoded,
292
+ content: notEmulated(canonicalRawReason)
293
+ };
294
+ const parts = draftMimePattern.exec(decoded);
295
+ const subject = parts?.[1] ?? "";
296
+ const body = parts?.[2] ?? "";
297
+ const runId = kind === "compose" ? runIdOf(subject, "draft", draftSubjectRest) : runIdOf(subject, "draft updated", draftSubjectRest);
298
+ if (parts === null || runId === void 0 || body !== (kind === "compose" ? draftText : updatedDraftText)) return {
299
+ decoded,
300
+ content: notEmulated(otherDraftReason(kind))
301
+ };
302
+ return {
303
+ decoded,
304
+ content: runId.length === 13 ? {
305
+ subject,
306
+ body
307
+ } : notEmulated(sizedRunIdReason("draft"))
308
+ };
309
+ };
310
+ /**
311
+ * The decoded view of a draft body for the credential guard (Gmail's own wire format wraps the
312
+ * draft MIME in base64url `message.raw`). Fail closed: when the body is a JSON object whose
313
+ * `message` object carries `raw`, the view returns the decoded MIME only when it is exactly the
314
+ * recorded draft MIME of this route (`kind`), the run id aside. Any other raw makes it throw a
315
+ * `DecodedViewRefusal` with the route's own constant reason (one of `draftRefusalReasons`) and
316
+ * whatever it decoded cleanly: the wrapper ledgers a repeat when that text holds the bearer, and
317
+ * the declared reason otherwise, before anything is recorded, so a refused raw (line-wrapped, the
318
+ * standard alphabet, a stray character, RFC 2047 words, quoted-printable, UTF-16, a 12-character
319
+ * run id, ...) never reaches the ledger. A body without `message.raw` yields no view (the route's
320
+ * own shape check refuses it).
321
+ */
322
+ const draftRawViews = (kind) => (body) => {
323
+ const parsed = parseJsonText(body);
324
+ const message = isJsonObject(parsed) ? parsed.message : void 0;
325
+ if (!isJsonObject(message) || !("raw" in message)) return [];
326
+ const { decoded, content } = readDraftRaw(message, kind);
327
+ if (isNotEmulated(content)) throw new DecodedViewRefusal({
328
+ reason: content.reason,
329
+ decoded: decoded === void 0 ? [] : [decoded]
330
+ });
331
+ return decoded === void 0 ? [] : [decoded];
332
+ };
333
+ const textPayload = (subject, body) => ({
334
+ partId: "",
335
+ mimeType: "text/plain",
336
+ filename: "",
337
+ headers: [{
338
+ name: "Subject",
339
+ value: subject
340
+ }, {
341
+ name: "Content-Type",
342
+ value: "text/plain; charset=utf-8"
343
+ }],
344
+ body
345
+ });
346
+ /** A draft message as the draft fixture records it (the metadata rendering only on compose). */
347
+ const draftMessage = (id, threadId, content, withMetadata) => ({
348
+ id,
349
+ threadId,
350
+ labelIds: ["DRAFT"],
351
+ snippet: content.body,
352
+ sizeEstimate: 512,
353
+ historyId: "900020",
354
+ internalDate: recordedInternalDate,
355
+ minimal: false,
356
+ metadataPayload: withMetadata ? textPayload(content.subject, { size: 64 }) : null,
357
+ fullPayload: textPayload(content.subject, {
358
+ size: new TextEncoder().encode(content.body).byteLength,
359
+ data: base64UrlOfText(content.body)
360
+ })
361
+ });
362
+ const draftAnswer = (draftId, message) => googleJson(200, {
363
+ id: draftId,
364
+ message: {
365
+ id: message.id,
366
+ threadId: message.threadId,
367
+ labelIds: [...message.labelIds]
368
+ }
369
+ });
370
+ const composeDraft = statefulRoute({
371
+ ...gmail("POST", "/drafts", true, [draftCase]),
372
+ decodedViews: draftRawViews("compose"),
373
+ viewRefusalReasons: draftRefusalReasons("compose")
374
+ }, "json", (request) => {
375
+ const body = withoutQuery(request) ?? exactBodyKeys(request.json, "the draft body", ["message"]);
376
+ return isNotEmulated(body) ? body : readDraftRaw(body.message, "compose").content;
377
+ }, (state, content) => () => {
378
+ const draftNumber = state.counters.nextDraftNumber;
379
+ const messageNumber = state.counters.nextDraftMessageNumber;
380
+ const id = mintedMessageId("d", messageNumber);
381
+ const message = draftMessage(id, id, content, true);
382
+ const draftId = mintedDraftId(draftNumber);
383
+ state.counters = {
384
+ ...state.counters,
385
+ nextDraftNumber: draftNumber + 1,
386
+ nextDraftMessageNumber: messageNumber + 1
387
+ };
388
+ state.messages = [...state.messages, message];
389
+ state.drafts = [...state.drafts, {
390
+ id: draftId,
391
+ messageId: id
392
+ }];
393
+ return draftAnswer(draftId, message);
394
+ });
395
+ const updateDraft = statefulRoute({
396
+ ...gmail("PUT", "/drafts/{draftId}", true, [draftCase], ["draftId"]),
397
+ decodedViews: draftRawViews("update"),
398
+ viewRefusalReasons: draftRefusalReasons("update")
399
+ }, "json", (request) => {
400
+ const draftId = param(request, "draftId");
401
+ const body = withoutQuery(request) ?? exactBodyKeys(request.json, "the draft body", ["id", "message"]);
402
+ if (isNotEmulated(body)) return body;
403
+ if (body.id !== draftId) return notEmulated("a body id other than the path draft id");
404
+ const content = readDraftRaw(body.message, "update").content;
405
+ return isNotEmulated(content) ? content : {
406
+ draftId,
407
+ content
408
+ };
409
+ }, (state, input, { env }) => {
410
+ const draft = state.drafts.find((candidate) => candidate.id === input.draftId);
411
+ const previous = draft === void 0 ? void 0 : findMessage(state, draft.messageId);
412
+ if (draft === void 0 || previous === void 0) return notEmulated("updating a draft the state does not hold is not emulated");
413
+ return () => {
414
+ const number = state.counters.nextDraftMessageNumber;
415
+ const id = mintedMessageId("d", number);
416
+ const message = env.drills.draftUpdateKeepsContent ? {
417
+ ...previous,
418
+ id,
419
+ metadataPayload: null
420
+ } : draftMessage(id, previous.threadId, input.content, false);
421
+ state.counters = {
422
+ ...state.counters,
423
+ nextDraftMessageNumber: number + 1
424
+ };
425
+ state.messages = [...state.messages.filter((candidate) => candidate.id !== previous.id), message];
426
+ state.drafts = state.drafts.map((candidate) => candidate.id === draft.id ? {
427
+ ...candidate,
428
+ messageId: id
429
+ } : candidate);
430
+ return draftAnswer(draft.id, message);
431
+ };
432
+ });
433
+ const deleteDraft = statefulRoute(gmail("DELETE", "/drafts/{draftId}", true, [draftCase], ["draftId"]), "none", (request) => withoutQuery(request) ?? param(request, "draftId"), (state, id, { env }) => {
434
+ const draft = state.drafts.find((candidate) => candidate.id === id);
435
+ if (draft === void 0) return draftIdPattern.test(id) ? answer(() => notFound(env)) : notEmulated("only deleting an absent r-<digits> draft id is recorded");
436
+ return () => {
437
+ state.drafts = state.drafts.filter((candidate) => candidate.id !== id);
438
+ state.messages = state.messages.filter((message) => message.id !== draft.messageId);
439
+ return googleNoContent();
440
+ };
441
+ });
442
+ const getThread = statefulRoute(gmail("GET", "/threads/{threadId}", false, [draftCase], ["threadId"]), "none", (request) => {
443
+ const query = exactQuery(request, ["format"]);
444
+ if (isNotEmulated(query)) return query;
445
+ return recordedValue(query, "format", "full") ?? param(request, "threadId");
446
+ }, (state, threadId) => {
447
+ const messages = state.messages.filter((message) => message.threadId === threadId);
448
+ const createdDrafts = messages.every((message) => isDraftMessage(message) && mintedMessageIdPattern.test(message.id));
449
+ if (messages.length === 0 || !createdDrafts) return notEmulated("a thread other than a draft thread created here is not emulated");
450
+ const rendered = messages.map((message) => renderMessage(message, "full"));
451
+ if (rendered.some((item) => item === void 0)) return notEmulated("a thread with a message without a full rendering is not emulated");
452
+ return answer(() => googleJson(200, {
453
+ id: threadId,
454
+ historyId: messages.at(-1)?.historyId ?? "",
455
+ messages: rendered.flatMap((item) => item === void 0 ? [] : [item])
456
+ }));
457
+ });
458
+ const boundaryPattern = /^[A-Za-z0-9_]{1,70}$/;
459
+ /**
460
+ * The boundary of `multipart/related; boundary=<b>`, or `undefined`. The parameter list is parsed
461
+ * whole: each parameter is split on its first `=`, and the content type must carry exactly one
462
+ * parameter, `boundary`, whose whole value is 1 to 70 of `[A-Za-z0-9_]` (unquoted). Any other,
463
+ * repeated, empty, or malformed parameter is refused (the send records no other parameter).
464
+ */
465
+ const multipartBoundary = (contentType) => {
466
+ if (mediaType(contentType) !== "multipart/related") return void 0;
467
+ const params = (contentType ?? "").split(";").slice(1).map((param) => {
468
+ const separator = param.indexOf("=");
469
+ return separator === -1 ? void 0 : {
470
+ name: param.slice(0, separator).trim().toLowerCase(),
471
+ value: param.slice(separator + 1)
472
+ };
473
+ });
474
+ const [only] = params;
475
+ return params.length === 1 && only !== void 0 && only.name === "boundary" && boundaryPattern.test(only.value) ? only.value : void 0;
476
+ };
477
+ const sendMessage = statefulRoute(evidence(googleEmulatorGmailOrigin, "POST", "/upload/gmail/v1/users/me/messages/send", true, [sendCase]), "bytes", (request) => {
478
+ const query = exactQuery(request, ["uploadType"]);
479
+ if (isNotEmulated(query)) return query;
480
+ const problem = recordedValue(query, "uploadType", "multipart");
481
+ if (problem !== void 0) return problem;
482
+ const boundary = multipartBoundary(request.header("content-type"));
483
+ if (boundary === void 0) return notEmulated("a content-type other than multipart/related; boundary=<b> is not emulated");
484
+ let text;
485
+ try {
486
+ text = new TextDecoder("utf-8", { fatal: true }).decode(request.bytes);
487
+ } catch {
488
+ return notEmulated("an upload body that is not 7-bit text is not emulated");
489
+ }
490
+ const subject = /\r\nSubject: ([^\r\n]*)\r\n/.exec(text)?.[1] ?? "";
491
+ const runId = runIdOf(subject, "send", sendSubjectRest);
492
+ if (runId === void 0) return notEmulated("a send other than the recorded run-scoped practice message is not emulated");
493
+ return runId.length === 13 ? {
494
+ boundary,
495
+ subject,
496
+ text
497
+ } : notEmulated(sizedRunIdReason("send"));
498
+ }, (state, input, { env }) => {
499
+ if (state.practiceAddress !== recordedPracticeAddress) return notEmulated(`a send while the seeded practiceAddress is not the recorded ${recordedPracticeAddress} is not emulated (the recorded sizeEstimate covers the address)`);
500
+ if (input.text !== uploadBody(input.boundary, practiceMime(state.practiceAddress, input.subject))) return notEmulated("a send other than the recorded 7-bit message to the seeded practice address only is not emulated");
501
+ return () => {
502
+ const instant = new Date(env.now());
503
+ if (!Number.isFinite(instant.getTime())) throw new Error("the clock answered an instant outside the Date range");
504
+ const date = instant.toUTCString().replace(/GMT$/, "+0000");
505
+ const number = state.counters.nextSentNumber;
506
+ const id = mintedMessageId("e", number);
507
+ const address = state.practiceAddress;
508
+ const sent = {
509
+ id,
510
+ threadId: id,
511
+ labelIds: [
512
+ "SENT",
513
+ "INBOX",
514
+ "UNREAD"
515
+ ],
516
+ snippet: "Synthetic conformance message sent to the seeded practice address only.",
517
+ sizeEstimate: 640,
518
+ historyId: "900030",
519
+ internalDate: recordedInternalDate,
520
+ minimal: false,
521
+ metadataPayload: {
522
+ partId: "",
523
+ mimeType: "text/plain",
524
+ filename: "",
525
+ headers: [
526
+ {
527
+ name: "MIME-Version",
528
+ value: "1.0"
529
+ },
530
+ {
531
+ name: "Date",
532
+ value: date
533
+ },
534
+ {
535
+ name: "Message-ID",
536
+ value: `<synthetic-send-${pad(number, 4)}@example.test>`
537
+ },
538
+ {
539
+ name: "Subject",
540
+ value: input.subject
541
+ },
542
+ {
543
+ name: "From",
544
+ value: address
545
+ },
546
+ {
547
+ name: "To",
548
+ value: address
549
+ },
550
+ {
551
+ name: "Content-Type",
552
+ value: "text/plain; charset=us-ascii"
553
+ }
554
+ ].filter((header) => !(env.drills.sentMessageWithoutTo && header.name === "To")),
555
+ body: { size: 88 }
556
+ },
557
+ fullPayload: null
558
+ };
559
+ state.counters = {
560
+ ...state.counters,
561
+ nextSentNumber: number + 1
562
+ };
563
+ state.messages = [...state.messages, sent];
564
+ return googleJson(200, {
565
+ id,
566
+ threadId: id,
567
+ labelIds: ["SENT"]
568
+ });
569
+ };
570
+ });
571
+ /** The Gmail routes, in manifest order. */
572
+ const gmailRoutes = [
573
+ listMessages,
574
+ getMessage,
575
+ getAttachment,
576
+ modifyMessage,
577
+ trashRoute("trash"),
578
+ trashRoute("untrash"),
579
+ createLabel,
580
+ getLabel,
581
+ deleteLabel,
582
+ composeDraft,
583
+ updateDraft,
584
+ deleteDraft,
585
+ getThread,
586
+ sendMessage
587
+ ];
588
+ //#endregion
589
+ export { gmailApiPath, gmailRoutes };
590
+
591
+ //# sourceMappingURL=gmail.mjs.map