@expandai/sdk 0.18.0 → 0.19.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/build/dts/FetchJson.d.ts +94 -61
- package/build/dts/FetchJson.d.ts.map +1 -1
- package/build/dts/Generated.d.ts +149 -10
- package/build/dts/Generated.d.ts.map +1 -1
- package/build/dts/Internal.d.ts.map +1 -1
- package/build/dts/Service.d.ts +15 -0
- package/build/dts/Service.d.ts.map +1 -1
- package/build/esm/FetchJson.js +7 -11
- package/build/esm/FetchJson.js.map +1 -1
- package/build/esm/Generated.js +57 -3
- package/build/esm/Generated.js.map +1 -1
- package/build/esm/Internal.js +22 -3
- package/build/esm/Internal.js.map +1 -1
- package/build/esm/Service.js +23 -1
- package/build/esm/Service.js.map +1 -1
- package/package.json +3 -2
- package/src/FetchJson.ts +7 -11
- package/src/Generated.ts +66 -3
- package/src/Internal.ts +31 -4
- package/src/Service.ts +40 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expandai/sdk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "TypeScript SDK for ExpandAI",
|
|
7
7
|
"author": "ExpandAI",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"./*": {
|
|
45
45
|
"types": "./build/dts/*.d.ts",
|
|
46
46
|
"import": "./build/esm/*.js"
|
|
47
|
-
}
|
|
47
|
+
},
|
|
48
|
+
"./internal/*": "./src/*.ts"
|
|
48
49
|
},
|
|
49
50
|
"scripts": {
|
|
50
51
|
"generate": "tsx scripts/generate-client.ts",
|
package/src/FetchJson.ts
CHANGED
|
@@ -14,29 +14,22 @@ import { decodeError, unexpectedStatus } from './HttpClientHelpers.js'
|
|
|
14
14
|
|
|
15
15
|
/** Search snippet with the extracted `json` value preserved (statejson snippets only). */
|
|
16
16
|
export const FetchSearchSnippetWithJson = S.Struct({
|
|
17
|
-
|
|
18
|
-
source: S.Literal('markdown', 'appendix', 'statejson'),
|
|
19
|
-
text: S.String,
|
|
17
|
+
...Generated.FetchSearchResult.fields.snippets.value.fields,
|
|
20
18
|
json: S.optionalWith(S.Unknown, { nullable: true }),
|
|
21
|
-
score: S.Number.pipe(S.greaterThanOrEqualTo(0), S.lessThanOrEqualTo(1)),
|
|
22
|
-
location: S.optionalWith(Generated.FetchSearchSnippetLocation, { nullable: true }),
|
|
23
19
|
})
|
|
24
20
|
|
|
25
21
|
export const FetchSearchResultWithJson = S.Struct({
|
|
26
|
-
|
|
22
|
+
...Generated.FetchSearchResult.fields,
|
|
27
23
|
snippets: S.Array(FetchSearchSnippetWithJson),
|
|
28
|
-
meta: S.optionalWith(Generated.FetchSearchMeta, { nullable: true }),
|
|
29
24
|
})
|
|
30
25
|
|
|
31
26
|
export class FetchObjectModeResultWithJson extends S.Class<FetchObjectModeResultWithJson>(
|
|
32
27
|
'FetchObjectModeResultWithJson',
|
|
33
28
|
)({
|
|
34
|
-
|
|
35
|
-
markdown: S.String,
|
|
36
|
-
json: S.Array(Generated.FetchObjectModeEvidence),
|
|
29
|
+
...Generated.FetchObjectModeResult.fields,
|
|
37
30
|
data: S.optionalWith(
|
|
38
31
|
S.Struct({
|
|
39
|
-
|
|
32
|
+
...Generated.FetchObjectModeResult.fields.data.from.fields,
|
|
40
33
|
search: S.optionalWith(FetchSearchResultWithJson, { nullable: true }),
|
|
41
34
|
}),
|
|
42
35
|
{ nullable: true },
|
|
@@ -75,7 +68,10 @@ export const fetchObjectModeJson = (
|
|
|
75
68
|
'413': (response) => decodeError(response, Generated.PayloadTooLarge),
|
|
76
69
|
'429': (response) => decodeError(response, Generated.TooManyRequests),
|
|
77
70
|
'500': (response) => decodeError(response, Generated.InternalError),
|
|
71
|
+
'502': (response) => decodeError(response, Generated.FetchNavigationFailed),
|
|
78
72
|
'503': (response) => decodeError(response, Generated.ServiceUnavailable),
|
|
73
|
+
'504': (response) => decodeError(response, Generated.FetchCaptureTimeout),
|
|
74
|
+
'529': (response) => decodeError(response, Generated.FetchCapacityTimeout),
|
|
79
75
|
orElse: (response) => unexpectedStatus(request, response),
|
|
80
76
|
}),
|
|
81
77
|
),
|
package/src/Generated.ts
CHANGED
|
@@ -186,6 +186,16 @@ export class InternalError extends S.Class<InternalError>('InternalError')({
|
|
|
186
186
|
_tag: InternalErrorTag,
|
|
187
187
|
}) {}
|
|
188
188
|
|
|
189
|
+
export class FetchNavigationFailedFailureType extends S.Literal('browserInternalDocument') {}
|
|
190
|
+
|
|
191
|
+
export class FetchNavigationFailedTag extends S.Literal('FetchNavigationFailed') {}
|
|
192
|
+
|
|
193
|
+
export class FetchNavigationFailed extends S.Class<FetchNavigationFailed>('FetchNavigationFailed')({
|
|
194
|
+
url: S.String,
|
|
195
|
+
failureType: FetchNavigationFailedFailureType,
|
|
196
|
+
_tag: FetchNavigationFailedTag,
|
|
197
|
+
}) {}
|
|
198
|
+
|
|
189
199
|
export class ServiceUnavailableTag extends S.Literal('ServiceUnavailable') {}
|
|
190
200
|
|
|
191
201
|
export class ServiceUnavailable extends S.Class<ServiceUnavailable>('ServiceUnavailable')({
|
|
@@ -194,6 +204,22 @@ export class ServiceUnavailable extends S.Class<ServiceUnavailable>('ServiceUnav
|
|
|
194
204
|
_tag: ServiceUnavailableTag,
|
|
195
205
|
}) {}
|
|
196
206
|
|
|
207
|
+
export class FetchCaptureTimeoutTag extends S.Literal('FetchCaptureTimeout') {}
|
|
208
|
+
|
|
209
|
+
export class FetchCaptureTimeout extends S.Class<FetchCaptureTimeout>('FetchCaptureTimeout')({
|
|
210
|
+
url: S.String,
|
|
211
|
+
timeoutMs: S.optionalWith(S.Number, { nullable: true }),
|
|
212
|
+
_tag: FetchCaptureTimeoutTag,
|
|
213
|
+
}) {}
|
|
214
|
+
|
|
215
|
+
export class FetchCapacityTimeoutTag extends S.Literal('FetchCapacityTimeout') {}
|
|
216
|
+
|
|
217
|
+
export class FetchCapacityTimeout extends S.Class<FetchCapacityTimeout>('FetchCapacityTimeout')({
|
|
218
|
+
url: S.String,
|
|
219
|
+
timeoutMs: S.optionalWith(S.Number, { nullable: true }),
|
|
220
|
+
_tag: FetchCapacityTimeoutTag,
|
|
221
|
+
}) {}
|
|
222
|
+
|
|
197
223
|
export class FetchFetchJsonParams extends S.Struct({
|
|
198
224
|
include: S.optionalWith(S.String, { nullable: true }),
|
|
199
225
|
}) {}
|
|
@@ -276,7 +302,7 @@ export class FetchObjectModeStateJsonHint extends S.Struct({
|
|
|
276
302
|
|
|
277
303
|
export class FetchObjectModeMarkdownHint extends S.Struct({
|
|
278
304
|
url: S.String,
|
|
279
|
-
moreTokens: S.Number,
|
|
305
|
+
moreTokens: S.optionalWith(S.Number, { nullable: true }),
|
|
280
306
|
}) {}
|
|
281
307
|
|
|
282
308
|
export class FetchObjectModeMeta extends S.Struct({
|
|
@@ -319,6 +345,10 @@ export class FetchObjectModeEvidenceRole extends S.Literal(
|
|
|
319
345
|
'other',
|
|
320
346
|
) {}
|
|
321
347
|
|
|
348
|
+
export class FetchObjectModeEvidenceUrlRedacted extends S.Literal(true) {}
|
|
349
|
+
|
|
350
|
+
export class FetchObjectModeEvidenceUrlOmitted extends S.Literal('invalid', 'unsafe', 'too-long') {}
|
|
351
|
+
|
|
322
352
|
export class FetchObjectModeEvidence extends S.Struct({
|
|
323
353
|
id: S.Number,
|
|
324
354
|
kind: FetchObjectModeEvidenceKind,
|
|
@@ -328,6 +358,8 @@ export class FetchObjectModeEvidence extends S.Struct({
|
|
|
328
358
|
method: S.optionalWith(S.String, { nullable: true }),
|
|
329
359
|
moreTokens: S.optionalWith(S.Number, { nullable: true }),
|
|
330
360
|
keys: S.optionalWith(S.Array(S.String), { nullable: true }),
|
|
361
|
+
urlRedacted: S.optionalWith(FetchObjectModeEvidenceUrlRedacted, { nullable: true }),
|
|
362
|
+
urlOmitted: S.optionalWith(FetchObjectModeEvidenceUrlOmitted, { nullable: true }),
|
|
331
363
|
}) {}
|
|
332
364
|
|
|
333
365
|
export class FetchAssetReferenceKind extends S.Literal('data-url') {}
|
|
@@ -342,6 +374,20 @@ export class FetchAssetReference extends S.Struct({
|
|
|
342
374
|
bytes: S.Number,
|
|
343
375
|
}) {}
|
|
344
376
|
|
|
377
|
+
export class FetchDegradedInfoReason extends S.Literal('pageTooLarge', 'modelInputTooLarge') {}
|
|
378
|
+
|
|
379
|
+
export class FetchDegradedInfoLimitKind extends S.Literal('domsnapshot_bytes', 'ast_bytes', 'hfc_tokens') {}
|
|
380
|
+
|
|
381
|
+
export class FetchDegradedInfo extends S.Struct({
|
|
382
|
+
reason: FetchDegradedInfoReason,
|
|
383
|
+
skipped: S.Array(S.Literal('hfc')),
|
|
384
|
+
limitKind: FetchDegradedInfoLimitKind,
|
|
385
|
+
measuredBytes: S.optionalWith(S.Int.pipe(S.greaterThanOrEqualTo(0)), { nullable: true }),
|
|
386
|
+
thresholdBytes: S.optionalWith(S.Int.pipe(S.greaterThanOrEqualTo(0)), { nullable: true }),
|
|
387
|
+
measuredTokens: S.optionalWith(S.Int.pipe(S.greaterThanOrEqualTo(0)), { nullable: true }),
|
|
388
|
+
thresholdTokens: S.optionalWith(S.Int.pipe(S.greaterThanOrEqualTo(0)), { nullable: true }),
|
|
389
|
+
}) {}
|
|
390
|
+
|
|
345
391
|
export class Int extends S.Int {}
|
|
346
392
|
|
|
347
393
|
export class FetchSearchSnippetLocation extends S.Struct({
|
|
@@ -354,7 +400,7 @@ export class FetchSearchSnippetLocation extends S.Struct({
|
|
|
354
400
|
|
|
355
401
|
export class FetchSearchSourceMetaSource extends S.Literal('markdown', 'appendix', 'statejson') {}
|
|
356
402
|
|
|
357
|
-
export class FetchSearchSourceMetaRanker extends S.Literal('zeroentropy', 'local_lexical') {}
|
|
403
|
+
export class FetchSearchSourceMetaRanker extends S.Literal('zeroentropy', 'mixedbread', 'local_lexical') {}
|
|
358
404
|
|
|
359
405
|
export class FetchSearchSourceMetaFallbackReason extends S.Literal('timeout', 'error') {}
|
|
360
406
|
|
|
@@ -394,6 +440,7 @@ export class FetchObjectModeResult extends S.Class<FetchObjectModeResult>('Fetch
|
|
|
394
440
|
data: S.optionalWith(
|
|
395
441
|
S.Struct({
|
|
396
442
|
assets: S.optionalWith(S.Array(FetchAssetReference), { nullable: true }),
|
|
443
|
+
degraded: S.optionalWith(FetchDegradedInfo, { nullable: true }),
|
|
397
444
|
search: S.optionalWith(FetchSearchResult, { nullable: true }),
|
|
398
445
|
screenshot: S.optionalWith(
|
|
399
446
|
S.Struct({
|
|
@@ -692,10 +739,14 @@ export class BatchedFetchItem extends S.Struct({
|
|
|
692
739
|
appendix: S.optionalWith(S.String.pipe(S.minLength(1)), { nullable: true }),
|
|
693
740
|
renderEngines: S.optionalWith(RenderEnginesInfo, { nullable: true }),
|
|
694
741
|
assets: S.optionalWith(S.Array(FetchAssetReference), { nullable: true }),
|
|
742
|
+
degraded: S.optionalWith(FetchDegradedInfo, { nullable: true }),
|
|
695
743
|
}),
|
|
696
744
|
{ nullable: true },
|
|
697
745
|
),
|
|
698
|
-
error: S.optionalWith(
|
|
746
|
+
error: S.optionalWith(
|
|
747
|
+
S.Union(FetchBlocked, FetchPageTooLarge, FetchNavigationFailed, FetchCaptureTimeout, FetchCapacityTimeout),
|
|
748
|
+
{ nullable: true },
|
|
749
|
+
),
|
|
699
750
|
}) {}
|
|
700
751
|
|
|
701
752
|
export class PaginationMeta extends S.Struct({
|
|
@@ -782,7 +833,10 @@ export const make = (
|
|
|
782
833
|
'413': (r) => decodeError(r, FetchFetch413),
|
|
783
834
|
'429': (r) => decodeError(r, TooManyRequests),
|
|
784
835
|
'500': (r) => decodeError(r, InternalError),
|
|
836
|
+
'502': (r) => decodeError(r, FetchNavigationFailed),
|
|
785
837
|
'503': (r) => decodeError(r, ServiceUnavailable),
|
|
838
|
+
'504': (r) => decodeError(r, FetchCaptureTimeout),
|
|
839
|
+
'529': (r) => decodeError(r, FetchCapacityTimeout),
|
|
786
840
|
orElse: (response) => unexpectedStatus(request, response),
|
|
787
841
|
}),
|
|
788
842
|
),
|
|
@@ -805,7 +859,10 @@ export const make = (
|
|
|
805
859
|
'413': (r) => decodeError(r, FetchFetchJson413),
|
|
806
860
|
'429': (r) => decodeError(r, TooManyRequests),
|
|
807
861
|
'500': (r) => decodeError(r, InternalError),
|
|
862
|
+
'502': (r) => decodeError(r, FetchNavigationFailed),
|
|
808
863
|
'503': (r) => decodeError(r, ServiceUnavailable),
|
|
864
|
+
'504': (r) => decodeError(r, FetchCaptureTimeout),
|
|
865
|
+
'529': (r) => decodeError(r, FetchCapacityTimeout),
|
|
809
866
|
orElse: (response) => unexpectedStatus(request, response),
|
|
810
867
|
}),
|
|
811
868
|
),
|
|
@@ -924,7 +981,10 @@ export interface Client {
|
|
|
924
981
|
| typeof FetchFetch413.Type
|
|
925
982
|
| typeof TooManyRequests.Type
|
|
926
983
|
| typeof InternalError.Type
|
|
984
|
+
| typeof FetchNavigationFailed.Type
|
|
927
985
|
| typeof ServiceUnavailable.Type
|
|
986
|
+
| typeof FetchCaptureTimeout.Type
|
|
987
|
+
| typeof FetchCapacityTimeout.Type
|
|
928
988
|
>
|
|
929
989
|
readonly fetchFetchJson: (options: {
|
|
930
990
|
readonly params: typeof FetchFetchJsonParams.Encoded
|
|
@@ -939,7 +999,10 @@ export interface Client {
|
|
|
939
999
|
| typeof FetchFetchJson413.Type
|
|
940
1000
|
| typeof TooManyRequests.Type
|
|
941
1001
|
| typeof InternalError.Type
|
|
1002
|
+
| typeof FetchNavigationFailed.Type
|
|
942
1003
|
| typeof ServiceUnavailable.Type
|
|
1004
|
+
| typeof FetchCaptureTimeout.Type
|
|
1005
|
+
| typeof FetchCapacityTimeout.Type
|
|
943
1006
|
>
|
|
944
1007
|
readonly fetchFetchSearch: (
|
|
945
1008
|
options: typeof FetchFetchSearchRequest.Encoded,
|
package/src/Internal.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
type RequestOptions,
|
|
28
28
|
} from './Service.js'
|
|
29
29
|
|
|
30
|
-
const RETRYABLE_STATUS_CODES = HashSet.make(408, 409, 429, 500, 502, 503, 504)
|
|
30
|
+
const RETRYABLE_STATUS_CODES = HashSet.make(408, 409, 429, 500, 502, 503, 504, 529)
|
|
31
31
|
|
|
32
32
|
interface TaggedApiError {
|
|
33
33
|
readonly _tag: string
|
|
@@ -57,7 +57,9 @@ function apiError(status: number, error: unknown): ExpandApiError {
|
|
|
57
57
|
* its public error channel.
|
|
58
58
|
*/
|
|
59
59
|
export function toExpandError(error: unknown, timeoutMs: number): ExpandError {
|
|
60
|
-
|
|
60
|
+
// Split across two `pipe` calls: the arm list is longer than `pipe`'s 20-argument overloads.
|
|
61
|
+
// Arms are evaluated top to bottom across both halves, so the ordering is unaffected.
|
|
62
|
+
const transportAndClientErrors = Match.value(error).pipe(
|
|
61
63
|
Match.when(
|
|
62
64
|
(error: unknown) => error instanceof Cause.TimeoutException,
|
|
63
65
|
(error) => new ExpandTimeoutError({ message: `Request timed out after ${timeoutMs}ms`, timeoutMs, cause: error }),
|
|
@@ -109,6 +111,8 @@ export function toExpandError(error: unknown, timeoutMs: number): ExpandError {
|
|
|
109
111
|
(error: unknown) => hasApiErrorTag(error, 'TooManyRequests'),
|
|
110
112
|
(error) => apiError(429, error),
|
|
111
113
|
),
|
|
114
|
+
)
|
|
115
|
+
return transportAndClientErrors.pipe(
|
|
112
116
|
Match.when(
|
|
113
117
|
(error: unknown) => error instanceof Generated.InternalError,
|
|
114
118
|
(error) => new ExpandApiError({ message: 'InternalError', status: 500, body: error, cause: error }),
|
|
@@ -118,13 +122,23 @@ export function toExpandError(error: unknown, timeoutMs: number): ExpandError {
|
|
|
118
122
|
(error) => apiError(500, error),
|
|
119
123
|
),
|
|
120
124
|
Match.when(
|
|
121
|
-
(error: unknown) => error
|
|
122
|
-
(error) => apiError(
|
|
125
|
+
(error: unknown) => hasApiErrorTag(error, 'FetchNavigationFailed'),
|
|
126
|
+
(error) => apiError(502, error),
|
|
123
127
|
),
|
|
128
|
+
// No `instanceof Generated.ServiceUnavailable` arm: decoded instances carry `_tag`, so the
|
|
129
|
+
// tag predicate already matches them and both would produce the same `apiError(503, ...)`.
|
|
124
130
|
Match.when(
|
|
125
131
|
(error: unknown) => hasApiErrorTag(error, 'ServiceUnavailable'),
|
|
126
132
|
(error) => apiError(503, error),
|
|
127
133
|
),
|
|
134
|
+
Match.when(
|
|
135
|
+
(error: unknown) => hasApiErrorTag(error, 'FetchCaptureTimeout'),
|
|
136
|
+
(error) => apiError(504, error),
|
|
137
|
+
),
|
|
138
|
+
Match.when(
|
|
139
|
+
(error: unknown) => hasApiErrorTag(error, 'FetchCapacityTimeout'),
|
|
140
|
+
(error) => apiError(529, error),
|
|
141
|
+
),
|
|
128
142
|
Match.when(
|
|
129
143
|
(error: unknown) => error instanceof Generated.HttpApiDecodeError,
|
|
130
144
|
(error) => new ExpandApiError({ message: 'HttpApiDecodeError', status: 400, body: error, cause: error }),
|
|
@@ -158,6 +172,19 @@ export function isRetryable(error: unknown, timeoutMs: number): boolean {
|
|
|
158
172
|
if (hasApiErrorTag(error, 'BatchedIdempotencyConflict') || hasApiErrorTag(error, 'BatchedCancellationConflict')) {
|
|
159
173
|
return false
|
|
160
174
|
}
|
|
175
|
+
// A 502 FetchNavigationFailed is a deterministic verdict about the target page (the browser
|
|
176
|
+
// committed an internal error document), not a transient gateway failure — retrying re-runs a
|
|
177
|
+
// full capture for the same outcome.
|
|
178
|
+
if (hasApiErrorTag(error, 'FetchNavigationFailed')) {
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
// A typed 504 FetchCaptureTimeout is the conservative fallback when no result was publishable by
|
|
182
|
+
// the request deadline and queue-only expiry was not proven. Automatic retry remains disabled so
|
|
183
|
+
// ambiguous timeouts do not amplify a control-plane incident. A deliberate retry may still be
|
|
184
|
+
// appropriate; a bare intermediary 504 stays on the normal retryable-status path.
|
|
185
|
+
if (hasApiErrorTag(error, 'FetchCaptureTimeout')) {
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
161
188
|
// An undecodable response (a transient 5xx with an empty/garbled body, a proxy error page, etc.)
|
|
162
189
|
// surfaces as a bare ParseError with no HTTP status, so the status-based classification below can't
|
|
163
190
|
// see the 5xx and the request would fail un-retried. Treat decode failures as retryable — the
|
package/src/Service.ts
CHANGED
|
@@ -83,6 +83,21 @@ export type BatchedParams = typeof Generated.FetchBatchedRequest.Encoded
|
|
|
83
83
|
export type BatchedResponse = typeof Generated.BatchedRun.Type
|
|
84
84
|
export interface BatchedRequestOptions extends RequestOptions {
|
|
85
85
|
readonly idempotencyKey?: string
|
|
86
|
+
/**
|
|
87
|
+
* Body fields the public contract does not declare, merged verbatim into the
|
|
88
|
+
* request payload.
|
|
89
|
+
*
|
|
90
|
+
* The API accepts a small number of options that are deliberately absent from
|
|
91
|
+
* the published OpenAPI document and refused for any organization not on the
|
|
92
|
+
* server's internal allowlist. Because the generated request schema is built
|
|
93
|
+
* from that same public document, it drops undeclared keys on encode — so an
|
|
94
|
+
* internal caller passing one through `params` would get a request that looks
|
|
95
|
+
* correct and carries nothing. This is the supported way to send one.
|
|
96
|
+
*
|
|
97
|
+
* Not for application code: anything here is unversioned, unvalidated by the
|
|
98
|
+
* SDK, and rejected by the API unless the calling organization is allowlisted.
|
|
99
|
+
*/
|
|
100
|
+
readonly internalOverrides?: Readonly<Record<string, unknown>>
|
|
86
101
|
}
|
|
87
102
|
export type GetBatchedParams = typeof Generated.FetchGetBatchedParams.Encoded
|
|
88
103
|
export type GetBatchedResponse = typeof Generated.BatchedStatus.Type
|
|
@@ -163,7 +178,10 @@ export class ExpandService extends Effect.Service<ExpandService>()('@expandai/sd
|
|
|
163
178
|
'413': (response) => decodeError(response, Generated.PayloadTooLarge),
|
|
164
179
|
'429': (response) => decodeError(response, Generated.TooManyRequests),
|
|
165
180
|
'500': (response) => decodeError(response, Generated.InternalError),
|
|
181
|
+
'502': (response) => decodeError(response, Generated.FetchNavigationFailed),
|
|
166
182
|
'503': (response) => decodeError(response, Generated.ServiceUnavailable),
|
|
183
|
+
'504': (response) => decodeError(response, Generated.FetchCaptureTimeout),
|
|
184
|
+
'529': (response) => decodeError(response, Generated.FetchCapacityTimeout),
|
|
167
185
|
orElse: (response) => unexpectedStatus(request, response),
|
|
168
186
|
}),
|
|
169
187
|
),
|
|
@@ -238,9 +256,30 @@ export class ExpandService extends Effect.Service<ExpandService>()('@expandai/sd
|
|
|
238
256
|
),
|
|
239
257
|
}),
|
|
240
258
|
)
|
|
259
|
+
// Merged after the generated payload because the generated schema drops any
|
|
260
|
+
// key it does not declare — silently, which is the dangerous part. See
|
|
261
|
+
// `BatchedRequestOptions.internalOverrides` for why it is not declared.
|
|
262
|
+
//
|
|
263
|
+
// A collision is refused rather than resolved: this hatch exists to add
|
|
264
|
+
// undeclared fields, and letting it win over `urls` or `include` would
|
|
265
|
+
// mean a request could quietly fetch something other than what the caller
|
|
266
|
+
// passed.
|
|
267
|
+
const overrides = requestOptions.internalOverrides ?? {}
|
|
268
|
+
// Checked against the schema's declared fields, not against the keys this
|
|
269
|
+
// particular call happened to set: an omitted-but-declared field like
|
|
270
|
+
// `include` could otherwise be supplied here and bypass the generated
|
|
271
|
+
// contract entirely.
|
|
272
|
+
const collisions = Object.keys(overrides).filter((key) =>
|
|
273
|
+
Object.hasOwn(Generated.FetchBatchedRequest.fields, key),
|
|
274
|
+
)
|
|
275
|
+
if (collisions.length > 0) {
|
|
276
|
+
return yield* new ExpandSdkError({
|
|
277
|
+
message: `internalOverrides may not replace declared request fields: ${collisions.join(', ')}`,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
241
280
|
const generatedRequest: Parameters<typeof generated.fetchBatched>[0] = {
|
|
242
281
|
params: { 'x-idempotency-key': idempotencyKey },
|
|
243
|
-
payload: params,
|
|
282
|
+
payload: { ...params, ...overrides },
|
|
244
283
|
}
|
|
245
284
|
return yield* withRetries(generated.fetchBatched(generatedRequest), retryOptions)
|
|
246
285
|
})
|