@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
package/src/github.ts ADDED
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Stateful GitHub emulator (the REST routes of the GitHub conformance cases on
3
+ * `https://api.github.com`, API version 2026-03-10), built on the upstream `@emulators/core`
4
+ * custom runtime, with a request ledger, status faults, and an `/_emulate/*` control plane.
5
+ *
6
+ * It never imports SDK code: its wire shapes, error bodies, and default seed are copied as data
7
+ * from the synthetic GitHub conformance fixtures, and every route names the conformance cases it
8
+ * follows in `githubEmulatorRoutes`. Only the routes those seven cases send are emulated. Response
9
+ * behaviour comes only from the fixtures: anything they do not show answers one ledgered 400
10
+ * not-emulated, writes nothing, and uses up no fault. That includes the leftover lookup's
11
+ * open-issue listing (`GET /repos/{owner}/{repo}/issues`, no fixture), so the read-only lookup
12
+ * fails and the live runner prints its lookup-failed WARN. Every route answers only on the
13
+ * recorded origin `https://api.github.com` (`fetchOn(origin)` serves it behind a loopback
14
+ * rewrite).
15
+ *
16
+ * Fail closed: a request is recognised only when its raw path is exactly an emulated route shape
17
+ * (every path parameter matches its raw pattern in full) under that route's method, and any
18
+ * `Authorization` header is exactly `Bearer <token>` (a recognisable bearer, below). Every other
19
+ * request is ledgered and answered with constant text only (`/<unrecognised>`, a standard method or
20
+ * `<other>`, an empty query, no body, a constant reason). The bearer value is never compared
21
+ * against anything, stored, forwarded, or ledgered. A recognised bearer must match the RFC 6750
22
+ * `b64token` syntax exactly (`^[A-Za-z0-9\-._~+/]+=*$`, at least 8 characters), start with a
23
+ * character in `[G-Zg-z\-._~+/]` other than `n`, `r`, `t`, `u`, and hold at least one character
24
+ * outside the JSON-number alphabet `[0-9.eE+-]` (every GitHub and Google token form does: `ghp_…`,
25
+ * `github_pat_…`, `gho_…`, `ya29.…`). So no number's text can contain it; it holds no escape
26
+ * introducer (`%`, `\`, `"`), so no escape starts inside it; and its first character is no hex
27
+ * digit and no JSON escape letter, so no stray `%`, `\`, or partial escape to its left can complete
28
+ * with it, and its characters always decode in place. An `Authorization` header with any other
29
+ * value is unrecognisable. A recognised request that repeats the bearer value in its raw path, any
30
+ * path segment, the raw query or any query key or value, any recorded header, or its body is
31
+ * refused and ledgered with constant text only: a standard method, the path `/<unrecognised>`, its
32
+ * route template, an empty query, no headers or body, and a constant reason
33
+ * (`the query repeats the credential`, for example). Each part is checked through the closure of
34
+ * two total, lexical transforms that cannot fail: a tolerant percent-decode (every `%XX` below
35
+ * `%80` becomes its ASCII character; any other `%` sequence is left as it is) and a tolerant
36
+ * JSON-unescape (in any text, whether or not it parses as JSON, `\uXXXX` below `\u0080` and `\"`,
37
+ * `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t` become their characters). Starting from each part's raw
38
+ * text, either transform is applied to every text of the previous step, deduplicated, until no new
39
+ * text appears (a fixpoint), and every text is checked for the bearer as a substring; both
40
+ * transforms never lengthen a text and shorten it whenever they change it. So any depth of
41
+ * percent-encoding or JSON escaping, in any order, is seen through in every part: the raw path and
42
+ * each raw path segment, the raw query and each query key and value (already decoded once by
43
+ * `URLSearchParams`), each recorded header, and the raw body. The work is capped at 64 rounds, 1024
44
+ * distinct texts, or 8 Mi characters read by the transforms, whichever comes first; a part whose
45
+ * closure hits a cap before its fixpoint counts as repeating the credential and is refused with the
46
+ * same constant entry (uncertainty refuses, it never admits; so any part over 4 Mi characters is
47
+ * always refused). Any other recognised request has the bearer value scrubbed from its ledgered
48
+ * fields and every not-emulated reason (plan-time reasons included); its recorded query is keyed by
49
+ * recorded key; a key recorded more than once lists its values in order (as a JSON array); and
50
+ * recorded headers and query keys and values that start like JSON (`{`, `[`, `"`) are recorded
51
+ * parsed with credential-named keys redacted at any depth, or as `<redacted>` when they do not
52
+ * parse, whatever the header's declared format. Refusals never echo a request's own query or body
53
+ * keys.
54
+ *
55
+ * Request-shape latitude (`/github`, the only accepted deviations): any bearer value in the RFC
56
+ * 6750 `b64token` syntax (`[A-Za-z0-9\-._~+/]+=*`) of at least 8 characters, starting with a
57
+ * character in `[G-Zg-z\-._~+/]` other than `n`, `r`, `t`, `u` (so a legacy all-hex token is
58
+ * refused), with at least one outside `[0-9.eE+-]`, that occurs nowhere else in the request (never
59
+ * compared against anything, stored, or ledgered); extra request headers; JSON key order;
60
+ * `content-type` media-type parameters; the order of query parameters; any non-empty issue title
61
+ * and comment body, and any issue body text; any comment listing `since` of the form
62
+ * `YYYY-MM-DDTHH:MM:SSZ`; any label listing `per_page` from 1 to 100, with no `page` or a `page`
63
+ * from 2 to one past the last page; any issue search `q` that starts with the seeded
64
+ * `repo:<owner>/<repo>` qualifier and whose query after it is longer than 256 characters (answered
65
+ * the recorded 422); any issue number the repository has not reached (answered the recorded 404);
66
+ * and any issue, comment, repository label, or file the state holds where a fixture has one, under
67
+ * the per-route state rules. `Authorization` must be exactly `Bearer <token>` (that spelling, one
68
+ * space), `Accept` `application/vnd.github+json`, and `X-GitHub-Api-Version` `2026-03-10`.
69
+ * Everything else (other keys and values, query parameters, empty query components such as a bare
70
+ * `?` or a stray `&`, another origin or repository, a repeated query key, an explicit `page=1`, and
71
+ * a comment listing `per_page` other than 100) is not emulated.
72
+ *
73
+ * Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by
74
+ * `makeGithubEmulator` (importing this module has no side effects).
75
+ *
76
+ * @experimental
77
+ */
78
+ import { Data, Predicate } from 'effect'
79
+ import {
80
+ githubApiRoutes,
81
+ githubEmulatorAccept,
82
+ githubEmulatorApiVersion,
83
+ githubEmulatorDrillKnobs,
84
+ type GithubApiEnv,
85
+ type GithubEmulatorDrills
86
+ } from './github/api.ts'
87
+ import {
88
+ buildSeedState,
89
+ decodeState,
90
+ type GithubEmulatorSeed,
91
+ type GithubEmulatorState
92
+ } from './github/state.ts'
93
+ import type { EmulatorRouteEvidence } from './route-evidence.ts'
94
+ import {
95
+ StatefulFault,
96
+ StatefulFaultMatch,
97
+ checkBooleanDrills,
98
+ makeStatefulEmulator,
99
+ routeEvidence,
100
+ type StatefulCoverage,
101
+ type StatefulEmulatorApi,
102
+ type StatefulFaultState,
103
+ type StatefulInputKind,
104
+ type StatefulLedgerEntry
105
+ } from './stateful-emulator.ts'
106
+ import { statefulCoreRuntime } from './stateful-core.ts'
107
+
108
+ export type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'
109
+
110
+ export { emulatorEvidenceHeader } from './route-evidence.ts'
111
+
112
+ export {
113
+ githubEmulatorAccept,
114
+ githubEmulatorApiVersion,
115
+ githubEmulatorErrorBodies,
116
+ githubEmulatorOrigin,
117
+ type GithubEmulatorDrills
118
+ } from './github/api.ts'
119
+
120
+ export {
121
+ GithubEmulatorComment,
122
+ GithubEmulatorFile,
123
+ GithubEmulatorIssue,
124
+ GithubEmulatorLabel,
125
+ GithubEmulatorProfile,
126
+ GithubEmulatorRepository,
127
+ GithubEmulatorSeed,
128
+ GithubEmulatorSeedIssue,
129
+ GithubEmulatorStateSchema,
130
+ GithubEmulatorUser,
131
+ type GithubEmulatorState
132
+ } from './github/state.ts'
133
+
134
+ /**
135
+ * Route evidence manifest: every emulated route, whether it writes, and the conformance cases
136
+ * whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
137
+ * by construction (both come from one route table).
138
+ */
139
+ export const githubEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> =
140
+ githubApiRoutes.map(routeEvidence)
141
+
142
+ /**
143
+ * Optional fault filter; an omitted field matches every request. `method` is the HTTP method;
144
+ * `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the
145
+ * manifest (another value is rejected when the fault is added).
146
+ */
147
+ export const GithubFaultMatch = StatefulFaultMatch
148
+
149
+ export type GithubFaultMatch = typeof StatefulFaultMatch.Type
150
+
151
+ /**
152
+ * A status fault (400-599): answer matching requests with this status, headers, and body instead
153
+ * of the route's write, so nothing is written. The body defaults to
154
+ * `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.
155
+ * Only a request the emulator would answer reaches a fault: a request that is not emulated, by
156
+ * its shape or by the state, never uses one up.
157
+ */
158
+ export const GithubFault = StatefulFault
159
+
160
+ export type GithubFault = StatefulFault
161
+
162
+ export type GithubFaultState = StatefulFaultState
163
+
164
+ export type GithubLedgerEntry = StatefulLedgerEntry
165
+
166
+ export type GithubCoverage = StatefulCoverage
167
+
168
+ /** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
169
+ export class GithubEmulatorInputInvalid extends Data.TaggedError('GithubEmulatorInputInvalid')<{
170
+ readonly input: StatefulInputKind
171
+ readonly reason: string
172
+ }> {
173
+ override get message(): string {
174
+ return `Invalid GitHub emulator ${this.input}: ${this.reason}`
175
+ }
176
+ }
177
+
178
+ export type GithubEmulatorOptions = {
179
+ /** Typed seed; defaults to the fixture entities (`profile: 'default'`). */
180
+ readonly seed?: GithubEmulatorSeed
181
+ /**
182
+ * Clock in epoch milliseconds (created issue and comment times, close times). Defaults to
183
+ * `Date.now`.
184
+ */
185
+ readonly now?: () => number
186
+ /** Drill knobs (tests only): make the emulator disagree with one conformance claim. */
187
+ readonly drills?: GithubEmulatorDrills
188
+ }
189
+
190
+ export type GithubEmulator = StatefulEmulatorApi<GithubEmulatorState, GithubEmulatorSeed>
191
+
192
+ const inputInvalid = (input: StatefulInputKind, reason: string) =>
193
+ new GithubEmulatorInputInvalid({ input, reason })
194
+
195
+ /** The constant reason of a request on no emulated route shape. */
196
+ const githubUnrecognisedReason = 'no emulated GitHub route for this method and path'
197
+
198
+ /** The constant reason of a request whose `Authorization` header is not one recognisable bearer. */
199
+ const githubUnrecognisedAuthorizationReason =
200
+ 'an unrecognisable Authorization header is not emulated'
201
+
202
+ /**
203
+ * Create a stateful GitHub emulator on the `@emulators/core` custom runtime. Each call has its own
204
+ * state, ledger, and faults. Rejects with `GithubEmulatorInputInvalid` for an invalid seed or
205
+ * option. Every request needs `Accept: application/vnd.github+json` and
206
+ * `X-GitHub-Api-Version: 2026-03-10` (else not emulated). See `src/stateful-emulator.ts` for the
207
+ * request precedence.
208
+ */
209
+ export const makeGithubEmulator = async (
210
+ options: GithubEmulatorOptions = {}
211
+ ): Promise<GithubEmulator> => {
212
+ const initial = buildSeedState(options.seed ?? {})
213
+
214
+ if (Predicate.isString(initial)) {
215
+ throw inputInvalid('seed', initial)
216
+ }
217
+
218
+ checkBooleanDrills(options.drills, githubEmulatorDrillKnobs, inputInvalid)
219
+
220
+ const drills = options.drills ?? {}
221
+
222
+ const env: GithubApiEnv = {
223
+ now: options.now ?? (() => Date.now()),
224
+ drills: {
225
+ linkOmitsNext: drills.linkOmitsNext === true,
226
+ notFoundOmitsDocumentationUrl: drills.notFoundOmitsDocumentationUrl === true,
227
+ validationWithoutErrors: drills.validationWithoutErrors === true,
228
+ contentUnfolded: drills.contentUnfolded === true,
229
+ sinceExcludesEqual: drills.sinceExcludesEqual === true,
230
+ addAnswerOmitsLabel: drills.addAnswerOmitsLabel === true,
231
+ closeWithoutClosedAt: drills.closeWithoutClosedAt === true
232
+ }
233
+ }
234
+
235
+ return makeStatefulEmulator<GithubEmulatorState, GithubApiEnv, GithubEmulatorSeed>(
236
+ {
237
+ routes: githubApiRoutes,
238
+ env,
239
+ initial,
240
+ buildSeed: buildSeedState,
241
+ requestProblem: header => {
242
+ // The values every fixture sends.
243
+ if (header('accept') !== githubEmulatorAccept) {
244
+ return `Accept other than ${githubEmulatorAccept} is not emulated`
245
+ }
246
+
247
+ return header('x-github-api-version') === githubEmulatorApiVersion
248
+ ? undefined
249
+ : `X-GitHub-Api-Version other than ${githubEmulatorApiVersion} is not emulated`
250
+ },
251
+ recordHeaders: [
252
+ { name: 'accept', json: false },
253
+ { name: 'x-github-api-version', json: false }
254
+ ],
255
+ failClosed: {
256
+ unrecognised: githubUnrecognisedReason,
257
+ unrecognisedAuthorization: githubUnrecognisedAuthorizationReason
258
+ },
259
+ // No cursors: label pages are page numbers, as GitHub's are.
260
+ clearRuntime: () => undefined,
261
+ runtimeState: () => ({}),
262
+ seedSummary: state => ({
263
+ labels: state.labels.length,
264
+ issues: state.issues.length,
265
+ files: state.files.length
266
+ }),
267
+ inputInvalid
268
+ },
269
+ statefulCoreRuntime({ name: 'github', initial, decodeState, inputInvalid })
270
+ )
271
+ }