@expandai/sdk 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -4
- package/build/dts/AppendixMarkers.d.ts +30 -0
- package/build/dts/AppendixMarkers.d.ts.map +1 -0
- package/build/dts/Client.d.ts +41 -6
- package/build/dts/Client.d.ts.map +1 -1
- package/build/dts/Crawls.d.ts +568 -0
- package/build/dts/Crawls.d.ts.map +1 -0
- package/build/dts/EnvProxyHttpClient.d.ts +28 -0
- package/build/dts/EnvProxyHttpClient.d.ts.map +1 -0
- package/build/dts/FetchJson.d.ts +444 -37
- package/build/dts/FetchJson.d.ts.map +1 -1
- package/build/dts/Generated.d.ts +1545 -788
- package/build/dts/Generated.d.ts.map +1 -1
- package/build/dts/HttpClientHelpers.d.ts +13 -0
- package/build/dts/HttpClientHelpers.d.ts.map +1 -1
- package/build/dts/Internal.d.ts +6 -4
- package/build/dts/Internal.d.ts.map +1 -1
- package/build/dts/ParseErrorBody.d.ts +26 -0
- package/build/dts/ParseErrorBody.d.ts.map +1 -0
- package/build/dts/Playground.d.ts +20 -0
- package/build/dts/Playground.d.ts.map +1 -1
- package/build/dts/RetryAfter.d.ts +40 -0
- package/build/dts/RetryAfter.d.ts.map +1 -0
- package/build/dts/Service.d.ts +94 -45
- package/build/dts/Service.d.ts.map +1 -1
- package/build/dts/StreamPage.d.ts +10 -0
- package/build/dts/StreamPage.d.ts.map +1 -0
- package/build/dts/index.d.ts +9 -2
- package/build/dts/index.d.ts.map +1 -1
- package/build/esm/AppendixMarkers.js +307 -0
- package/build/esm/AppendixMarkers.js.map +1 -0
- package/build/esm/Client.js +96 -15
- package/build/esm/Client.js.map +1 -1
- package/build/esm/Crawls.js +786 -0
- package/build/esm/Crawls.js.map +1 -0
- package/build/esm/EnvProxyHttpClient.js +55 -0
- package/build/esm/EnvProxyHttpClient.js.map +1 -0
- package/build/esm/FetchJson.js +48 -22
- package/build/esm/FetchJson.js.map +1 -1
- package/build/esm/Generated.js +540 -112
- package/build/esm/Generated.js.map +1 -1
- package/build/esm/HttpClientHelpers.js +36 -1
- package/build/esm/HttpClientHelpers.js.map +1 -1
- package/build/esm/Internal.js +140 -33
- package/build/esm/Internal.js.map +1 -1
- package/build/esm/ParseErrorBody.js +41 -0
- package/build/esm/ParseErrorBody.js.map +1 -0
- package/build/esm/Playground.js +38 -0
- package/build/esm/Playground.js.map +1 -1
- package/build/esm/RetryAfter.js +104 -0
- package/build/esm/RetryAfter.js.map +1 -0
- package/build/esm/Service.js +96 -17
- package/build/esm/Service.js.map +1 -1
- package/build/esm/StreamPage.js +46 -0
- package/build/esm/StreamPage.js.map +1 -0
- package/build/esm/index.js +5 -1
- package/build/esm/index.js.map +1 -1
- package/package.json +8 -2
- package/src/AppendixMarkers.ts +370 -0
- package/src/Client.ts +131 -16
- package/src/Crawls.ts +1387 -0
- package/src/EnvProxyHttpClient.ts +71 -0
- package/src/FetchJson.ts +63 -25
- package/src/Generated.ts +738 -162
- package/src/HttpClientHelpers.ts +48 -1
- package/src/Internal.ts +163 -105
- package/src/ParseErrorBody.ts +42 -0
- package/src/Playground.ts +48 -0
- package/src/RetryAfter.ts +138 -0
- package/src/Service.ts +208 -25
- package/src/StreamPage.ts +47 -0
- package/src/index.ts +29 -3
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { HttpClient } from '@effect/platform'
|
|
2
|
+
import { NodeHttpClient, Undici } from '@effect/platform-node'
|
|
3
|
+
import { Effect, Layer, Option } from 'effect'
|
|
4
|
+
import { ExpandSdkError } from './Error.js'
|
|
5
|
+
|
|
6
|
+
// Undici resolves each protocol's proxy as `lowercase ?? UPPERCASE` — the
|
|
7
|
+
// lowercase variant wins even when empty, and an empty effective value means
|
|
8
|
+
// "no proxy". `NO_PROXY` never fails to parse, so it is not checked.
|
|
9
|
+
const PROXY_ENV_SOURCES: ReadonlyArray<readonly [string, string]> = [
|
|
10
|
+
['http_proxy', 'HTTP_PROXY'],
|
|
11
|
+
['https_proxy', 'HTTPS_PROXY'],
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Describes the first configured proxy environment variable whose value is not
|
|
16
|
+
* a valid URL, or `None` when the proxy configuration is usable. Undici's
|
|
17
|
+
* `EnvHttpProxyAgent` throws a raw `TypeError: Invalid URL` mid-construction
|
|
18
|
+
* on such a value; callers surface this message early instead (the SDK's
|
|
19
|
+
* `buildServiceConfig`, the CLI's startup preflight). Checked with the same
|
|
20
|
+
* effective-value precedence undici applies, so a value undici would never
|
|
21
|
+
* parse is never rejected here.
|
|
22
|
+
*/
|
|
23
|
+
export function invalidProxyEnvMessage(): Option.Option<string> {
|
|
24
|
+
for (const [lowercase, uppercase] of PROXY_ENV_SOURCES) {
|
|
25
|
+
const name = process.env[lowercase] !== undefined ? lowercase : uppercase
|
|
26
|
+
const value = process.env[name]
|
|
27
|
+
if (value === undefined || value === '') {
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
new URL(value)
|
|
32
|
+
} catch {
|
|
33
|
+
// Mirrors undici: any value `new URL` rejects would crash EnvHttpProxyAgent.
|
|
34
|
+
return Option.some(
|
|
35
|
+
`Invalid proxy URL in the ${name} environment variable: ${JSON.stringify(value)}. Fix or unset it.`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return Option.none()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `HttpClient` layer honoring the standard proxy environment variables
|
|
44
|
+
* `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` (upper- or lowercase).
|
|
45
|
+
*
|
|
46
|
+
* Requests dispatch through undici's `EnvHttpProxyAgent`, which resolves the
|
|
47
|
+
* proxy configuration at acquisition and degrades to a plain undici `Agent`
|
|
48
|
+
* when no proxy variable is set.
|
|
49
|
+
*
|
|
50
|
+
* A malformed proxy URL dies with a descriptive `ExpandSdkError` rather than
|
|
51
|
+
* failing typed: an error channel here would ripple through every dependent
|
|
52
|
+
* service's `Default` layer type, and the callers that can act on the problem
|
|
53
|
+
* already validate eagerly via {@link invalidProxyEnvMessage}. The die is the
|
|
54
|
+
* safety net for paths that skip that, e.g. the CLI's exported MCP server.
|
|
55
|
+
*/
|
|
56
|
+
export const layer: Layer.Layer<HttpClient.HttpClient> = NodeHttpClient.layerUndiciWithoutDispatcher.pipe(
|
|
57
|
+
Layer.provide(
|
|
58
|
+
Layer.scoped(
|
|
59
|
+
NodeHttpClient.Dispatcher,
|
|
60
|
+
Effect.acquireRelease(
|
|
61
|
+
Effect.suspend(() =>
|
|
62
|
+
Option.match(invalidProxyEnvMessage(), {
|
|
63
|
+
onSome: (message) => Effect.die(new ExpandSdkError({ message })),
|
|
64
|
+
onNone: () => Effect.sync(() => new Undici.EnvHttpProxyAgent()),
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
(dispatcher) => Effect.promise(() => dispatcher.destroy()),
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
)
|
package/src/FetchJson.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// The OpenAPI spec declares `json` on each search snippet (the extracted State JSON value), but
|
|
4
4
|
// the code generator drops the bare `unknown`-typed field, so `Generated.FetchObjectModeResult`
|
|
5
|
-
// / `Generated.
|
|
5
|
+
// / `Generated.FetchSnapshotHighlightsResult` decode it away. These hand-written schemas re-add
|
|
6
6
|
// `json`, and the request functions mirror the generated client but decode the 200 body with
|
|
7
7
|
// them — so callers that need to surface the cited JSON get it. Kept out of `Generated.ts` so the
|
|
8
8
|
// OpenAPI conformance test (which checks the generated schemas) is unaffected.
|
|
@@ -10,38 +10,69 @@
|
|
|
10
10
|
import { HttpClient, HttpClientRequest, HttpClientResponse } from '@effect/platform'
|
|
11
11
|
import { Effect, Schema as S } from 'effect'
|
|
12
12
|
import * as Generated from './Generated.js'
|
|
13
|
-
import { decodeError, unexpectedStatus } from './HttpClientHelpers.js'
|
|
13
|
+
import { decodeError, decodeSuccess, unexpectedStatus } from './HttpClientHelpers.js'
|
|
14
14
|
|
|
15
15
|
/** Search snippet with the extracted `json` value preserved (statejson snippets only). */
|
|
16
|
-
export const
|
|
17
|
-
...Generated.
|
|
16
|
+
export const FetchHighlightsSnippetWithJson = S.Struct({
|
|
17
|
+
...Generated.FetchHighlightsResult.fields.snippets.value.fields,
|
|
18
18
|
json: S.optionalWith(S.Unknown, { nullable: true }),
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
export const
|
|
22
|
-
...Generated.
|
|
23
|
-
snippets: S.Array(
|
|
21
|
+
export const FetchHighlightsResultWithJson = S.Struct({
|
|
22
|
+
...Generated.FetchHighlightsResult.fields,
|
|
23
|
+
snippets: S.Array(FetchHighlightsSnippetWithJson),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/** Object-mode evidence with the inline JSON payload preserved. */
|
|
27
|
+
export const FetchObjectModeEvidenceWithJson = S.Struct({
|
|
28
|
+
...Generated.FetchObjectModeEvidence.fields,
|
|
29
|
+
body: S.optionalWith(S.Unknown, { nullable: true }),
|
|
24
30
|
})
|
|
25
31
|
|
|
26
32
|
export class FetchObjectModeResultWithJson extends S.Class<FetchObjectModeResultWithJson>(
|
|
27
33
|
'FetchObjectModeResultWithJson',
|
|
28
34
|
)({
|
|
29
35
|
...Generated.FetchObjectModeResult.fields,
|
|
36
|
+
json: S.Array(FetchObjectModeEvidenceWithJson),
|
|
30
37
|
data: S.optionalWith(
|
|
31
38
|
S.Struct({
|
|
32
39
|
...Generated.FetchObjectModeResult.fields.data.from.fields,
|
|
33
|
-
|
|
40
|
+
highlights: S.optionalWith(FetchHighlightsResultWithJson, { nullable: true }),
|
|
34
41
|
}),
|
|
35
42
|
{ nullable: true },
|
|
36
43
|
),
|
|
44
|
+
originError: S.optionalWith(Generated.FetchHttpOriginError, { nullable: true }),
|
|
37
45
|
}) {}
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The 200 body of `/v1/fetch/json`. An origin that never answered has no
|
|
49
|
+
* content to return, so the endpoint reports its outcome as the whole body
|
|
50
|
+
* rather than as an HTTP error status — a non-2xx there would mean Expand
|
|
51
|
+
* failed, which it did not.
|
|
52
|
+
*
|
|
53
|
+
* Discriminate on `_tag`, which only `FetchOriginError` carries.
|
|
54
|
+
*/
|
|
55
|
+
export const FetchObjectModeJsonResponse = S.Union(
|
|
56
|
+
FetchObjectModeResultWithJson,
|
|
57
|
+
Generated.FetchNetworkOriginError,
|
|
58
|
+
Generated.FetchHttpOriginRefusal,
|
|
59
|
+
)
|
|
60
|
+
export type FetchObjectModeJsonResponse = typeof FetchObjectModeJsonResponse.Type
|
|
61
|
+
|
|
62
|
+
/** The unreachable-origin member of `FetchObjectModeJsonResponse`, after narrowing with `isFetchOriginError`. */
|
|
63
|
+
export type FetchOriginErrorResponse = Extract<FetchObjectModeJsonResponse, { readonly _tag: 'FetchOriginError' }>
|
|
64
|
+
|
|
65
|
+
/** Narrows a `FetchObjectModeJsonResponse` to the unreachable-origin case — the only variant with a `_tag`. */
|
|
66
|
+
export function isFetchOriginError(response: FetchObjectModeJsonResponse): response is FetchOriginErrorResponse {
|
|
67
|
+
return '_tag' in response && response._tag === 'FetchOriginError'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class FetchSnapshotHighlightsResultWithJson extends S.Class<FetchSnapshotHighlightsResultWithJson>(
|
|
71
|
+
'FetchSnapshotHighlightsResultWithJson',
|
|
41
72
|
)({
|
|
42
73
|
snapshotId: S.String.pipe(S.minLength(1)),
|
|
43
74
|
response: Generated.ResponseInfo,
|
|
44
|
-
|
|
75
|
+
highlights: FetchHighlightsResultWithJson,
|
|
45
76
|
durationMs: S.Number.pipe(S.greaterThanOrEqualTo(0)),
|
|
46
77
|
playground: S.String,
|
|
47
78
|
}) {}
|
|
@@ -61,15 +92,21 @@ export const fetchObjectModeJson = (
|
|
|
61
92
|
Effect.flatMap(
|
|
62
93
|
httpClient.execute(request),
|
|
63
94
|
HttpClientResponse.matchStatus({
|
|
64
|
-
|
|
95
|
+
// `decodeSuccess`, not a raw schema decode: a 2xx body this SDK cannot read is
|
|
96
|
+
// deterministic skew, and retrying it re-runs a full capture per attempt.
|
|
97
|
+
'200': (response) => decodeSuccess(response, FetchObjectModeJsonResponse),
|
|
65
98
|
'400': (response) => decodeError(response, Generated.FetchFetchJson400),
|
|
66
99
|
'401': (response) => decodeError(response, Generated.AuthFailed),
|
|
67
100
|
'403': (response) => decodeError(response, Generated.FetchFetchJson403),
|
|
68
|
-
'
|
|
69
|
-
'
|
|
101
|
+
'404': (response) => decodeError(response, Generated.FetchOriginNotFound),
|
|
102
|
+
'410': (response) => decodeError(response, Generated.FetchOriginGone),
|
|
103
|
+
'413': (response) => decodeError(response, Generated.FetchFetchJson413),
|
|
104
|
+
'415': (response) => decodeError(response, Generated.FetchUnsupportedContentType),
|
|
105
|
+
'422': (response) => decodeError(response, Generated.FetchVideoUnavailable),
|
|
106
|
+
'429': (response) => decodeError(response, Generated.FetchFetchJson429),
|
|
70
107
|
'500': (response) => decodeError(response, Generated.InternalError),
|
|
71
|
-
'502': (response) => decodeError(response, Generated.
|
|
72
|
-
'503': (response) => decodeError(response, Generated.
|
|
108
|
+
'502': (response) => decodeError(response, Generated.FetchFetchJson502),
|
|
109
|
+
'503': (response) => decodeError(response, Generated.FetchFetchJson503),
|
|
73
110
|
'504': (response) => decodeError(response, Generated.FetchCaptureTimeout),
|
|
74
111
|
'529': (response) => decodeError(response, Generated.FetchCapacityTimeout),
|
|
75
112
|
orElse: (response) => unexpectedStatus(request, response),
|
|
@@ -78,24 +115,25 @@ export const fetchObjectModeJson = (
|
|
|
78
115
|
),
|
|
79
116
|
)
|
|
80
117
|
|
|
81
|
-
/** Mirrors `Generated.Client['
|
|
82
|
-
export const
|
|
118
|
+
/** Mirrors `Generated.Client['snapshotsHighlights']` but decodes the 200 body with `json` preserved. */
|
|
119
|
+
export const fetchSnapshotHighlights = (
|
|
83
120
|
httpClient: HttpClient.HttpClient,
|
|
84
|
-
|
|
121
|
+
snapshotId: string,
|
|
122
|
+
options: typeof Generated.SnapshotHighlightsInput.Encoded,
|
|
85
123
|
) =>
|
|
86
|
-
HttpClientRequest.make('POST')(
|
|
124
|
+
HttpClientRequest.make('POST')(`/v1/snapshots/${encodeURIComponent(snapshotId)}/highlights`).pipe(
|
|
87
125
|
(request) => Effect.orDie(HttpClientRequest.bodyJson(request, options)),
|
|
88
126
|
Effect.flatMap((request) =>
|
|
89
127
|
Effect.flatMap(
|
|
90
128
|
httpClient.execute(request),
|
|
91
129
|
HttpClientResponse.matchStatus({
|
|
92
|
-
'200': (response) =>
|
|
93
|
-
'400': (response) => decodeError(response, Generated.
|
|
130
|
+
'200': (response) => decodeSuccess(response, FetchSnapshotHighlightsResultWithJson),
|
|
131
|
+
'400': (response) => decodeError(response, Generated.SnapshotsHighlights400),
|
|
94
132
|
'401': (response) => decodeError(response, Generated.AuthFailed),
|
|
95
|
-
'403': (response) => decodeError(response, Generated.
|
|
96
|
-
'404': (response) => decodeError(response, Generated.
|
|
133
|
+
'403': (response) => decodeError(response, Generated.SnapshotsHighlights403),
|
|
134
|
+
'404': (response) => decodeError(response, Generated.FetchSnapshotHighlightsNotFound),
|
|
97
135
|
'413': (response) => decodeError(response, Generated.PayloadTooLarge),
|
|
98
|
-
'429': (response) => decodeError(response, Generated.
|
|
136
|
+
'429': (response) => decodeError(response, Generated.SnapshotsHighlights429),
|
|
99
137
|
'500': (response) => decodeError(response, Generated.InternalError),
|
|
100
138
|
'503': (response) => decodeError(response, Generated.ServiceUnavailable),
|
|
101
139
|
orElse: (response) => unexpectedStatus(request, response),
|