@sanity/client 7.26.2 → 8.0.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 +5 -377
- package/dist/browserUpload-CQgx9YYo.js +86 -0
- package/dist/browserUpload-CQgx9YYo.js.map +1 -0
- package/dist/browserUpload-icWlVP15.js +86 -0
- package/dist/browserUpload-icWlVP15.js.map +1 -0
- package/dist/config-a8VajuEY.js +126 -0
- package/dist/config-a8VajuEY.js.map +1 -0
- package/dist/csm.d.ts +122 -335
- package/dist/csm.js +79 -91
- package/dist/csm.js.map +1 -1
- package/dist/dist-C9ExSk2R.js +73 -0
- package/dist/dist-C9ExSk2R.js.map +1 -0
- package/dist/dist-Z8cIRxoB.js +69 -0
- package/dist/dist-Z8cIRxoB.js.map +1 -0
- package/dist/index.d.ts +107 -6723
- package/dist/index.js +2822 -2847
- package/dist/index.js.map +1 -1
- package/dist/{index.d.cts → index.node.d.ts} +4291 -5328
- package/dist/index.node.js +3131 -0
- package/dist/index.node.js.map +1 -0
- package/dist/isRecord-Kfmt-nk-.js +7 -0
- package/dist/isRecord-Kfmt-nk-.js.map +1 -0
- package/dist/media-library.d.ts +9 -100
- package/dist/media-library.js +36 -14
- package/dist/media-library.js.map +1 -1
- package/dist/request-CJxcN16k.js +372 -0
- package/dist/request-CJxcN16k.js.map +1 -0
- package/dist/request-k7VS_NnC.js +354 -0
- package/dist/request-k7VS_NnC.js.map +1 -0
- package/dist/resolveEditInfo-sq7yF78q.js +325 -0
- package/dist/resolveEditInfo-sq7yF78q.js.map +1 -0
- package/dist/rolldown-runtime-vyAXikos.js +9 -0
- package/dist/stega.d.ts +78 -7137
- package/dist/stega.js +27 -20
- package/dist/stega.js.map +1 -1
- package/dist/stegaClean-C18wLWau.js +21 -0
- package/dist/stegaClean-C18wLWau.js.map +1 -0
- package/dist/stegaEncodeSourceMap-B2fGArSf.js +163 -0
- package/dist/stegaEncodeSourceMap-B2fGArSf.js.map +1 -0
- package/dist/stegaEncodeSourceMap-DkoIlutY.js +367 -0
- package/dist/stegaEncodeSourceMap-DkoIlutY.js.map +1 -0
- package/dist/{index.browser.d.cts → types-CUxZSgB2.d.ts} +3956 -5256
- package/dist/types-CfGzbXrl.d.ts +108 -0
- package/package.json +64 -141
- package/src/SanityClient.ts +82 -117
- package/src/agent/actions/AgentActionsClient.ts +12 -12
- package/src/agent/actions/commonTypes.ts +2 -1
- package/src/agent/actions/generate.ts +26 -6
- package/src/agent/actions/patch.ts +27 -7
- package/src/agent/actions/prompt.ts +19 -5
- package/src/agent/actions/transform.ts +23 -5
- package/src/agent/actions/translate.ts +23 -5
- package/src/assets/AssetsClient.ts +71 -23
- package/src/csm/applySourceDocuments.ts +3 -1
- package/src/csm/createEditUrl.ts +1 -1
- package/src/csm/draftUtils.ts +0 -1
- package/src/csm/index.ts +2 -2
- package/src/csm/studioPath.ts +2 -1
- package/src/csm/types.ts +8 -2
- package/src/data/dataMethods.ts +801 -349
- package/src/data/eventsource.ts +13 -10
- package/src/data/listen.ts +18 -23
- package/src/data/live.ts +44 -27
- package/src/data/resolveEventSourceFetch.ts +96 -0
- package/src/datasets/DatasetsClient.ts +58 -41
- package/src/defineCreateClient.ts +35 -23
- package/src/http/browserMiddleware.ts +7 -1
- package/src/http/browserUpload.ts +162 -0
- package/src/http/errors.ts +60 -6
- package/src/http/nodeMiddleware.ts +94 -30
- package/src/http/request.ts +310 -87
- package/src/http/requestOptions.ts +131 -15
- package/src/{index.browser.ts → index.node.ts} +9 -4
- package/src/index.ts +4 -3
- package/src/mediaLibrary/MediaLibraryVideoClient.ts +26 -11
- package/src/projects/ProjectsClient.ts +18 -16
- package/src/releases/ReleasesClient.ts +38 -33
- package/src/stega/index.ts +1 -2
- package/src/stega/stegaEncodeSourceMap.ts +5 -0
- package/src/stega/types.ts +2 -2
- package/src/types.ts +148 -105
- package/src/users/UsersClient.ts +10 -14
- package/src/util/shareReplayLatest.ts +1 -1
- package/src/warnings.ts +6 -1
- package/dist/_chunks-cjs/config.cjs +0 -206
- package/dist/_chunks-cjs/config.cjs.map +0 -1
- package/dist/_chunks-cjs/isRecord.cjs +0 -6
- package/dist/_chunks-cjs/isRecord.cjs.map +0 -1
- package/dist/_chunks-cjs/resolveEditInfo.cjs +0 -334
- package/dist/_chunks-cjs/resolveEditInfo.cjs.map +0 -1
- package/dist/_chunks-cjs/stegaClean.cjs +0 -46
- package/dist/_chunks-cjs/stegaClean.cjs.map +0 -1
- package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs +0 -388
- package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs.map +0 -1
- package/dist/_chunks-es/config.js +0 -207
- package/dist/_chunks-es/config.js.map +0 -1
- package/dist/_chunks-es/isRecord.js +0 -7
- package/dist/_chunks-es/isRecord.js.map +0 -1
- package/dist/_chunks-es/resolveEditInfo.js +0 -335
- package/dist/_chunks-es/resolveEditInfo.js.map +0 -1
- package/dist/_chunks-es/stegaClean.js +0 -47
- package/dist/_chunks-es/stegaClean.js.map +0 -1
- package/dist/_chunks-es/stegaEncodeSourceMap.js +0 -389
- package/dist/_chunks-es/stegaEncodeSourceMap.js.map +0 -1
- package/dist/csm.cjs +0 -99
- package/dist/csm.cjs.map +0 -1
- package/dist/csm.d.cts +0 -391
- package/dist/index.browser.cjs +0 -3106
- package/dist/index.browser.cjs.map +0 -1
- package/dist/index.browser.d.ts +0 -6802
- package/dist/index.browser.js +0 -3082
- package/dist/index.browser.js.map +0 -1
- package/dist/index.cjs +0 -2946
- package/dist/index.cjs.map +0 -1
- package/dist/media-library.cjs +0 -20
- package/dist/media-library.cjs.map +0 -1
- package/dist/media-library.d.cts +0 -117
- package/dist/stega.browser.cjs +0 -29
- package/dist/stega.browser.cjs.map +0 -1
- package/dist/stega.browser.d.cts +0 -7200
- package/dist/stega.browser.d.ts +0 -7200
- package/dist/stega.browser.js +0 -24
- package/dist/stega.browser.js.map +0 -1
- package/dist/stega.cjs +0 -29
- package/dist/stega.cjs.map +0 -1
- package/dist/stega.d.cts +0 -7200
- package/src/data/eventsourcePolyfill.ts +0 -7
- package/umd/.gitkeep +0 -1
- package/umd/sanityClient.js +0 -6543
- package/umd/sanityClient.min.js +0 -6
package/src/http/request.ts
CHANGED
|
@@ -1,110 +1,333 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
createRequester,
|
|
3
|
+
type FetchFunction,
|
|
4
|
+
HttpError as GetItHttpError,
|
|
5
|
+
type RequestOptions as FetchRequestOptions,
|
|
6
|
+
type TransformMiddleware,
|
|
7
|
+
type WrappingMiddleware,
|
|
8
|
+
} from 'get-it'
|
|
9
|
+
import {isRetryableRequest, retry} from 'get-it/middleware'
|
|
10
|
+
import {from, Observable} from 'rxjs'
|
|
4
11
|
|
|
5
12
|
import type {Any} from '../types'
|
|
6
|
-
import {ClientError, ServerError} from './errors'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
import {ClientError, httpResponseFromFetch, ServerError} from './errors'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Middleware accepted by the internal HTTP layer. Two flavors per get-it v9:
|
|
17
|
+
* a flat-pipeline transform (`beforeRequest`/`afterResponse`) or a wrapping
|
|
18
|
+
* middleware that surrounds the fetch chain.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export type LegacyMiddleware = TransformMiddleware | WrappingMiddleware
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The shape this client's internal pipeline produces. Mirrors the historical
|
|
26
|
+
* `ResponseEvent` from the get-it v8 multi-event observable, so all the existing
|
|
27
|
+
* downstream code (`_requestObservable`, `_uploadObservable`,
|
|
28
|
+
* `defineCreateClient`) keeps working without churn.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export interface ResponseEvent {
|
|
33
|
+
type: 'response'
|
|
34
|
+
body: unknown
|
|
35
|
+
statusCode: number
|
|
36
|
+
statusMessage: string | null
|
|
37
|
+
headers: Record<string, string>
|
|
38
|
+
url: string
|
|
39
|
+
method: string
|
|
18
40
|
}
|
|
19
41
|
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Legacy "requester" type — the result of `defineHttpRequest`. Returns a
|
|
44
|
+
* single-event Observable for compatibility with the rest of the codebase.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export type LegacyRequester = (options: Any) => Observable<ResponseEvent>
|
|
22
49
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Promise-based sibling of {@link LegacyRequester}. Resolves directly to the
|
|
52
|
+
* single `ResponseEvent` the transport produces, skipping the RxJS wrapper.
|
|
53
|
+
* Used by the promise-based client surface so it never constructs an
|
|
54
|
+
* Observable.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export type PromiseRequester = (options: Any) => Promise<ResponseEvent>
|
|
26
59
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Both forms of the transport, sharing a single underlying get-it requester
|
|
62
|
+
* (so retry state and the one-shot warning de-duplication are shared between
|
|
63
|
+
* the observable and promise paths).
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export interface DualRequester {
|
|
68
|
+
observable: LegacyRequester
|
|
69
|
+
promise: PromiseRequester
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Options for tuning the HTTP request pipeline per-client.
|
|
74
|
+
*
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
export interface HttpRequestConfig {
|
|
78
|
+
ignoreWarnings?: string | RegExp | Array<string | RegExp>
|
|
79
|
+
maxRetries?: number
|
|
80
|
+
retryDelay?: (attemptNumber: number) => number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build both the observable and promise transport forms from a single get-it
|
|
85
|
+
* requester. The promise form is the primitive (`executeRequest` is already
|
|
86
|
+
* promise-based); the observable form wraps it lazily so each subscription
|
|
87
|
+
* starts its own request (cold), and unsubscribing aborts the in-flight
|
|
88
|
+
* fetch — the same contract as the get-it v8 observable adapter.
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export function defineRequester(
|
|
93
|
+
envOptions: EnvironmentOptions,
|
|
94
|
+
config: HttpRequestConfig = {},
|
|
95
|
+
): DualRequester {
|
|
96
|
+
// Framework-patched fetch implementations read extra `RequestInit` fields
|
|
97
|
+
// for caching semantics — Next.js App Router's `cache` and `next` options in
|
|
98
|
+
// particular. Legacy callers pass those via an object-valued `fetch` request
|
|
99
|
+
// option (see `adaptToFetchOptions`, which stashes it in `meta.fetchInit`
|
|
100
|
+
// since get-it v9's own `fetch` option only accepts a function). Merge them
|
|
101
|
+
// into the init of whichever fetch implementation is effective for the
|
|
102
|
+
// request: per-request/test-override fetch, the environment default, or the
|
|
103
|
+
// global fetch.
|
|
104
|
+
const applyFetchInit: WrappingMiddleware = (opts, next) => {
|
|
105
|
+
const fetchInit = opts.meta?.fetchInit
|
|
106
|
+
if (typeof fetchInit !== 'object' || fetchInit === null) return next(opts)
|
|
107
|
+
const baseFetch: NonNullable<FetchRequestOptions['fetch']> =
|
|
108
|
+
opts.fetch ?? envOptions.fetch ?? globalThis.fetch
|
|
109
|
+
const fetchWithInit: typeof baseFetch = (input, init) =>
|
|
110
|
+
baseFetch(input, {...fetchInit, ...init})
|
|
111
|
+
return next({...opts, fetch: fetchWithInit})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const requester = createRequester({
|
|
115
|
+
...(envOptions.fetch ? {fetch: envOptions.fetch} : {}),
|
|
116
|
+
headers: envOptions.headers,
|
|
117
|
+
// Keep get-it's built-in 4xx/5xx → HttpError so the retry middleware can
|
|
118
|
+
// see them; we translate to ClientError/ServerError after the retry loop
|
|
119
|
+
// has exhausted in `executeRequest`.
|
|
120
|
+
httpErrors: true,
|
|
121
|
+
middleware: [
|
|
122
|
+
retry({
|
|
123
|
+
shouldRetry: shouldRetryRequest,
|
|
124
|
+
maxRetries: config.maxRetries ?? 5,
|
|
125
|
+
...(config.retryDelay ? {retryDelay: config.retryDelay} : {}),
|
|
126
|
+
}),
|
|
127
|
+
...envOptions.middleware,
|
|
128
|
+
applyFetchInit,
|
|
129
|
+
printWarnings(config),
|
|
130
|
+
],
|
|
131
|
+
})
|
|
30
132
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
133
|
+
const promise: PromiseRequester = (options: Any) => {
|
|
134
|
+
// Options arrive fetch-shaped from `requestOptions` — the single
|
|
135
|
+
// translation boundary between public option names and the transport.
|
|
136
|
+
if (typeof options.url !== 'string') {
|
|
137
|
+
throw new TypeError('Request options must include a `url`')
|
|
138
|
+
}
|
|
139
|
+
return executeRequest(requester, options)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Same per-subscription AbortController pattern as `_observe` in
|
|
143
|
+
// dataMethods: a caller-supplied signal is combined in via
|
|
144
|
+
// `AbortSignal.any`, so the request aborts both on the caller's signal and
|
|
145
|
+
// on unsubscribe. `AbortSignal.any` (rather than `addEventListener`)
|
|
146
|
+
// because the caller's signal can be long-lived and reused — a manually
|
|
147
|
+
// added listener would accumulate there once per subscription, since
|
|
148
|
+
// `{once: true}` only cleans up if the signal actually fires.
|
|
149
|
+
const observable: LegacyRequester = (options: Any) =>
|
|
150
|
+
new Observable<ResponseEvent>((subscriber) => {
|
|
151
|
+
const controller = new AbortController()
|
|
152
|
+
const userSignal: AbortSignal | undefined = options.signal
|
|
153
|
+
const signal = userSignal
|
|
154
|
+
? AbortSignal.any([userSignal, controller.signal])
|
|
155
|
+
: controller.signal
|
|
156
|
+
const subscription = from(promise({...options, signal})).subscribe(subscriber)
|
|
157
|
+
return () => {
|
|
158
|
+
subscription.unsubscribe()
|
|
159
|
+
controller.abort()
|
|
36
160
|
}
|
|
37
|
-
return false
|
|
38
161
|
})
|
|
162
|
+
|
|
163
|
+
return {promise, observable}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @internal */
|
|
167
|
+
export function defineHttpRequest(
|
|
168
|
+
envOptions: EnvironmentOptions,
|
|
169
|
+
config: HttpRequestConfig = {},
|
|
170
|
+
): LegacyRequester {
|
|
171
|
+
return defineRequester(envOptions, config).observable
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Options describing the environment-specific defaults (Node vs. browser).
|
|
176
|
+
*
|
|
177
|
+
* @internal
|
|
178
|
+
*/
|
|
179
|
+
export interface EnvironmentOptions {
|
|
180
|
+
fetch?: FetchRequestOptions['fetch']
|
|
181
|
+
headers?: Record<string, string>
|
|
182
|
+
middleware: LegacyMiddleware[]
|
|
183
|
+
/**
|
|
184
|
+
* Resolves the environment's fetch implementation — the same transport
|
|
185
|
+
* regular requests use (custom fetch variants, undici configuration,
|
|
186
|
+
* env-proxy support and all), optionally configured for an explicit proxy
|
|
187
|
+
* URL. Lets consumers of the resolved config (the EventSource fetch
|
|
188
|
+
* resolver) avoid falling back to whatever `globalThis.fetch` happens to
|
|
189
|
+
* be. The Node environment supplies get-it's undici-backed fetch; the
|
|
190
|
+
* browser environment leaves it unset (the global fetch IS the
|
|
191
|
+
* environment's fetch there).
|
|
192
|
+
*
|
|
193
|
+
* Looked up via the env rather than imported directly so that the Node-only
|
|
194
|
+
* `get-it/node` (which transitively pulls in `undici`) never ends up in the
|
|
195
|
+
* browser bundle, even via rollup's inlined dynamic imports.
|
|
196
|
+
*/
|
|
197
|
+
resolveFetch?: (proxyUrl?: string) => FetchFunction
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function executeRequest(
|
|
201
|
+
requester: ReturnType<typeof createRequester>,
|
|
202
|
+
fetchOptions: FetchRequestOptions,
|
|
203
|
+
): Promise<ResponseEvent> {
|
|
204
|
+
const url = fetchOptions.url
|
|
205
|
+
const method = (fetchOptions.method ?? 'GET').toUpperCase()
|
|
206
|
+
|
|
207
|
+
let response
|
|
208
|
+
try {
|
|
209
|
+
response = await requester(fetchOptions)
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (err instanceof GetItHttpError) {
|
|
212
|
+
// `err.body` is the response body as a string (get-it v9 stores the
|
|
213
|
+
// already-decoded text), regardless of which response variant
|
|
214
|
+
// `err.response` is.
|
|
215
|
+
const errBodyText = typeof err.body === 'string' ? err.body : ''
|
|
216
|
+
const errBody = parseJsonText(errBodyText, err.headers)
|
|
217
|
+
const canonical = httpResponseFromFetch(
|
|
218
|
+
{
|
|
219
|
+
status: err.status,
|
|
220
|
+
statusText: err.statusText,
|
|
221
|
+
headers: err.headers,
|
|
222
|
+
body: errBody,
|
|
223
|
+
},
|
|
224
|
+
url,
|
|
225
|
+
method,
|
|
226
|
+
)
|
|
227
|
+
const tag = extractRequestTag(fetchOptions.query)
|
|
228
|
+
if (canonical.statusCode >= 500) {
|
|
229
|
+
throw new ServerError(canonical)
|
|
230
|
+
}
|
|
231
|
+
throw new ClientError(canonical, tag)
|
|
232
|
+
}
|
|
233
|
+
throw err
|
|
39
234
|
}
|
|
40
235
|
|
|
236
|
+
const body = parseJsonBody(response)
|
|
41
237
|
return {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
238
|
+
type: 'response',
|
|
239
|
+
body,
|
|
240
|
+
statusCode: response.status,
|
|
241
|
+
statusMessage: response.statusText || null,
|
|
242
|
+
headers: headersToRecord(response.headers),
|
|
243
|
+
url,
|
|
244
|
+
method,
|
|
245
|
+
}
|
|
246
|
+
}
|
|
47
247
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Extract the GROQ request tag (used for error messages) from the query.
|
|
250
|
+
*/
|
|
251
|
+
function extractRequestTag(query: FetchRequestOptions['query']): string | undefined {
|
|
252
|
+
if (!query) return undefined
|
|
253
|
+
if (query instanceof URLSearchParams) return query.get('tag') ?? undefined
|
|
254
|
+
const tag = query.tag
|
|
255
|
+
return typeof tag === 'string' ? tag : undefined
|
|
256
|
+
}
|
|
52
257
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
258
|
+
function parseJsonBody(response: {headers: Headers; text(): string}): unknown {
|
|
259
|
+
return parseJsonText(response.text(), response.headers)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Parse a response body according to its `content-type`: JSON when the header
|
|
264
|
+
* says so (falling back to the raw text on malformed JSON), text otherwise.
|
|
265
|
+
* Shared with the browser XHR upload path so error bodies parse identically
|
|
266
|
+
* on both transports.
|
|
267
|
+
*
|
|
268
|
+
* @internal
|
|
269
|
+
*/
|
|
270
|
+
export function parseJsonText(text: string, headers: Headers): unknown {
|
|
271
|
+
const contentType = (headers.get('content-type') ?? '').toLowerCase()
|
|
272
|
+
if (!text) return undefined
|
|
273
|
+
if (contentType.includes('application/json')) {
|
|
274
|
+
try {
|
|
275
|
+
return JSON.parse(text)
|
|
276
|
+
} catch {
|
|
277
|
+
return text
|
|
278
|
+
}
|
|
58
279
|
}
|
|
280
|
+
return text
|
|
59
281
|
}
|
|
60
282
|
|
|
61
|
-
|
|
62
|
-
|
|
283
|
+
function headersToRecord(headers: Headers): Record<string, string> {
|
|
284
|
+
const out: Record<string, string> = {}
|
|
285
|
+
headers.forEach((value, key) => {
|
|
286
|
+
out[key] = value
|
|
287
|
+
})
|
|
288
|
+
return out
|
|
63
289
|
}
|
|
64
290
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
observable({implementation: Observable}),
|
|
79
|
-
])
|
|
291
|
+
function shouldRetryRequest(err: unknown, attempt: number, options: FetchRequestOptions): boolean {
|
|
292
|
+
// HTTP errors aren't usually retryable, but Content Lake gives us a few
|
|
293
|
+
// status codes where retrying *is* the right move.
|
|
294
|
+
if (err instanceof GetItHttpError) {
|
|
295
|
+
const isSafe = (options.method ?? 'GET') === 'GET' || options.method === 'HEAD'
|
|
296
|
+
const isQuery = (options.url ?? '').includes('/data/query')
|
|
297
|
+
const status = err.status
|
|
298
|
+
const retryableStatus = status === 429 || status === 502 || status === 503
|
|
299
|
+
if ((isSafe || isQuery) && retryableStatus) return true
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return isRetryableRequest(err, attempt, options)
|
|
80
304
|
}
|
|
81
305
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return retry.shouldRetry(err, attempt, options)
|
|
306
|
+
function printWarnings(config: HttpRequestConfig): TransformMiddleware {
|
|
307
|
+
const seen: Record<string, boolean> = {}
|
|
308
|
+
|
|
309
|
+
const shouldIgnore = (message: string): boolean => {
|
|
310
|
+
if (config.ignoreWarnings === undefined) return false
|
|
311
|
+
const patterns = Array.isArray(config.ignoreWarnings)
|
|
312
|
+
? config.ignoreWarnings
|
|
313
|
+
: [config.ignoreWarnings]
|
|
314
|
+
return patterns.some((pattern) =>
|
|
315
|
+
typeof pattern === 'string' ? message.includes(pattern) : pattern.test(message),
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
afterResponse(response) {
|
|
321
|
+
const header = response.headers.get('x-sanity-warning')
|
|
322
|
+
if (!header) return response
|
|
323
|
+
// Multiple warnings can be comma-separated per HTTP header semantics.
|
|
324
|
+
for (const msg of header.split(',').map((m) => m.trim())) {
|
|
325
|
+
if (!msg || seen[msg] || shouldIgnore(msg)) continue
|
|
326
|
+
seen[msg] = true
|
|
327
|
+
// oxlint-disable-next-line no-console
|
|
328
|
+
console.warn(msg)
|
|
329
|
+
}
|
|
330
|
+
return response
|
|
331
|
+
},
|
|
332
|
+
}
|
|
110
333
|
}
|
|
@@ -1,11 +1,34 @@
|
|
|
1
|
-
import type {RequestOptions} from 'get-it'
|
|
1
|
+
import type {RequestOptions as GetItRequestOptions} from 'get-it'
|
|
2
2
|
|
|
3
3
|
import type {Any} from '../types'
|
|
4
4
|
|
|
5
5
|
const projectHeader = 'X-Sanity-Project-ID'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The request shape the transport (`defineRequester`) consumes: get-it v9's
|
|
9
|
+
* own options, with `headers` pinned to the plain-record form this builder
|
|
10
|
+
* always produces.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export type FetchRequest = Omit<GetItRequestOptions, 'headers'> & {
|
|
15
|
+
headers: Record<string, string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Project the public request options (`timeout: 0` to disable,
|
|
20
|
+
* `withCredentials`, `maxRedirects`, the function/object-form `fetch`, ...)
|
|
21
|
+
* plus the client config into a get-it v9 fetch-shaped request. This is the
|
|
22
|
+
* single translation boundary between the client's public option names and
|
|
23
|
+
* the transport — everything below it speaks get-it v9.
|
|
24
|
+
*
|
|
25
|
+
* Reads the live client config, so reconfiguration via `client.config()` /
|
|
26
|
+
* `withConfig()` (token, headers, proxy, ...) applies to subsequent requests.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export function requestOptions(config: Any, overrides: Any = {}): FetchRequest {
|
|
31
|
+
const headers: Record<string, string> = {}
|
|
9
32
|
|
|
10
33
|
if (config.headers) {
|
|
11
34
|
Object.assign(headers, config.headers)
|
|
@@ -13,29 +36,122 @@ export function requestOptions(config: Any, overrides: Any = {}): Omit<RequestOp
|
|
|
13
36
|
|
|
14
37
|
const token = overrides.token || config.token
|
|
15
38
|
if (token) {
|
|
16
|
-
headers
|
|
39
|
+
headers['Authorization'] = `Bearer ${token}`
|
|
17
40
|
}
|
|
18
41
|
|
|
19
42
|
if (!overrides.useGlobalApi && !config.useProjectHostname && config.projectId) {
|
|
20
43
|
headers[projectHeader] = config.projectId
|
|
21
44
|
}
|
|
22
45
|
|
|
46
|
+
const request: FetchRequest = {
|
|
47
|
+
url: overrides.url,
|
|
48
|
+
headers: Object.assign(headers, overrides.headers || {}),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (overrides.method) request.method = overrides.method
|
|
52
|
+
if (overrides.body !== undefined) request.body = overrides.body
|
|
53
|
+
if (overrides.query) request.query = expandQueryArrays(overrides.query)
|
|
54
|
+
if (overrides.signal) request.signal = overrides.signal
|
|
55
|
+
|
|
23
56
|
const withCredentials = Boolean(
|
|
24
57
|
typeof overrides.withCredentials === 'undefined'
|
|
25
58
|
? config.withCredentials
|
|
26
59
|
: overrides.withCredentials,
|
|
27
60
|
)
|
|
61
|
+
if (withCredentials) request.credentials = 'include'
|
|
28
62
|
|
|
63
|
+
// The public option is the legacy `maxRedirects` count, but only "follow"
|
|
64
|
+
// vs "don't" ever worked — fetch has no redirect budget.
|
|
65
|
+
if (typeof overrides.maxRedirects === 'number') {
|
|
66
|
+
request.redirect = overrides.maxRedirects === 0 ? 'manual' : 'follow'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Public semantics: `0` disables the timeout (get-it uses `false`), default
|
|
70
|
+
// is five minutes.
|
|
29
71
|
const timeout = typeof overrides.timeout === 'undefined' ? config.timeout : overrides.timeout
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
request.timeout = typeof timeout === 'undefined' ? 5 * 60 * 1000 : timeout === 0 ? false : timeout
|
|
73
|
+
|
|
74
|
+
// `useAbortSignal: false` is set by the query path when the caller provided
|
|
75
|
+
// no signal of their own, and means no AbortSignal may reach the fetch
|
|
76
|
+
// init: Next.js' patched fetch opts a request out of React Request
|
|
77
|
+
// Memoization whenever `init.signal` is present, and get-it v9 implements
|
|
78
|
+
// timeouts via `AbortSignal.timeout()`. Switch to get-it's rejection-only
|
|
79
|
+
// timeout mode (`{signal: false}`): the deadline still rejects, but no
|
|
80
|
+
// signal reaches the fetch init and the underlying request is left running,
|
|
81
|
+
// so a memoizing fetch can still settle the shared promise for other
|
|
82
|
+
// consumers.
|
|
83
|
+
if (overrides.useAbortSignal === false && !request.signal) {
|
|
84
|
+
request.timeout =
|
|
85
|
+
typeof request.timeout === 'number' && request.timeout > 0
|
|
86
|
+
? {total: request.timeout, signal: false}
|
|
87
|
+
: false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The public `fetch` option is either a custom fetch implementation
|
|
91
|
+
// (function) or a bag of extra `RequestInit` fields (object) — Next.js App
|
|
92
|
+
// Router's `cache`/`next` caching options arrive as the latter. get-it's
|
|
93
|
+
// `fetch` option only accepts a function, so the init extras travel in
|
|
94
|
+
// `meta` and are merged into the effective fetch by the `applyFetchInit`
|
|
95
|
+
// middleware. (A boolean `fetch` was v8's "force the fetch transport"
|
|
96
|
+
// switch — a no-op now that fetch is the only transport.)
|
|
97
|
+
const fetchOption =
|
|
98
|
+
typeof overrides.fetch === 'object' && typeof config.fetch === 'object'
|
|
99
|
+
? {...config.fetch, ...overrides.fetch}
|
|
100
|
+
: overrides.fetch || config.fetch
|
|
101
|
+
if (typeof fetchOption === 'function') {
|
|
102
|
+
request.fetch = fetchOption
|
|
103
|
+
} else if (typeof fetchOption === 'object' && fetchOption !== null) {
|
|
104
|
+
request.meta = {...request.meta, fetchInit: fetchOption}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The config's fetch resolver supplies the transport for every request —
|
|
108
|
+
// the environment's own fetch by default, a proxy-configured one when an
|
|
109
|
+
// explicit `proxy` is set, or a caller-supplied resolver (the test suite
|
|
110
|
+
// injects its mock this way). A per-request `fetch` function wins over it.
|
|
111
|
+
// There is deliberately no per-request proxy.
|
|
112
|
+
if (!request.fetch && config.resolveFetch) {
|
|
113
|
+
request.fetch = config.resolveFetch(typeof config.proxy === 'string' ? config.proxy : undefined)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Per-request retry cap/opt-out (`maxRetries: 0` disables retries) —
|
|
117
|
+
// honored natively by get-it's retry middleware, overriding the
|
|
118
|
+
// client-level maximum in both directions.
|
|
119
|
+
if (typeof overrides.maxRetries === 'number') {
|
|
120
|
+
request.maxRetries = overrides.maxRetries
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Lineage travels in `meta`; the Node middleware projects it onto the
|
|
124
|
+
// `x-sanity-lineage` header (merged with the `X_SANITY_LINEAGE` env var).
|
|
125
|
+
if (typeof config.lineage === 'string' && config.lineage) {
|
|
126
|
+
request.meta = {...request.meta, lineage: config.lineage}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return request
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Expand array-valued query params into repeated keys.
|
|
134
|
+
*
|
|
135
|
+
* get-it v9 stringifies a plain object's values directly, so passing
|
|
136
|
+
* `{meta: ['palette', 'location']}` would produce `?meta=palette,location`
|
|
137
|
+
* — which Content Lake doesn't recognise. Repeated keys
|
|
138
|
+
* (`?meta=palette&meta=location`) are produced via `URLSearchParams`.
|
|
139
|
+
*/
|
|
140
|
+
function expandQueryArrays(query: Any): FetchRequest['query'] {
|
|
141
|
+
if (query instanceof URLSearchParams) return query
|
|
142
|
+
if (!query || typeof query !== 'object') return query
|
|
143
|
+
if (!Object.values(query).some(Array.isArray)) return query
|
|
144
|
+
|
|
145
|
+
const params = new URLSearchParams()
|
|
146
|
+
for (const [key, value] of Object.entries(query)) {
|
|
147
|
+
if (value === undefined || value === null) continue
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
for (const item of value) {
|
|
150
|
+
if (item !== undefined && item !== null) params.append(key, `${item}`)
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
params.append(key, `${value}`)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return params
|
|
41
157
|
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import defineCreateClientExports, {type ClientConfig, SanityClient} from './defineCreateClient'
|
|
2
2
|
import {defineDeprecatedCreateClient} from './defineDeprecatedCreateClient'
|
|
3
|
-
import
|
|
3
|
+
import environment from './http/nodeMiddleware'
|
|
4
|
+
import type {Requester} from './types'
|
|
4
5
|
|
|
5
6
|
export * from './defineCreateClient'
|
|
6
7
|
|
|
7
|
-
const exp = defineCreateClientExports<SanityClient, ClientConfig>(
|
|
8
|
+
const exp = defineCreateClientExports<SanityClient, ClientConfig>(environment, SanityClient)
|
|
8
9
|
|
|
9
10
|
/** @public */
|
|
10
|
-
export const requester = exp.requester
|
|
11
|
+
export const requester: Requester = exp.requester
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* @remarks
|
|
15
|
+
* As of API version `v2025-02-19`, the default perspective used by the client has changed from `raw` to `published`. {@link https://www.sanity.io/changelog/676aaa9d-2da6-44fb-abe5-580f28047c10|Changelog}
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
13
18
|
export const createClient = exp.createClient
|
|
14
19
|
|
|
15
20
|
/**
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import defineCreateClientExports, {type ClientConfig, SanityClient} from './defineCreateClient'
|
|
2
2
|
import {defineDeprecatedCreateClient} from './defineDeprecatedCreateClient'
|
|
3
|
-
import
|
|
3
|
+
import environment from './http/browserMiddleware'
|
|
4
|
+
import type {Requester} from './types'
|
|
4
5
|
|
|
5
6
|
export * from './defineCreateClient'
|
|
6
7
|
|
|
7
|
-
const exp = defineCreateClientExports<SanityClient, ClientConfig>(
|
|
8
|
+
const exp = defineCreateClientExports<SanityClient, ClientConfig>(environment, SanityClient)
|
|
8
9
|
|
|
9
10
|
/** @public */
|
|
10
|
-
export const requester = exp.requester
|
|
11
|
+
export const requester: Requester = exp.requester
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @remarks
|