@sanity/client 6.20.0 → 6.20.2-beta.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/dist/index.browser.cjs +61 -7
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.d.cts +7 -0
- package/dist/index.browser.d.ts +7 -0
- package/dist/index.browser.js +61 -7
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +62 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +62 -7
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/config.ts +5 -0
- package/src/data/listen.ts +13 -1
- package/src/defineCreateClient.ts +1 -0
- package/src/http/browserMiddleware.ts +3 -1
- package/src/http/domainSharding.ts +96 -0
- package/src/http/nodeMiddleware.ts +3 -0
- package/src/types.ts +8 -0
- package/src/warnings.ts +5 -0
- package/umd/sanityClient.js +75 -26
- package/umd/sanityClient.min.js +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/client",
|
|
3
|
-
"version": "6.20.0",
|
|
3
|
+
"version": "6.20.2-beta.0",
|
|
4
4
|
"description": "Client for retrieving, creating and patching data from Sanity.io",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
"dependencies": {
|
|
120
120
|
"@sanity/eventsource": "^5.0.2",
|
|
121
|
-
"get-it": "^8.6.
|
|
121
|
+
"get-it": "^8.6.1",
|
|
122
122
|
"rxjs": "^7.0.0"
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|
|
@@ -129,8 +129,8 @@
|
|
|
129
129
|
"@sanity/pkg-utils": "^6.9.3",
|
|
130
130
|
"@types/json-diff": "^1.0.3",
|
|
131
131
|
"@types/node": "^20.8.8",
|
|
132
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
133
|
-
"@typescript-eslint/parser": "^7.
|
|
132
|
+
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
|
133
|
+
"@typescript-eslint/parser": "^7.13.1",
|
|
134
134
|
"@vercel/stega": "0.1.2",
|
|
135
135
|
"@vitest/coverage-v8": "1.6.0",
|
|
136
136
|
"eslint": "^8.57.0",
|
|
@@ -140,10 +140,10 @@
|
|
|
140
140
|
"faucet": "^0.0.4",
|
|
141
141
|
"happy-dom": "^12.10.3",
|
|
142
142
|
"json-diff": "^1.0.6",
|
|
143
|
-
"ls-engines": "^0.9.
|
|
144
|
-
"next": "^14.2.
|
|
143
|
+
"ls-engines": "^0.9.2",
|
|
144
|
+
"next": "^14.2.4",
|
|
145
145
|
"nock": "^13.5.4",
|
|
146
|
-
"prettier": "^3.3.
|
|
146
|
+
"prettier": "^3.3.2",
|
|
147
147
|
"prettier-plugin-packagejson": "^2.5.0",
|
|
148
148
|
"rimraf": "^5.0.7",
|
|
149
149
|
"rollup": "^4.18.0",
|
package/src/config.ts
CHANGED
|
@@ -130,6 +130,11 @@ export const initConfig = (
|
|
|
130
130
|
|
|
131
131
|
newConfig.apiVersion = `${newConfig.apiVersion}`.replace(/^v/, '')
|
|
132
132
|
newConfig.isDefaultApi = newConfig.apiHost === defaultConfig.apiHost
|
|
133
|
+
|
|
134
|
+
if (newConfig.useCdn === true && newConfig.withCredentials) {
|
|
135
|
+
warnings.printCdnAndWithCredentialsWarning()
|
|
136
|
+
}
|
|
137
|
+
|
|
133
138
|
// If `useCdn` is undefined, we treat it as `true`
|
|
134
139
|
newConfig.useCdn = newConfig.useCdn !== false && !newConfig.withCredentials
|
|
135
140
|
|
package/src/data/listen.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {Observable} from 'rxjs'
|
|
2
2
|
|
|
3
|
+
import {domainSharder as sharder} from '../http/domainSharding'
|
|
3
4
|
import type {ObservableSanityClient, SanityClient} from '../SanityClient'
|
|
4
5
|
import type {Any, ListenEvent, ListenOptions, ListenParams, MutationEvent} from '../types'
|
|
5
6
|
import defaults from '../util/defaults'
|
|
@@ -64,7 +65,11 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
|
|
|
64
65
|
const listenOpts = pick(options, possibleOptions)
|
|
65
66
|
const qs = encodeQueryString({query, params, options: {tag, ...listenOpts}})
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
let uri = `${url}${_getDataUrl(this, 'listen', qs)}`
|
|
69
|
+
if (this.config().useDomainSharding) {
|
|
70
|
+
uri = sharder.getShardedUrl(uri)
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
if (uri.length > MAX_URL_LENGTH) {
|
|
69
74
|
return new Observable((observer) => observer.error(new Error('Query too large for listener')))
|
|
70
75
|
}
|
|
@@ -91,6 +96,12 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
|
|
|
91
96
|
// Once it is`true`, it will never be `false` again.
|
|
92
97
|
let unsubscribed = false
|
|
93
98
|
|
|
99
|
+
// We're about to connect, and will reuse the same shard/bucket for every reconnect henceforth.
|
|
100
|
+
// This may seem inoptimal, but once connected we should just consider this as a "permanent"
|
|
101
|
+
// connection, since we'll automatically retry on failures/disconnects. Once we explicitly
|
|
102
|
+
// unsubsccribe, we can decrement the bucket and free up the shard.
|
|
103
|
+
sharder.incrementBucketForUrl(uri)
|
|
104
|
+
|
|
94
105
|
open()
|
|
95
106
|
|
|
96
107
|
function onError() {
|
|
@@ -187,6 +198,7 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
|
|
|
187
198
|
stopped = true
|
|
188
199
|
unsubscribe()
|
|
189
200
|
unsubscribed = true
|
|
201
|
+
sharder.decrementBucketForUrl(uri)
|
|
190
202
|
}
|
|
191
203
|
|
|
192
204
|
return stop
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type {Middleware, RequestOptions} from 'get-it'
|
|
2
|
+
|
|
3
|
+
const UNSHARDED_URL_RE = /^https:\/\/([a-z0-9]+)\.api\.(sanity\..*)/
|
|
4
|
+
const SHARDED_URL_RE = /^https:\/\/[a-z0-9]+\.api\.s(\d+)\.sanity\.(.*)/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get a default sharding implementation where buckets are reused across instances.
|
|
8
|
+
* Helps prevent the case when multiple clients are instantiated, each having their
|
|
9
|
+
* own state of which buckets are least used.
|
|
10
|
+
*/
|
|
11
|
+
export const domainSharder = getDomainSharder()
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export function getDomainSharder(initialBuckets?: number[]) {
|
|
17
|
+
const buckets: number[] = initialBuckets || new Array(10).fill(0, 0)
|
|
18
|
+
|
|
19
|
+
function incrementBucketForUrl(url: string) {
|
|
20
|
+
const shard = getShardFromUrl(url)
|
|
21
|
+
if (shard !== null) {
|
|
22
|
+
buckets[shard]++
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function decrementBucketForUrl(url: string) {
|
|
27
|
+
const shard = getShardFromUrl(url)
|
|
28
|
+
if (shard !== null) {
|
|
29
|
+
buckets[shard]--
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getShardedUrl(url: string): string {
|
|
34
|
+
const [isMatch, projectId, rest] = url.match(UNSHARDED_URL_RE) || []
|
|
35
|
+
if (!isMatch) {
|
|
36
|
+
return url
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Find index of bucket with fewest requests
|
|
40
|
+
const bucket = buckets.reduce(
|
|
41
|
+
(smallest, count, index) => (count < buckets[smallest] ? index : smallest),
|
|
42
|
+
0,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return `https://${projectId}.api.s${bucket}.${rest}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getShardFromUrl(url: string): number | null {
|
|
49
|
+
const [isMatch, shard] = url.match(SHARDED_URL_RE) || []
|
|
50
|
+
return isMatch ? parseInt(shard, 10) : null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const middleware = {
|
|
54
|
+
processOptions: (options: {useDomainSharding?: boolean; url: string}) => {
|
|
55
|
+
if (!useDomainSharding(options)) {
|
|
56
|
+
return options
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const url = getShardedUrl(options.url)
|
|
60
|
+
options.url = url
|
|
61
|
+
|
|
62
|
+
return options
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
onRequest(req: {
|
|
66
|
+
options: Partial<RequestOptions> & {useDomainSharding?: boolean; url: string}
|
|
67
|
+
}) {
|
|
68
|
+
if (useDomainSharding(req.options)) {
|
|
69
|
+
incrementBucketForUrl(req.options.url)
|
|
70
|
+
}
|
|
71
|
+
return req
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
onResponse(
|
|
75
|
+
res,
|
|
76
|
+
context: {options: Partial<RequestOptions> & {useDomainSharding?: boolean; url: string}},
|
|
77
|
+
) {
|
|
78
|
+
if (useDomainSharding(context.options)) {
|
|
79
|
+
decrementBucketForUrl(context.options.url)
|
|
80
|
+
}
|
|
81
|
+
return res
|
|
82
|
+
},
|
|
83
|
+
} satisfies Middleware
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
middleware,
|
|
87
|
+
incrementBucketForUrl,
|
|
88
|
+
decrementBucketForUrl,
|
|
89
|
+
getShardedUrl,
|
|
90
|
+
getBuckets: () => buckets,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function useDomainSharding(options: RequestOptions | {useDomainSharding?: boolean}): boolean {
|
|
95
|
+
return 'useDomainSharding' in options && options.useDomainSharding === true
|
|
96
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {agent, debug, headers} from 'get-it/middleware'
|
|
2
2
|
|
|
3
3
|
import {name, version} from '../../package.json'
|
|
4
|
+
import {domainSharder} from './domainSharding'
|
|
4
5
|
|
|
5
6
|
const middleware = [
|
|
7
|
+
domainSharder.middleware,
|
|
8
|
+
|
|
6
9
|
debug({verbose: true, namespace: 'sanity:client'}),
|
|
7
10
|
headers({'User-Agent': `${name} ${version}`}),
|
|
8
11
|
|
package/src/types.ts
CHANGED
|
@@ -46,6 +46,14 @@ export interface ClientConfig {
|
|
|
46
46
|
apiVersion?: string
|
|
47
47
|
proxy?: string
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Spread the requests over a number of hostnames to work around HTTP/1.1 limitations.
|
|
51
|
+
* Only applicable in browsers, and for certain allowed projects.
|
|
52
|
+
*
|
|
53
|
+
* @alpha
|
|
54
|
+
*/
|
|
55
|
+
useDomainSharding?: boolean
|
|
56
|
+
|
|
49
57
|
/**
|
|
50
58
|
* Optional request tag prefix for all request tags
|
|
51
59
|
*/
|
package/src/warnings.ts
CHANGED
|
@@ -6,6 +6,11 @@ const createWarningPrinter = (message: string[]) =>
|
|
|
6
6
|
// eslint-disable-next-line no-console
|
|
7
7
|
once((...args: Any[]) => console.warn(message.join(' '), ...args))
|
|
8
8
|
|
|
9
|
+
export const printCdnAndWithCredentialsWarning = createWarningPrinter([
|
|
10
|
+
`Because you set \`withCredentials\` to true, we will override your \`useCdn\``,
|
|
11
|
+
`setting to be false since (cookie-based) credentials are never set on the CDN`,
|
|
12
|
+
])
|
|
13
|
+
|
|
9
14
|
export const printCdnWarning = createWarningPrinter([
|
|
10
15
|
`Since you haven't set a value for \`useCdn\`, we will deliver content using our`,
|
|
11
16
|
`global, edge-cached API-CDN. If you wish to have content delivered faster, set`,
|
package/umd/sanityClient.js
CHANGED
|
@@ -166,17 +166,12 @@
|
|
|
166
166
|
typeof result[key] > "u" ? result[key] = value : isArray$3(result[key]) ? result[key].push(value) : result[key] = [result[key], value];
|
|
167
167
|
}
|
|
168
168
|
return result;
|
|
169
|
-
}, parseHeaders$1 = /* @__PURE__ */ getDefaultExportFromCjs$1(parseHeaders), __defProp$4 = Object.defineProperty,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}, __privateGet$8 = (obj, member, getter) => (__accessCheck$8(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)), __privateAdd$8 = (obj, member, value) => {
|
|
173
|
-
if (member.has(obj))
|
|
174
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
175
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
176
|
-
}, __privateSet$8 = (obj, member, value, setter) => (__accessCheck$8(obj, member, "write to private field"), member.set(obj, value), value), _method, _url, _resHeaders, _headers, _controller, _init, _useAbortSignal;
|
|
169
|
+
}, parseHeaders$1 = /* @__PURE__ */ getDefaultExportFromCjs$1(parseHeaders), __defProp$4 = Object.defineProperty, __typeError = (msg) => {
|
|
170
|
+
throw TypeError(msg);
|
|
171
|
+
}, __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value, __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key != "symbol" ? key + "" : key, value), __accessCheck$8 = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg), __privateGet$8 = (obj, member, getter) => (__accessCheck$8(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)), __privateAdd$8 = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateSet$8 = (obj, member, value, setter) => (__accessCheck$8(obj, member, "write to private field"), member.set(obj, value), value), _method, _url, _resHeaders, _headers, _controller, _init, _useAbortSignal;
|
|
177
172
|
class FetchXhr {
|
|
178
173
|
constructor() {
|
|
179
|
-
__publicField$4(this, "onabort"), __publicField$4(this, "onerror"), __publicField$4(this, "onreadystatechange"), __publicField$4(this, "ontimeout"), __publicField$4(this, "readyState", 0), __publicField$4(this, "response"), __publicField$4(this, "responseText", ""), __publicField$4(this, "responseType", ""), __publicField$4(this, "status"), __publicField$4(this, "statusText"), __publicField$4(this, "withCredentials"), __privateAdd$8(this, _method
|
|
174
|
+
__publicField$4(this, "onabort"), __publicField$4(this, "onerror"), __publicField$4(this, "onreadystatechange"), __publicField$4(this, "ontimeout"), __publicField$4(this, "readyState", 0), __publicField$4(this, "response"), __publicField$4(this, "responseText", ""), __publicField$4(this, "responseType", ""), __publicField$4(this, "status"), __publicField$4(this, "statusText"), __publicField$4(this, "withCredentials"), __privateAdd$8(this, _method), __privateAdd$8(this, _url), __privateAdd$8(this, _resHeaders), __privateAdd$8(this, _headers, {}), __privateAdd$8(this, _controller), __privateAdd$8(this, _init, {}), __privateAdd$8(this, _useAbortSignal);
|
|
180
175
|
}
|
|
181
176
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- _async is only declared for typings compatibility
|
|
182
177
|
open(method, url, _async) {
|
|
@@ -203,10 +198,13 @@
|
|
|
203
198
|
headers: __privateGet$8(this, _headers),
|
|
204
199
|
body
|
|
205
200
|
};
|
|
206
|
-
typeof AbortController == "function" && __privateGet$8(this, _useAbortSignal) && (__privateSet$8(this, _controller, new AbortController()), typeof EventTarget < "u" && __privateGet$8(this, _controller).signal instanceof EventTarget && (options.signal = __privateGet$8(this, _controller).signal)), typeof document < "u" && (options.credentials = this.withCredentials ? "include" : "omit"), fetch(__privateGet$8(this, _url), options).then((res) =>
|
|
207
|
-
|
|
201
|
+
typeof AbortController == "function" && __privateGet$8(this, _useAbortSignal) && (__privateSet$8(this, _controller, new AbortController()), typeof EventTarget < "u" && __privateGet$8(this, _controller).signal instanceof EventTarget && (options.signal = __privateGet$8(this, _controller).signal)), typeof document < "u" && (options.credentials = this.withCredentials ? "include" : "omit"), fetch(__privateGet$8(this, _url), options).then((res) => {
|
|
202
|
+
var _a;
|
|
203
|
+
return res.headers.forEach((value, key) => {
|
|
204
|
+
__privateSet$8(this, _resHeaders, __privateGet$8(this, _resHeaders) + `${key}: ${value}\r
|
|
208
205
|
`);
|
|
209
|
-
|
|
206
|
+
}), this.status = res.status, this.statusText = res.statusText, this.readyState = 3, (_a = this.onreadystatechange) == null || _a.call(this), textBody ? res.text() : res.arrayBuffer();
|
|
207
|
+
}).then((resBody) => {
|
|
210
208
|
var _a;
|
|
211
209
|
typeof resBody == "string" ? this.responseText = resBody : this.response = resBody, this.readyState = 4, (_a = this.onreadystatechange) == null || _a.call(this);
|
|
212
210
|
}).catch((err) => {
|
|
@@ -307,8 +305,7 @@
|
|
|
307
305
|
|
|
308
306
|
var browser$3 = { exports: {} }, ms, hasRequiredMs;
|
|
309
307
|
function requireMs() {
|
|
310
|
-
if (hasRequiredMs)
|
|
311
|
-
return ms;
|
|
308
|
+
if (hasRequiredMs) return ms;
|
|
312
309
|
hasRequiredMs = 1;
|
|
313
310
|
var s = 1e3, m = s * 60, h = m * 60, d = h * 24, w = d * 7, y = d * 365.25;
|
|
314
311
|
ms = function(val, options) {
|
|
@@ -614,11 +611,9 @@
|
|
|
614
611
|
return Object.prototype.toString.call(o) === "[object Object]";
|
|
615
612
|
}
|
|
616
613
|
function isPlainObject$1(o) {
|
|
617
|
-
if (isObject(o) === !1)
|
|
618
|
-
return !1;
|
|
614
|
+
if (isObject(o) === !1) return !1;
|
|
619
615
|
const ctor = o.constructor;
|
|
620
|
-
if (ctor === void 0)
|
|
621
|
-
return !0;
|
|
616
|
+
if (ctor === void 0) return !0;
|
|
622
617
|
const prot = ctor.prototype;
|
|
623
618
|
return !(isObject(prot) === !1 || // eslint-disable-next-line no-prototype-builtins
|
|
624
619
|
prot.hasOwnProperty("isPrototypeOf") === !1);
|
|
@@ -697,7 +692,7 @@
|
|
|
697
692
|
}
|
|
698
693
|
};
|
|
699
694
|
}
|
|
700
|
-
var __defProp$1$1 = Object.defineProperty, __defNormalProp$1$1 = (obj, key, value) => key in obj ? __defProp$1$1(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value, __publicField$1$1 = (obj, key, value) =>
|
|
695
|
+
var __defProp$1$1 = Object.defineProperty, __defNormalProp$1$1 = (obj, key, value) => key in obj ? __defProp$1$1(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value, __publicField$1$1 = (obj, key, value) => __defNormalProp$1$1(obj, typeof key != "symbol" ? key + "" : key, value);
|
|
701
696
|
class Cancel {
|
|
702
697
|
constructor(message) {
|
|
703
698
|
__publicField$1$1(this, "__CANCEL__", !0), __publicField$1$1(this, "message"), this.message = message;
|
|
@@ -2303,7 +2298,10 @@ ${selectionOpts}`);
|
|
|
2303
2298
|
const createWarningPrinter = (message) => (
|
|
2304
2299
|
// eslint-disable-next-line no-console
|
|
2305
2300
|
once((...args) => console.warn(message.join(" "), ...args))
|
|
2306
|
-
),
|
|
2301
|
+
), printCdnAndWithCredentialsWarning = createWarningPrinter([
|
|
2302
|
+
"Because you set `withCredentials` to true, we will override your `useCdn`",
|
|
2303
|
+
"setting to be false since (cookie-based) credentials are never set on the CDN"
|
|
2304
|
+
]), printCdnWarning = createWarningPrinter([
|
|
2307
2305
|
"Since you haven't set a value for `useCdn`, we will deliver content using our",
|
|
2308
2306
|
"global, edge-cached API-CDN. If you wish to have content delivered faster, set",
|
|
2309
2307
|
"`useCdn: false` to use the Live API. Note: You may incur higher costs using the live API."
|
|
@@ -2381,7 +2379,7 @@ ${selectionOpts}`);
|
|
|
2381
2379
|
`stega.studioUrl must be a string or a function, received ${newConfig.stega.studioUrl}`
|
|
2382
2380
|
);
|
|
2383
2381
|
const isBrowser = typeof window < "u" && window.location && window.location.hostname, isLocalhost = isBrowser && isLocal(window.location.hostname);
|
|
2384
|
-
isBrowser && isLocalhost && newConfig.token && newConfig.ignoreBrowserTokenWarning !== !0 ? printBrowserTokenWarning() : typeof newConfig.useCdn > "u" && printCdnWarning(), projectBased && projectId(newConfig.projectId), newConfig.dataset && dataset(newConfig.dataset), "requestTagPrefix" in newConfig && (newConfig.requestTagPrefix = newConfig.requestTagPrefix ? requestTag(newConfig.requestTagPrefix).replace(/\.+$/, "") : void 0), newConfig.apiVersion = `${newConfig.apiVersion}`.replace(/^v/, ""), newConfig.isDefaultApi = newConfig.apiHost === defaultConfig.apiHost, newConfig.useCdn = newConfig.useCdn !== !1 && !newConfig.withCredentials, validateApiVersion(newConfig.apiVersion);
|
|
2382
|
+
isBrowser && isLocalhost && newConfig.token && newConfig.ignoreBrowserTokenWarning !== !0 ? printBrowserTokenWarning() : typeof newConfig.useCdn > "u" && printCdnWarning(), projectBased && projectId(newConfig.projectId), newConfig.dataset && dataset(newConfig.dataset), "requestTagPrefix" in newConfig && (newConfig.requestTagPrefix = newConfig.requestTagPrefix ? requestTag(newConfig.requestTagPrefix).replace(/\.+$/, "") : void 0), newConfig.apiVersion = `${newConfig.apiVersion}`.replace(/^v/, ""), newConfig.isDefaultApi = newConfig.apiHost === defaultConfig.apiHost, newConfig.useCdn === !0 && newConfig.withCredentials && printCdnAndWithCredentialsWarning(), newConfig.useCdn = newConfig.useCdn !== !1 && !newConfig.withCredentials, validateApiVersion(newConfig.apiVersion);
|
|
2385
2383
|
const hostParts = newConfig.apiHost.split("://", 2), protocol = hostParts[0], host = hostParts[1], cdnHost = newConfig.isDefaultApi ? defaultCdnHost : host;
|
|
2386
2384
|
return newConfig.useProjectHostname ? (newConfig.url = `${protocol}://${newConfig.projectId}.${host}/v${newConfig.apiVersion}`, newConfig.cdnUrl = `${protocol}://${newConfig.projectId}.${cdnHost}/v${newConfig.apiVersion}`) : (newConfig.url = `${newConfig.apiHost}/v${newConfig.apiVersion}`, newConfig.cdnUrl = newConfig.url), newConfig;
|
|
2387
2385
|
}, projectHeader = "X-Sanity-Project-ID";
|
|
@@ -2664,6 +2662,55 @@ ${selectionOpts}`);
|
|
|
2664
2662
|
opts
|
|
2665
2663
|
);
|
|
2666
2664
|
}
|
|
2665
|
+
const UNSHARDED_URL_RE = /^https:\/\/([a-z0-9]+)\.api\.(sanity\..*)/, SHARDED_URL_RE = /^https:\/\/[a-z0-9]+\.api\.s(\d+)\.sanity\.(.*)/, domainSharder = getDomainSharder();
|
|
2666
|
+
function getDomainSharder(initialBuckets) {
|
|
2667
|
+
const buckets = new Array(10).fill(0, 0);
|
|
2668
|
+
function incrementBucketForUrl(url) {
|
|
2669
|
+
const shard = getShardFromUrl(url);
|
|
2670
|
+
shard !== null && buckets[shard]++;
|
|
2671
|
+
}
|
|
2672
|
+
function decrementBucketForUrl(url) {
|
|
2673
|
+
const shard = getShardFromUrl(url);
|
|
2674
|
+
shard !== null && buckets[shard]--;
|
|
2675
|
+
}
|
|
2676
|
+
function getShardedUrl(url) {
|
|
2677
|
+
const [isMatch, projectId2, rest] = url.match(UNSHARDED_URL_RE) || [];
|
|
2678
|
+
if (!isMatch)
|
|
2679
|
+
return url;
|
|
2680
|
+
const bucket = buckets.reduce(
|
|
2681
|
+
(smallest, count, index) => count < buckets[smallest] ? index : smallest,
|
|
2682
|
+
0
|
|
2683
|
+
);
|
|
2684
|
+
return `https://${projectId2}.api.s${bucket}.${rest}`;
|
|
2685
|
+
}
|
|
2686
|
+
function getShardFromUrl(url) {
|
|
2687
|
+
const [isMatch, shard] = url.match(SHARDED_URL_RE) || [];
|
|
2688
|
+
return isMatch ? parseInt(shard, 10) : null;
|
|
2689
|
+
}
|
|
2690
|
+
return {
|
|
2691
|
+
middleware: {
|
|
2692
|
+
processOptions: (options) => {
|
|
2693
|
+
if (!useDomainSharding(options))
|
|
2694
|
+
return options;
|
|
2695
|
+
const url = getShardedUrl(options.url);
|
|
2696
|
+
return options.url = url, options;
|
|
2697
|
+
},
|
|
2698
|
+
onRequest(req) {
|
|
2699
|
+
return useDomainSharding(req.options) && incrementBucketForUrl(req.options.url), req;
|
|
2700
|
+
},
|
|
2701
|
+
onResponse(res, context) {
|
|
2702
|
+
return useDomainSharding(context.options) && decrementBucketForUrl(context.options.url), res;
|
|
2703
|
+
}
|
|
2704
|
+
},
|
|
2705
|
+
incrementBucketForUrl,
|
|
2706
|
+
decrementBucketForUrl,
|
|
2707
|
+
getShardedUrl,
|
|
2708
|
+
getBuckets: () => buckets
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
function useDomainSharding(options) {
|
|
2712
|
+
return "useDomainSharding" in options && options.useDomainSharding === !0;
|
|
2713
|
+
}
|
|
2667
2714
|
var defaults = (obj, defaults2) => Object.keys(defaults2).concat(Object.keys(obj)).reduce((target, prop) => (target[prop] = typeof obj[prop] > "u" ? defaults2[prop] : obj[prop], target), {});
|
|
2668
2715
|
const pick = (obj, props) => props.reduce((selection, prop) => (typeof obj[prop] > "u" || (selection[prop] = obj[prop]), selection), {}), MAX_URL_LENGTH = 14800, possibleOptions = [
|
|
2669
2716
|
"includePreviousRevision",
|
|
@@ -2675,15 +2722,16 @@ ${selectionOpts}`);
|
|
|
2675
2722
|
includeResult: !0
|
|
2676
2723
|
};
|
|
2677
2724
|
function _listen(query, params, opts = {}) {
|
|
2678
|
-
const { url, token, withCredentials, requestTagPrefix } = this.config(), tag = opts.tag && requestTagPrefix ? [requestTagPrefix, opts.tag].join(".") : opts.tag, options = { ...defaults(opts, defaultOptions), tag }, listenOpts = pick(options, possibleOptions), qs = encodeQueryString({ query, params, options: { tag, ...listenOpts } })
|
|
2679
|
-
|
|
2725
|
+
const { url, token, withCredentials, requestTagPrefix } = this.config(), tag = opts.tag && requestTagPrefix ? [requestTagPrefix, opts.tag].join(".") : opts.tag, options = { ...defaults(opts, defaultOptions), tag }, listenOpts = pick(options, possibleOptions), qs = encodeQueryString({ query, params, options: { tag, ...listenOpts } });
|
|
2726
|
+
let uri = `${url}${_getDataUrl(this, "listen", qs)}`;
|
|
2727
|
+
if (this.config().useDomainSharding && (uri = domainSharder.getShardedUrl(uri)), uri.length > MAX_URL_LENGTH)
|
|
2680
2728
|
return new Observable((observer) => observer.error(new Error("Query too large for listener")));
|
|
2681
2729
|
const listenFor = options.events ? options.events : ["mutation"], shouldEmitReconnect = listenFor.indexOf("reconnect") !== -1, esOptions = {};
|
|
2682
2730
|
return (token || withCredentials) && (esOptions.withCredentials = !0), token && (esOptions.headers = {
|
|
2683
2731
|
Authorization: `Bearer ${token}`
|
|
2684
2732
|
}), new Observable((observer) => {
|
|
2685
2733
|
let es, reconnectTimer, stopped = !1, unsubscribed = !1;
|
|
2686
|
-
open();
|
|
2734
|
+
domainSharder.incrementBucketForUrl(uri), open();
|
|
2687
2735
|
function onError() {
|
|
2688
2736
|
stopped || (emitReconnect(), !stopped && es.readyState === es.CLOSED && (unsubscribe(), clearTimeout(reconnectTimer), reconnectTimer = setTimeout(open, 100)));
|
|
2689
2737
|
}
|
|
@@ -2718,7 +2766,7 @@ ${selectionOpts}`);
|
|
|
2718
2766
|
});
|
|
2719
2767
|
}
|
|
2720
2768
|
function stop() {
|
|
2721
|
-
stopped = !0, unsubscribe(), unsubscribed = !0;
|
|
2769
|
+
stopped = !0, unsubscribe(), unsubscribed = !0, domainSharder.decrementBucketForUrl(uri);
|
|
2722
2770
|
}
|
|
2723
2771
|
return stop;
|
|
2724
2772
|
});
|
|
@@ -3329,6 +3377,7 @@ ${selectionOpts}`);
|
|
|
3329
3377
|
maxRedirects: 0,
|
|
3330
3378
|
maxRetries: config.maxRetries,
|
|
3331
3379
|
retryDelay: config.retryDelay,
|
|
3380
|
+
useDomainSharding: config.useDomainSharding,
|
|
3332
3381
|
...options
|
|
3333
3382
|
}),
|
|
3334
3383
|
config
|
|
@@ -3339,7 +3388,7 @@ ${selectionOpts}`);
|
|
|
3339
3388
|
return printNoDefaultExport(), createClient2(config);
|
|
3340
3389
|
};
|
|
3341
3390
|
}
|
|
3342
|
-
var envMiddleware = [];
|
|
3391
|
+
var envMiddleware = [domainSharder.middleware];
|
|
3343
3392
|
const exp = defineCreateClientExports(envMiddleware, SanityClient), requester = exp.requester, createClient = exp.createClient, deprecatedCreateClient = defineDeprecatedCreateClient(createClient);
|
|
3344
3393
|
|
|
3345
3394
|
const reKeySegment = /_key\s*==\s*['"](.*)['"]/;
|