@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.
- package/LICENSE +21 -0
- package/README.md +2135 -0
- package/dist/anthropic.d.mts +269 -0
- package/dist/anthropic.d.mts.map +1 -0
- package/dist/anthropic.mjs +177 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/chat-completions.d.mts +288 -0
- package/dist/chat-completions.d.mts.map +1 -0
- package/dist/chat-completions.mjs +451 -0
- package/dist/chat-completions.mjs.map +1 -0
- package/dist/codex.d.mts +265 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +199 -0
- package/dist/codex.mjs.map +1 -0
- package/dist/dropbox/api.d.mts +63 -0
- package/dist/dropbox/api.d.mts.map +1 -0
- package/dist/dropbox/api.mjs +565 -0
- package/dist/dropbox/api.mjs.map +1 -0
- package/dist/dropbox/state.d.mts +136 -0
- package/dist/dropbox/state.d.mts.map +1 -0
- package/dist/dropbox/state.mjs +209 -0
- package/dist/dropbox/state.mjs.map +1 -0
- package/dist/dropbox.d.mts +79 -0
- package/dist/dropbox.d.mts.map +1 -0
- package/dist/dropbox.mjs +124 -0
- package/dist/dropbox.mjs.map +1 -0
- package/dist/email-fixtures.d.mts +36 -0
- package/dist/email-fixtures.d.mts.map +1 -0
- package/dist/email-fixtures.mjs +1080 -0
- package/dist/email-fixtures.mjs.map +1 -0
- package/dist/email.d.mts +160 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +608 -0
- package/dist/email.mjs.map +1 -0
- package/dist/emulator-compose.d.mts +40 -0
- package/dist/emulator-compose.d.mts.map +1 -0
- package/dist/emulator-compose.mjs +91 -0
- package/dist/emulator-compose.mjs.map +1 -0
- package/dist/emulator-http.d.mts +53 -0
- package/dist/emulator-http.d.mts.map +1 -0
- package/dist/emulator-http.mjs +117 -0
- package/dist/emulator-http.mjs.map +1 -0
- package/dist/emulator-kernel.d.mts +176 -0
- package/dist/emulator-kernel.d.mts.map +1 -0
- package/dist/emulator-kernel.mjs +413 -0
- package/dist/emulator-kernel.mjs.map +1 -0
- package/dist/fixture-route.d.mts +129 -0
- package/dist/fixture-route.d.mts.map +1 -0
- package/dist/fixture-route.mjs +257 -0
- package/dist/fixture-route.mjs.map +1 -0
- package/dist/fortnox/api.d.mts +92 -0
- package/dist/fortnox/api.d.mts.map +1 -0
- package/dist/fortnox/api.mjs +750 -0
- package/dist/fortnox/api.mjs.map +1 -0
- package/dist/fortnox/state.d.mts +533 -0
- package/dist/fortnox/state.d.mts.map +1 -0
- package/dist/fortnox/state.mjs +612 -0
- package/dist/fortnox/state.mjs.map +1 -0
- package/dist/fortnox.d.mts +128 -0
- package/dist/fortnox.d.mts.map +1 -0
- package/dist/fortnox.mjs +403 -0
- package/dist/fortnox.mjs.map +1 -0
- package/dist/gateway-evaluate-recordings.d.mts +12 -0
- package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
- package/dist/gateway-evaluate-recordings.mjs +128 -0
- package/dist/gateway-evaluate-recordings.mjs.map +1 -0
- package/dist/gateway.d.mts +272 -0
- package/dist/gateway.d.mts.map +1 -0
- package/dist/gateway.mjs +400 -0
- package/dist/gateway.mjs.map +1 -0
- package/dist/github/api.d.mts +63 -0
- package/dist/github/api.d.mts.map +1 -0
- package/dist/github/api.mjs +527 -0
- package/dist/github/api.mjs.map +1 -0
- package/dist/github/state.d.mts +272 -0
- package/dist/github/state.d.mts.map +1 -0
- package/dist/github/state.mjs +303 -0
- package/dist/github/state.mjs.map +1 -0
- package/dist/github.d.mts +77 -0
- package/dist/github.d.mts.map +1 -0
- package/dist/github.mjs +180 -0
- package/dist/github.mjs.map +1 -0
- package/dist/google/calendar.d.mts +10 -0
- package/dist/google/calendar.d.mts.map +1 -0
- package/dist/google/calendar.mjs +252 -0
- package/dist/google/calendar.mjs.map +1 -0
- package/dist/google/drive.d.mts +8 -0
- package/dist/google/drive.d.mts.map +1 -0
- package/dist/google/drive.mjs +252 -0
- package/dist/google/drive.mjs.map +1 -0
- package/dist/google/gmail.d.mts +10 -0
- package/dist/google/gmail.d.mts.map +1 -0
- package/dist/google/gmail.mjs +591 -0
- package/dist/google/gmail.mjs.map +1 -0
- package/dist/google/shared.d.mts +107 -0
- package/dist/google/shared.d.mts.map +1 -0
- package/dist/google/shared.mjs +146 -0
- package/dist/google/shared.mjs.map +1 -0
- package/dist/google/state.d.mts +385 -0
- package/dist/google/state.d.mts.map +1 -0
- package/dist/google/state.mjs +496 -0
- package/dist/google/state.mjs.map +1 -0
- package/dist/google.d.mts +75 -0
- package/dist/google.d.mts.map +1 -0
- package/dist/google.mjs +210 -0
- package/dist/google.mjs.map +1 -0
- package/dist/linkedin-search/api.d.mts +45 -0
- package/dist/linkedin-search/api.d.mts.map +1 -0
- package/dist/linkedin-search/api.mjs +181 -0
- package/dist/linkedin-search/api.mjs.map +1 -0
- package/dist/linkedin-search/state.d.mts +123 -0
- package/dist/linkedin-search/state.d.mts.map +1 -0
- package/dist/linkedin-search/state.mjs +261 -0
- package/dist/linkedin-search/state.mjs.map +1 -0
- package/dist/linkedin-search.d.mts +69 -0
- package/dist/linkedin-search.d.mts.map +1 -0
- package/dist/linkedin-search.mjs +158 -0
- package/dist/linkedin-search.mjs.map +1 -0
- package/dist/mcp/api.d.mts +44 -0
- package/dist/mcp/api.d.mts.map +1 -0
- package/dist/mcp/api.mjs +557 -0
- package/dist/mcp/api.mjs.map +1 -0
- package/dist/mcp/recordings.d.mts +40 -0
- package/dist/mcp/recordings.d.mts.map +1 -0
- package/dist/mcp/recordings.mjs +2390 -0
- package/dist/mcp/recordings.mjs.map +1 -0
- package/dist/mcp/state.d.mts +71 -0
- package/dist/mcp/state.d.mts.map +1 -0
- package/dist/mcp/state.mjs +83 -0
- package/dist/mcp/state.mjs.map +1 -0
- package/dist/mcp.d.mts +84 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +241 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/messages.d.mts +183 -0
- package/dist/messages.d.mts.map +1 -0
- package/dist/messages.mjs +532 -0
- package/dist/messages.mjs.map +1 -0
- package/dist/microsoft/api.d.mts +47 -0
- package/dist/microsoft/api.d.mts.map +1 -0
- package/dist/microsoft/api.mjs +178 -0
- package/dist/microsoft/api.mjs.map +1 -0
- package/dist/microsoft/calendar.d.mts +30 -0
- package/dist/microsoft/calendar.d.mts.map +1 -0
- package/dist/microsoft/calendar.mjs +271 -0
- package/dist/microsoft/calendar.mjs.map +1 -0
- package/dist/microsoft/drive.d.mts +36 -0
- package/dist/microsoft/drive.d.mts.map +1 -0
- package/dist/microsoft/drive.mjs +298 -0
- package/dist/microsoft/drive.mjs.map +1 -0
- package/dist/microsoft/graph.d.mts +198 -0
- package/dist/microsoft/graph.d.mts.map +1 -0
- package/dist/microsoft/graph.mjs +264 -0
- package/dist/microsoft/graph.mjs.map +1 -0
- package/dist/microsoft/mail.d.mts +48 -0
- package/dist/microsoft/mail.d.mts.map +1 -0
- package/dist/microsoft/mail.mjs +488 -0
- package/dist/microsoft/mail.mjs.map +1 -0
- package/dist/microsoft/state.d.mts +480 -0
- package/dist/microsoft/state.d.mts.map +1 -0
- package/dist/microsoft/state.mjs +625 -0
- package/dist/microsoft/state.mjs.map +1 -0
- package/dist/microsoft.d.mts +168 -0
- package/dist/microsoft.d.mts.map +1 -0
- package/dist/microsoft.mjs +483 -0
- package/dist/microsoft.mjs.map +1 -0
- package/dist/node.d.mts +47 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +178 -0
- package/dist/node.mjs.map +1 -0
- package/dist/notion/api.d.mts +51 -0
- package/dist/notion/api.d.mts.map +1 -0
- package/dist/notion/api.mjs +507 -0
- package/dist/notion/api.mjs.map +1 -0
- package/dist/notion/state.d.mts +329 -0
- package/dist/notion/state.d.mts.map +1 -0
- package/dist/notion/state.mjs +432 -0
- package/dist/notion/state.mjs.map +1 -0
- package/dist/notion.d.mts +72 -0
- package/dist/notion.d.mts.map +1 -0
- package/dist/notion.mjs +127 -0
- package/dist/notion.mjs.map +1 -0
- package/dist/openai.d.mts +165 -0
- package/dist/openai.d.mts.map +1 -0
- package/dist/openai.mjs +141 -0
- package/dist/openai.mjs.map +1 -0
- package/dist/opencode-recordings.d.mts +7 -0
- package/dist/opencode-recordings.d.mts.map +1 -0
- package/dist/opencode-recordings.mjs +222 -0
- package/dist/opencode-recordings.mjs.map +1 -0
- package/dist/opencode.d.mts +70 -0
- package/dist/opencode.d.mts.map +1 -0
- package/dist/opencode.mjs +217 -0
- package/dist/opencode.mjs.map +1 -0
- package/dist/r2-fixtures.d.mts +36 -0
- package/dist/r2-fixtures.d.mts.map +1 -0
- package/dist/r2-fixtures.mjs +245 -0
- package/dist/r2-fixtures.mjs.map +1 -0
- package/dist/r2-guard.d.mts +43 -0
- package/dist/r2-guard.d.mts.map +1 -0
- package/dist/r2-guard.mjs +215 -0
- package/dist/r2-guard.mjs.map +1 -0
- package/dist/r2.d.mts +166 -0
- package/dist/r2.d.mts.map +1 -0
- package/dist/r2.mjs +517 -0
- package/dist/r2.mjs.map +1 -0
- package/dist/responses.d.mts +231 -0
- package/dist/responses.d.mts.map +1 -0
- package/dist/responses.mjs +556 -0
- package/dist/responses.mjs.map +1 -0
- package/dist/route-evidence.d.mts +45 -0
- package/dist/route-evidence.d.mts.map +1 -0
- package/dist/route-evidence.mjs +56 -0
- package/dist/route-evidence.mjs.map +1 -0
- package/dist/router.d.mts +93 -0
- package/dist/router.d.mts.map +1 -0
- package/dist/router.mjs +259 -0
- package/dist/router.mjs.map +1 -0
- package/dist/stateful-core.d.mts +17 -0
- package/dist/stateful-core.d.mts.map +1 -0
- package/dist/stateful-core.mjs +41 -0
- package/dist/stateful-core.mjs.map +1 -0
- package/dist/stateful-emulator.d.mts +565 -0
- package/dist/stateful-emulator.d.mts.map +1 -0
- package/dist/stateful-emulator.mjs +1228 -0
- package/dist/stateful-emulator.mjs.map +1 -0
- package/dist/stateful-secrets.d.mts +84 -0
- package/dist/stateful-secrets.d.mts.map +1 -0
- package/dist/stateful-secrets.mjs +216 -0
- package/dist/stateful-secrets.mjs.map +1 -0
- package/dist/subscription-usage-recordings.d.mts +9 -0
- package/dist/subscription-usage-recordings.d.mts.map +1 -0
- package/dist/subscription-usage-recordings.mjs +53 -0
- package/dist/subscription-usage-recordings.mjs.map +1 -0
- package/dist/subscription-usage.d.mts +53 -0
- package/dist/subscription-usage.d.mts.map +1 -0
- package/dist/subscription-usage.mjs +21 -0
- package/dist/subscription-usage.mjs.map +1 -0
- package/dist/telegram/api.d.mts +34 -0
- package/dist/telegram/api.d.mts.map +1 -0
- package/dist/telegram/api.mjs +257 -0
- package/dist/telegram/api.mjs.map +1 -0
- package/dist/telegram/state.d.mts +128 -0
- package/dist/telegram/state.d.mts.map +1 -0
- package/dist/telegram/state.mjs +154 -0
- package/dist/telegram/state.mjs.map +1 -0
- package/dist/telegram.d.mts +62 -0
- package/dist/telegram.d.mts.map +1 -0
- package/dist/telegram.mjs +127 -0
- package/dist/telegram.mjs.map +1 -0
- package/dist/todoist/api.d.mts +55 -0
- package/dist/todoist/api.d.mts.map +1 -0
- package/dist/todoist/api.mjs +415 -0
- package/dist/todoist/api.mjs.map +1 -0
- package/dist/todoist/state.d.mts +275 -0
- package/dist/todoist/state.d.mts.map +1 -0
- package/dist/todoist/state.mjs +345 -0
- package/dist/todoist/state.mjs.map +1 -0
- package/dist/todoist.d.mts +68 -0
- package/dist/todoist.d.mts.map +1 -0
- package/dist/todoist.mjs +181 -0
- package/dist/todoist.mjs.map +1 -0
- package/dist/xai.d.mts +267 -0
- package/dist/xai.d.mts.map +1 -0
- package/dist/xai.mjs +252 -0
- package/dist/xai.mjs.map +1 -0
- package/package.json +157 -0
- package/src/anthropic.ts +289 -0
- package/src/chat-completions.ts +924 -0
- package/src/codex.ts +296 -0
- package/src/dropbox/api.ts +1084 -0
- package/src/dropbox/state.ts +364 -0
- package/src/dropbox.ts +203 -0
- package/src/email-fixtures.ts +1297 -0
- package/src/email.ts +1108 -0
- package/src/emulator-compose.ts +147 -0
- package/src/emulator-http.ts +184 -0
- package/src/emulator-kernel.ts +844 -0
- package/src/fixture-route.ts +561 -0
- package/src/fortnox/api.ts +1352 -0
- package/src/fortnox/state.ts +798 -0
- package/src/fortnox.ts +801 -0
- package/src/gateway-evaluate-recordings.ts +153 -0
- package/src/gateway.ts +530 -0
- package/src/github/api.ts +986 -0
- package/src/github/state.ts +439 -0
- package/src/github.ts +271 -0
- package/src/google/calendar.ts +486 -0
- package/src/google/drive.ts +471 -0
- package/src/google/gmail.ts +1011 -0
- package/src/google/shared.ts +321 -0
- package/src/google/state.ts +686 -0
- package/src/google.ts +298 -0
- package/src/linkedin-search/api.ts +363 -0
- package/src/linkedin-search/state.ts +373 -0
- package/src/linkedin-search.ts +241 -0
- package/src/mcp/api.ts +995 -0
- package/src/mcp/recordings.ts +2665 -0
- package/src/mcp/state.ts +125 -0
- package/src/mcp.ts +319 -0
- package/src/messages.ts +844 -0
- package/src/microsoft/api.ts +426 -0
- package/src/microsoft/calendar.ts +491 -0
- package/src/microsoft/drive.ts +541 -0
- package/src/microsoft/graph.ts +550 -0
- package/src/microsoft/mail.ts +833 -0
- package/src/microsoft/state.ts +822 -0
- package/src/microsoft.ts +982 -0
- package/src/node.ts +293 -0
- package/src/notion/api.ts +976 -0
- package/src/notion/state.ts +512 -0
- package/src/notion.ts +210 -0
- package/src/openai.ts +198 -0
- package/src/opencode-recordings.ts +257 -0
- package/src/opencode.ts +271 -0
- package/src/r2-fixtures.ts +295 -0
- package/src/r2-guard.ts +323 -0
- package/src/r2.ts +890 -0
- package/src/responses.ts +901 -0
- package/src/route-evidence.ts +90 -0
- package/src/router.ts +490 -0
- package/src/stateful-core.ts +70 -0
- package/src/stateful-emulator.ts +2571 -0
- package/src/stateful-secrets.ts +299 -0
- package/src/subscription-usage-recordings.ts +77 -0
- package/src/subscription-usage.ts +68 -0
- package/src/telegram/api.ts +437 -0
- package/src/telegram/state.ts +229 -0
- package/src/telegram.ts +227 -0
- package/src/todoist/api.ts +736 -0
- package/src/todoist/state.ts +456 -0
- package/src/todoist.ts +316 -0
- package/src/xai.ts +341 -0
package/src/node.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node boundary: serve an emulator fetch handler on loopback.
|
|
3
|
+
*
|
|
4
|
+
* This is the only module in the package that imports `node:` builtins.
|
|
5
|
+
* Servers bind to `127.0.0.1` only. Streamed response bodies are written
|
|
6
|
+
* chunk by chunk (honoring backpressure) so progressive delivery survives the
|
|
7
|
+
* socket, and an erroring body stream destroys the connection mid-response,
|
|
8
|
+
* the way a dropped upstream does.
|
|
9
|
+
*
|
|
10
|
+
* @experimental
|
|
11
|
+
*/
|
|
12
|
+
import { Buffer } from 'node:buffer'
|
|
13
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
|
|
14
|
+
import type { Socket } from 'node:net'
|
|
15
|
+
import { Data, Effect, Predicate, type Scope } from 'effect'
|
|
16
|
+
|
|
17
|
+
/** A fetch handler, such as the one returned by an emulator factory. */
|
|
18
|
+
export type FetchHandler = (request: Request) => Promise<Response>
|
|
19
|
+
|
|
20
|
+
export type ServeFetchHandlerOptions = {
|
|
21
|
+
/** TCP port; `0` (default) picks a free port. */
|
|
22
|
+
readonly port?: number
|
|
23
|
+
/** Only `127.0.0.1` is accepted (the default); any other host is refused. */
|
|
24
|
+
readonly host?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The only host emulator servers bind to. */
|
|
28
|
+
export const emulatorServerHost = '127.0.0.1'
|
|
29
|
+
|
|
30
|
+
/** Serving failed: a refused host, an invalid port, or a listen error. */
|
|
31
|
+
export class EmulatorServeError extends Data.TaggedError('EmulatorServeError')<{
|
|
32
|
+
readonly reason: 'host-not-allowed' | 'invalid-port' | 'listen-failed'
|
|
33
|
+
readonly cause?: unknown
|
|
34
|
+
}> {
|
|
35
|
+
override get message(): string {
|
|
36
|
+
switch (this.reason) {
|
|
37
|
+
case 'host-not-allowed':
|
|
38
|
+
return `Emulator servers bind to ${emulatorServerHost} only`
|
|
39
|
+
case 'invalid-port':
|
|
40
|
+
return 'Emulator server port must be an integer from 0 to 65535'
|
|
41
|
+
case 'listen-failed':
|
|
42
|
+
return 'Emulator server could not listen'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A running loopback server (Promise variant). `close` is idempotent. */
|
|
48
|
+
export type RunningFetchHandlerServer = {
|
|
49
|
+
/** Base URL, for example `http://127.0.0.1:43123`. */
|
|
50
|
+
readonly url: string
|
|
51
|
+
readonly close: () => Promise<void>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A running loopback server (Effect variant). `close` is idempotent. */
|
|
55
|
+
export type ServedFetchHandler = {
|
|
56
|
+
readonly url: string
|
|
57
|
+
readonly close: Effect.Effect<void>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const validateOptions = (options: ServeFetchHandlerOptions): EmulatorServeError | undefined => {
|
|
61
|
+
if (options.host !== undefined && options.host !== emulatorServerHost) {
|
|
62
|
+
return new EmulatorServeError({ reason: 'host-not-allowed' })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const port = options.port ?? 0
|
|
66
|
+
|
|
67
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
68
|
+
return new EmulatorServeError({ reason: 'invalid-port' })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const readBody = (incoming: IncomingMessage): Promise<Uint8Array<ArrayBuffer>> =>
|
|
75
|
+
new Promise((resolve, reject) => {
|
|
76
|
+
const chunks: Array<Buffer> = []
|
|
77
|
+
|
|
78
|
+
incoming.on('data', (chunk: Buffer | string) => {
|
|
79
|
+
chunks.push(Predicate.isString(chunk) ? Buffer.from(chunk) : chunk)
|
|
80
|
+
})
|
|
81
|
+
incoming.on('end', () => resolve(new Uint8Array(Buffer.concat(chunks))))
|
|
82
|
+
incoming.on('error', reject)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const toWebRequest = async (incoming: IncomingMessage, origin: string): Promise<Request> => {
|
|
86
|
+
const headers = new Headers()
|
|
87
|
+
|
|
88
|
+
for (const [name, value] of Object.entries(incoming.headers)) {
|
|
89
|
+
if (Array.isArray(value)) {
|
|
90
|
+
for (const item of value) headers.append(name, item)
|
|
91
|
+
} else if (value !== undefined) {
|
|
92
|
+
headers.set(name, value)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const method = incoming.method ?? 'GET'
|
|
97
|
+
const body = await readBody(incoming)
|
|
98
|
+
const init: RequestInit = { method, headers }
|
|
99
|
+
|
|
100
|
+
if (method !== 'GET' && method !== 'HEAD' && body.byteLength > 0) {
|
|
101
|
+
init.body = body
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new Request(new URL(incoming.url ?? '/', origin), init)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const waitForDrain = (outgoing: ServerResponse): Promise<void> =>
|
|
108
|
+
new Promise(resolve => {
|
|
109
|
+
const done = () => {
|
|
110
|
+
outgoing.off('drain', done)
|
|
111
|
+
outgoing.off('close', done)
|
|
112
|
+
resolve()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
outgoing.on('drain', done)
|
|
116
|
+
outgoing.on('close', done)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Write a web `Response` to a Node response: status and headers first, then
|
|
121
|
+
* each body chunk as soon as the handler produces it. A body stream error
|
|
122
|
+
* destroys the socket mid-response.
|
|
123
|
+
*/
|
|
124
|
+
const writeResponse = async (outgoing: ServerResponse, response: Response): Promise<void> => {
|
|
125
|
+
outgoing.statusCode = response.status
|
|
126
|
+
|
|
127
|
+
if (response.statusText.length > 0) {
|
|
128
|
+
outgoing.statusMessage = response.statusText
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
response.headers.forEach((value, name) => {
|
|
132
|
+
if (name !== 'set-cookie') outgoing.setHeader(name, value)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const cookies = response.headers.getSetCookie()
|
|
136
|
+
|
|
137
|
+
if (cookies.length > 0) {
|
|
138
|
+
outgoing.setHeader('set-cookie', cookies)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (response.body === null) {
|
|
142
|
+
outgoing.end()
|
|
143
|
+
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
outgoing.flushHeaders()
|
|
148
|
+
|
|
149
|
+
const reader = response.body.getReader()
|
|
150
|
+
|
|
151
|
+
outgoing.on('close', () => {
|
|
152
|
+
if (!outgoing.writableFinished) {
|
|
153
|
+
reader.cancel().catch(() => undefined)
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const pump = async (): Promise<void> => {
|
|
158
|
+
const { done, value } = await reader.read()
|
|
159
|
+
|
|
160
|
+
if (done || outgoing.destroyed) {
|
|
161
|
+
outgoing.end()
|
|
162
|
+
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!outgoing.write(value)) {
|
|
167
|
+
await waitForDrain(outgoing)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return pump()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Destroy without an error argument: the client sees a dropped connection, and no unhandled
|
|
174
|
+
// 'error' event is emitted on the response.
|
|
175
|
+
await pump().catch(() => {
|
|
176
|
+
outgoing.destroy()
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const handleRequest = (
|
|
181
|
+
handler: FetchHandler,
|
|
182
|
+
origin: string,
|
|
183
|
+
incoming: IncomingMessage,
|
|
184
|
+
outgoing: ServerResponse
|
|
185
|
+
): Promise<void> =>
|
|
186
|
+
toWebRequest(incoming, origin)
|
|
187
|
+
.then(handler)
|
|
188
|
+
.then(
|
|
189
|
+
response => writeResponse(outgoing, response),
|
|
190
|
+
() => {
|
|
191
|
+
if (outgoing.headersSent) {
|
|
192
|
+
outgoing.destroy()
|
|
193
|
+
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
outgoing.statusCode = 500
|
|
198
|
+
outgoing.setHeader('content-type', 'text/plain')
|
|
199
|
+
outgoing.end('emulator handler failed')
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
// A response that cannot be written (for example an invalid header value) drops the connection.
|
|
203
|
+
.catch(() => {
|
|
204
|
+
outgoing.destroy()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Serve a fetch handler on `127.0.0.1` (Promise variant, for hosts without an
|
|
209
|
+
* Effect runtime). Rejects with `EmulatorServeError` for a non-loopback host,
|
|
210
|
+
* an invalid port, or a listen failure.
|
|
211
|
+
*/
|
|
212
|
+
export const startFetchHandlerServer = (
|
|
213
|
+
handler: FetchHandler,
|
|
214
|
+
options: ServeFetchHandlerOptions = {}
|
|
215
|
+
): Promise<RunningFetchHandlerServer> => {
|
|
216
|
+
const invalid = validateOptions(options)
|
|
217
|
+
|
|
218
|
+
if (invalid !== undefined) {
|
|
219
|
+
return Promise.reject(invalid)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const sockets = new Set<Socket>()
|
|
223
|
+
const server = createServer()
|
|
224
|
+
let origin = ''
|
|
225
|
+
|
|
226
|
+
server.on('connection', socket => {
|
|
227
|
+
socket.setNoDelay(true)
|
|
228
|
+
sockets.add(socket)
|
|
229
|
+
socket.on('close', () => sockets.delete(socket))
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
server.on('request', (incoming: IncomingMessage, outgoing: ServerResponse) => {
|
|
233
|
+
void handleRequest(handler, origin, incoming, outgoing)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
let closing: Promise<void> | undefined
|
|
237
|
+
|
|
238
|
+
const close = (): Promise<void> => {
|
|
239
|
+
closing ??= new Promise(resolve => {
|
|
240
|
+
server.close(() => resolve())
|
|
241
|
+
|
|
242
|
+
for (const socket of sockets) socket.destroy()
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
return closing
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
server.once('error', cause =>
|
|
250
|
+
reject(new EmulatorServeError({ reason: 'listen-failed', cause }))
|
|
251
|
+
)
|
|
252
|
+
server.listen(options.port ?? 0, emulatorServerHost, () => {
|
|
253
|
+
const address = server.address()
|
|
254
|
+
|
|
255
|
+
if (address === null || Predicate.isString(address)) {
|
|
256
|
+
void close()
|
|
257
|
+
reject(new EmulatorServeError({ reason: 'listen-failed' }))
|
|
258
|
+
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
origin = `http://${emulatorServerHost}:${address.port}`
|
|
263
|
+
resolve({ url: origin, close })
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Serve a fetch handler on `127.0.0.1` as a scoped resource: the server
|
|
270
|
+
* closes when the scope closes (or earlier via `close`). Fails with
|
|
271
|
+
* `EmulatorServeError` for a non-loopback host, an invalid port, or a listen
|
|
272
|
+
* failure.
|
|
273
|
+
*/
|
|
274
|
+
export const serveFetchHandler = (
|
|
275
|
+
handler: FetchHandler,
|
|
276
|
+
options: ServeFetchHandlerOptions = {}
|
|
277
|
+
): Effect.Effect<ServedFetchHandler, EmulatorServeError, Scope.Scope> =>
|
|
278
|
+
Effect.acquireRelease(
|
|
279
|
+
Effect.suspend(() => {
|
|
280
|
+
const invalid = validateOptions(options)
|
|
281
|
+
|
|
282
|
+
return invalid === undefined
|
|
283
|
+
? Effect.tryPromise({
|
|
284
|
+
try: () => startFetchHandlerServer(handler, options),
|
|
285
|
+
catch: cause =>
|
|
286
|
+
cause instanceof EmulatorServeError
|
|
287
|
+
? cause
|
|
288
|
+
: new EmulatorServeError({ reason: 'listen-failed', cause })
|
|
289
|
+
})
|
|
290
|
+
: Effect.fail(invalid)
|
|
291
|
+
}),
|
|
292
|
+
server => Effect.promise(() => server.close())
|
|
293
|
+
).pipe(Effect.map(server => ({ url: server.url, close: Effect.promise(() => server.close()) })))
|