@sanity/client 0.0.0-dev.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/LICENSE +21 -0
- package/README.md +1225 -0
- package/dist/index.browser.cjs +1760 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.js +1737 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.cjs +1769 -0
- package/dist/index.cjs.js +16 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2234 -0
- package/dist/index.js +1746 -0
- package/dist/index.js.map +1 -0
- package/package.json +127 -0
- package/src/SanityClient.ts +1253 -0
- package/src/assets/AssetsClient.ts +191 -0
- package/src/config.ts +96 -0
- package/src/data/dataMethods.ts +416 -0
- package/src/data/encodeQueryString.ts +29 -0
- package/src/data/listen.ts +196 -0
- package/src/data/patch.ts +345 -0
- package/src/data/transaction.ts +358 -0
- package/src/datasets/DatasetsClient.ts +115 -0
- package/src/generateHelpUrl.ts +5 -0
- package/src/http/browserMiddleware.ts +1 -0
- package/src/http/errors.ts +68 -0
- package/src/http/nodeMiddleware.ts +11 -0
- package/src/http/request.ts +48 -0
- package/src/http/requestOptions.ts +33 -0
- package/src/index.browser.ts +28 -0
- package/src/index.ts +28 -0
- package/src/projects/ProjectsClient.ts +61 -0
- package/src/types.ts +575 -0
- package/src/users/UsersClient.ts +55 -0
- package/src/util/defaults.ts +10 -0
- package/src/util/getSelection.ts +21 -0
- package/src/util/once.ts +14 -0
- package/src/util/pick.ts +11 -0
- package/src/validators.ts +78 -0
- package/src/warnings.ts +30 -0
- package/umd/.gitkeep +1 -0
- package/umd/sanityClient.js +4840 -0
- package/umd/sanityClient.min.js +14 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {lastValueFrom, type Observable} from 'rxjs'
|
|
2
|
+
import {filter, map} from 'rxjs/operators'
|
|
3
|
+
|
|
4
|
+
import {_requestObservable} from '../data/dataMethods'
|
|
5
|
+
import type {ObservableSanityClient, SanityClient} from '../SanityClient'
|
|
6
|
+
import type {
|
|
7
|
+
Any,
|
|
8
|
+
HttpRequest,
|
|
9
|
+
HttpRequestEvent,
|
|
10
|
+
ResponseEvent,
|
|
11
|
+
SanityAssetDocument,
|
|
12
|
+
SanityImageAssetDocument,
|
|
13
|
+
UploadClientConfig,
|
|
14
|
+
} from '../types'
|
|
15
|
+
import * as validators from '../validators'
|
|
16
|
+
|
|
17
|
+
/** @internal */
|
|
18
|
+
export class ObservableAssetsClient {
|
|
19
|
+
#client: ObservableSanityClient
|
|
20
|
+
#httpRequest: HttpRequest
|
|
21
|
+
constructor(client: ObservableSanityClient, httpRequest: HttpRequest) {
|
|
22
|
+
this.#client = client
|
|
23
|
+
this.#httpRequest = httpRequest
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Uploads a file asset to the configured dataset
|
|
28
|
+
*
|
|
29
|
+
* @param assetType - Asset type (file)
|
|
30
|
+
* @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream.
|
|
31
|
+
* @param options - Options to use for the upload
|
|
32
|
+
*/
|
|
33
|
+
upload(
|
|
34
|
+
assetType: 'file',
|
|
35
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
36
|
+
options?: UploadClientConfig
|
|
37
|
+
): Observable<HttpRequestEvent<{document: SanityAssetDocument}>>
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Uploads an image asset to the configured dataset
|
|
41
|
+
*
|
|
42
|
+
* @param assetType - Asset type (image)
|
|
43
|
+
* @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream.
|
|
44
|
+
* @param options - Options to use for the upload
|
|
45
|
+
*/
|
|
46
|
+
upload(
|
|
47
|
+
assetType: 'image',
|
|
48
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
49
|
+
options?: UploadClientConfig
|
|
50
|
+
): Observable<HttpRequestEvent<{document: SanityImageAssetDocument}>>
|
|
51
|
+
/**
|
|
52
|
+
* Uploads a file or an image asset to the configured dataset
|
|
53
|
+
*
|
|
54
|
+
* @param assetType - Asset type (file/image)
|
|
55
|
+
* @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream.
|
|
56
|
+
* @param options - Options to use for the upload
|
|
57
|
+
*/
|
|
58
|
+
upload(
|
|
59
|
+
assetType: 'file' | 'image',
|
|
60
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
61
|
+
options?: UploadClientConfig
|
|
62
|
+
): Observable<HttpRequestEvent<{document: SanityAssetDocument | SanityImageAssetDocument}>>
|
|
63
|
+
upload(
|
|
64
|
+
assetType: 'file' | 'image',
|
|
65
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
66
|
+
options?: UploadClientConfig
|
|
67
|
+
): Observable<HttpRequestEvent<{document: SanityAssetDocument | SanityImageAssetDocument}>> {
|
|
68
|
+
return _upload(this.#client, this.#httpRequest, assetType, body, options)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** @internal */
|
|
73
|
+
export class AssetsClient {
|
|
74
|
+
#client: SanityClient
|
|
75
|
+
#httpRequest: HttpRequest
|
|
76
|
+
constructor(client: SanityClient, httpRequest: HttpRequest) {
|
|
77
|
+
this.#client = client
|
|
78
|
+
this.#httpRequest = httpRequest
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Uploads a file asset to the configured dataset
|
|
83
|
+
*
|
|
84
|
+
* @param assetType - Asset type (file)
|
|
85
|
+
* @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream.
|
|
86
|
+
* @param options - Options to use for the upload
|
|
87
|
+
*/
|
|
88
|
+
upload(
|
|
89
|
+
assetType: 'file',
|
|
90
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
91
|
+
options?: UploadClientConfig
|
|
92
|
+
): Promise<SanityAssetDocument>
|
|
93
|
+
/**
|
|
94
|
+
* Uploads an image asset to the configured dataset
|
|
95
|
+
*
|
|
96
|
+
* @param assetType - Asset type (image)
|
|
97
|
+
* @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream.
|
|
98
|
+
* @param options - Options to use for the upload
|
|
99
|
+
*/
|
|
100
|
+
upload(
|
|
101
|
+
assetType: 'image',
|
|
102
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
103
|
+
options?: UploadClientConfig
|
|
104
|
+
): Promise<SanityImageAssetDocument>
|
|
105
|
+
/**
|
|
106
|
+
* Uploads a file or an image asset to the configured dataset
|
|
107
|
+
*
|
|
108
|
+
* @param assetType - Asset type (file/image)
|
|
109
|
+
* @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream.
|
|
110
|
+
* @param options - Options to use for the upload
|
|
111
|
+
*/
|
|
112
|
+
upload(
|
|
113
|
+
assetType: 'file' | 'image',
|
|
114
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
115
|
+
options?: UploadClientConfig
|
|
116
|
+
): Promise<SanityAssetDocument | SanityImageAssetDocument>
|
|
117
|
+
upload(
|
|
118
|
+
assetType: 'file' | 'image',
|
|
119
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
120
|
+
options?: UploadClientConfig
|
|
121
|
+
): Promise<SanityAssetDocument | SanityImageAssetDocument> {
|
|
122
|
+
const observable = _upload(this.#client, this.#httpRequest, assetType, body, options)
|
|
123
|
+
return lastValueFrom(
|
|
124
|
+
observable.pipe(
|
|
125
|
+
filter((event: Any) => event.type === 'response'),
|
|
126
|
+
map(
|
|
127
|
+
(event) =>
|
|
128
|
+
(event as ResponseEvent<{document: SanityAssetDocument | SanityImageAssetDocument}>)
|
|
129
|
+
.body.document
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function _upload(
|
|
137
|
+
client: SanityClient | ObservableSanityClient,
|
|
138
|
+
httpRequest: HttpRequest,
|
|
139
|
+
assetType: 'image' | 'file',
|
|
140
|
+
body: File | Blob | Buffer | NodeJS.ReadableStream,
|
|
141
|
+
opts: UploadClientConfig = {}
|
|
142
|
+
): Observable<HttpRequestEvent<{document: SanityAssetDocument | SanityImageAssetDocument}>> {
|
|
143
|
+
validators.validateAssetType(assetType)
|
|
144
|
+
|
|
145
|
+
// If an empty array is given, explicitly set `none` to override API defaults
|
|
146
|
+
let meta = opts.extract || undefined
|
|
147
|
+
if (meta && !meta.length) {
|
|
148
|
+
meta = ['none']
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const dataset = validators.hasDataset(client.config())
|
|
152
|
+
const assetEndpoint = assetType === 'image' ? 'images' : 'files'
|
|
153
|
+
const options = optionsFromFile(opts, body)
|
|
154
|
+
const {tag, label, title, description, creditLine, filename, source} = options
|
|
155
|
+
const query: Any = {
|
|
156
|
+
label,
|
|
157
|
+
title,
|
|
158
|
+
description,
|
|
159
|
+
filename,
|
|
160
|
+
meta,
|
|
161
|
+
creditLine,
|
|
162
|
+
}
|
|
163
|
+
if (source) {
|
|
164
|
+
query.sourceId = source.id
|
|
165
|
+
query.sourceName = source.name
|
|
166
|
+
query.sourceUrl = source.url
|
|
167
|
+
}
|
|
168
|
+
return _requestObservable(client, httpRequest, {
|
|
169
|
+
tag,
|
|
170
|
+
method: 'POST',
|
|
171
|
+
timeout: options.timeout || 0,
|
|
172
|
+
uri: `/assets/${assetEndpoint}/${dataset}`,
|
|
173
|
+
headers: options.contentType ? {'Content-Type': options.contentType} : {},
|
|
174
|
+
query,
|
|
175
|
+
body,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function optionsFromFile(opts: Record<string, Any>, file: Any) {
|
|
180
|
+
if (typeof window === 'undefined' || !(file instanceof window.File)) {
|
|
181
|
+
return opts
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return Object.assign(
|
|
185
|
+
{
|
|
186
|
+
filename: opts.preserveFilename === false ? undefined : file.name,
|
|
187
|
+
contentType: file.type,
|
|
188
|
+
},
|
|
189
|
+
opts
|
|
190
|
+
)
|
|
191
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {generateHelpUrl} from './generateHelpUrl'
|
|
2
|
+
import type {ClientConfig, InitializedClientConfig} from './types'
|
|
3
|
+
import * as validate from './validators'
|
|
4
|
+
import * as warnings from './warnings'
|
|
5
|
+
|
|
6
|
+
const defaultCdnHost = 'apicdn.sanity.io'
|
|
7
|
+
export const defaultConfig = {
|
|
8
|
+
apiHost: 'https://api.sanity.io',
|
|
9
|
+
apiVersion: '1',
|
|
10
|
+
useProjectHostname: true,
|
|
11
|
+
} satisfies ClientConfig
|
|
12
|
+
|
|
13
|
+
const LOCALHOSTS = ['localhost', '127.0.0.1', '0.0.0.0']
|
|
14
|
+
const isLocal = (host: string) => LOCALHOSTS.indexOf(host) !== -1
|
|
15
|
+
|
|
16
|
+
export const validateApiVersion = function validateApiVersion(apiVersion: string) {
|
|
17
|
+
if (apiVersion === '1' || apiVersion === 'X') {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const apiDate = new Date(apiVersion)
|
|
22
|
+
const apiVersionValid =
|
|
23
|
+
/^\d{4}-\d{2}-\d{2}$/.test(apiVersion) && apiDate instanceof Date && apiDate.getTime() > 0
|
|
24
|
+
|
|
25
|
+
if (!apiVersionValid) {
|
|
26
|
+
throw new Error('Invalid API version string, expected `1` or date in format `YYYY-MM-DD`')
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const initConfig = (
|
|
31
|
+
config: Partial<ClientConfig>,
|
|
32
|
+
prevConfig: Partial<ClientConfig>
|
|
33
|
+
): InitializedClientConfig => {
|
|
34
|
+
const specifiedConfig = Object.assign({}, prevConfig, config)
|
|
35
|
+
if (!specifiedConfig.apiVersion) {
|
|
36
|
+
warnings.printNoApiVersionSpecifiedWarning()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const newConfig = Object.assign({} as InitializedClientConfig, defaultConfig, specifiedConfig)
|
|
40
|
+
const projectBased = newConfig.useProjectHostname
|
|
41
|
+
|
|
42
|
+
if (typeof Promise === 'undefined') {
|
|
43
|
+
const helpUrl = generateHelpUrl('js-client-promise-polyfill')
|
|
44
|
+
throw new Error(`No native Promise-implementation found, polyfill needed - see ${helpUrl}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (projectBased && !newConfig.projectId) {
|
|
48
|
+
throw new Error('Configuration must contain `projectId`')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isBrowser = typeof window !== 'undefined' && window.location && window.location.hostname
|
|
52
|
+
const isLocalhost = isBrowser && isLocal(window.location.hostname)
|
|
53
|
+
|
|
54
|
+
if (isBrowser && isLocalhost && newConfig.token && newConfig.ignoreBrowserTokenWarning !== true) {
|
|
55
|
+
warnings.printBrowserTokenWarning()
|
|
56
|
+
} else if (typeof newConfig.useCdn === 'undefined') {
|
|
57
|
+
warnings.printCdnWarning()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (projectBased) {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- the nullability here is wrong, as line 48 throws an error if it's undefined
|
|
62
|
+
validate.projectId(newConfig.projectId!)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (newConfig.dataset) {
|
|
66
|
+
validate.dataset(newConfig.dataset)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if ('requestTagPrefix' in newConfig) {
|
|
70
|
+
// Allow setting and unsetting request tag prefix
|
|
71
|
+
newConfig.requestTagPrefix = newConfig.requestTagPrefix
|
|
72
|
+
? validate.requestTag(newConfig.requestTagPrefix).replace(/\.+$/, '')
|
|
73
|
+
: undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
newConfig.apiVersion = `${newConfig.apiVersion}`.replace(/^v/, '')
|
|
77
|
+
newConfig.isDefaultApi = newConfig.apiHost === defaultConfig.apiHost
|
|
78
|
+
newConfig.useCdn = Boolean(newConfig.useCdn) && !newConfig.withCredentials
|
|
79
|
+
|
|
80
|
+
validateApiVersion(newConfig.apiVersion)
|
|
81
|
+
|
|
82
|
+
const hostParts = newConfig.apiHost.split('://', 2)
|
|
83
|
+
const protocol = hostParts[0]
|
|
84
|
+
const host = hostParts[1]
|
|
85
|
+
const cdnHost = newConfig.isDefaultApi ? defaultCdnHost : host
|
|
86
|
+
|
|
87
|
+
if (newConfig.useProjectHostname) {
|
|
88
|
+
newConfig.url = `${protocol}://${newConfig.projectId}.${host}/v${newConfig.apiVersion}`
|
|
89
|
+
newConfig.cdnUrl = `${protocol}://${newConfig.projectId}.${cdnHost}/v${newConfig.apiVersion}`
|
|
90
|
+
} else {
|
|
91
|
+
newConfig.url = `${newConfig.apiHost}/v${newConfig.apiVersion}`
|
|
92
|
+
newConfig.cdnUrl = newConfig.url
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return newConfig
|
|
96
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import {type MonoTypeOperatorFunction, Observable} from 'rxjs'
|
|
2
|
+
import {filter, map} from 'rxjs/operators'
|
|
3
|
+
|
|
4
|
+
import {requestOptions} from '../http/requestOptions'
|
|
5
|
+
import type {ObservableSanityClient, SanityClient} from '../SanityClient'
|
|
6
|
+
import type {
|
|
7
|
+
AllDocumentIdsMutationOptions,
|
|
8
|
+
AllDocumentsMutationOptions,
|
|
9
|
+
Any,
|
|
10
|
+
BaseMutationOptions,
|
|
11
|
+
FilteredResponseQueryOptions,
|
|
12
|
+
FirstDocumentIdMutationOptions,
|
|
13
|
+
FirstDocumentMutationOptions,
|
|
14
|
+
HttpRequest,
|
|
15
|
+
HttpRequestEvent,
|
|
16
|
+
IdentifiedSanityDocumentStub,
|
|
17
|
+
MultipleMutationResult,
|
|
18
|
+
Mutation,
|
|
19
|
+
MutationSelection,
|
|
20
|
+
QueryParams,
|
|
21
|
+
RawQueryResponse,
|
|
22
|
+
RequestObservableOptions,
|
|
23
|
+
RequestOptions,
|
|
24
|
+
SanityDocument,
|
|
25
|
+
SingleMutationResult,
|
|
26
|
+
UnfilteredResponseQueryOptions,
|
|
27
|
+
} from '../types'
|
|
28
|
+
import {getSelection} from '../util/getSelection'
|
|
29
|
+
import * as validate from '../validators'
|
|
30
|
+
import * as validators from '../validators'
|
|
31
|
+
import {encodeQueryString} from './encodeQueryString'
|
|
32
|
+
import {ObservablePatch, Patch} from './patch'
|
|
33
|
+
import {ObservableTransaction, Transaction} from './transaction'
|
|
34
|
+
|
|
35
|
+
const excludeFalsey = (param: Any, defValue: Any) => {
|
|
36
|
+
const value = typeof param === 'undefined' ? defValue : param
|
|
37
|
+
return param === false ? undefined : value
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const getMutationQuery = (options: BaseMutationOptions = {}) => {
|
|
41
|
+
return {
|
|
42
|
+
dryRun: options.dryRun,
|
|
43
|
+
returnIds: true,
|
|
44
|
+
returnDocuments: excludeFalsey(options.returnDocuments, true),
|
|
45
|
+
visibility: options.visibility || 'sync',
|
|
46
|
+
autoGenerateArrayKeys: options.autoGenerateArrayKeys,
|
|
47
|
+
skipCrossDatasetReferenceValidation: options.skipCrossDatasetReferenceValidation,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isResponse = (event: Any) => event.type === 'response'
|
|
52
|
+
const getBody = (event: Any) => event.body
|
|
53
|
+
|
|
54
|
+
const indexBy = (docs: Any[], attr: Any) =>
|
|
55
|
+
docs.reduce((indexed, doc) => {
|
|
56
|
+
indexed[attr(doc)] = doc
|
|
57
|
+
return indexed
|
|
58
|
+
}, Object.create(null))
|
|
59
|
+
|
|
60
|
+
const getQuerySizeLimit = 11264
|
|
61
|
+
|
|
62
|
+
/** @internal */
|
|
63
|
+
export function _fetch<R, Q extends QueryParams>(
|
|
64
|
+
client: ObservableSanityClient | SanityClient,
|
|
65
|
+
httpRequest: HttpRequest,
|
|
66
|
+
query: string,
|
|
67
|
+
params?: Q,
|
|
68
|
+
options: FilteredResponseQueryOptions | UnfilteredResponseQueryOptions = {}
|
|
69
|
+
): Observable<RawQueryResponse<R> | R> {
|
|
70
|
+
const mapResponse =
|
|
71
|
+
options.filterResponse === false ? (res: Any) => res : (res: Any) => res.result
|
|
72
|
+
|
|
73
|
+
return _dataRequest(client, httpRequest, 'query', {query, params}, options).pipe(map(mapResponse))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @internal */
|
|
77
|
+
export function _getDocument<R extends Record<string, Any>>(
|
|
78
|
+
client: ObservableSanityClient | SanityClient,
|
|
79
|
+
httpRequest: HttpRequest,
|
|
80
|
+
id: string,
|
|
81
|
+
opts: {tag?: string} = {}
|
|
82
|
+
): Observable<SanityDocument<R> | undefined> {
|
|
83
|
+
const options = {uri: _getDataUrl(client, 'doc', id), json: true, tag: opts.tag}
|
|
84
|
+
return _requestObservable<SanityDocument<R> | undefined>(client, httpRequest, options).pipe(
|
|
85
|
+
filter(isResponse),
|
|
86
|
+
map((event) => event.body.documents && event.body.documents[0])
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @internal */
|
|
91
|
+
export function _getDocuments<R extends Record<string, Any>>(
|
|
92
|
+
client: ObservableSanityClient | SanityClient,
|
|
93
|
+
httpRequest: HttpRequest,
|
|
94
|
+
ids: string[],
|
|
95
|
+
opts: {tag?: string} = {}
|
|
96
|
+
): Observable<(SanityDocument<R> | null)[]> {
|
|
97
|
+
const options = {uri: _getDataUrl(client, 'doc', ids.join(',')), json: true, tag: opts.tag}
|
|
98
|
+
return _requestObservable<(SanityDocument<R> | null)[]>(client, httpRequest, options).pipe(
|
|
99
|
+
filter(isResponse),
|
|
100
|
+
map((event: Any) => {
|
|
101
|
+
const indexed = indexBy(event.body.documents || [], (doc: Any) => doc._id)
|
|
102
|
+
return ids.map((id) => indexed[id] || null)
|
|
103
|
+
})
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @internal */
|
|
108
|
+
export function _createIfNotExists<R extends Record<string, Any>>(
|
|
109
|
+
client: ObservableSanityClient | SanityClient,
|
|
110
|
+
httpRequest: HttpRequest,
|
|
111
|
+
doc: IdentifiedSanityDocumentStub<R>,
|
|
112
|
+
options?:
|
|
113
|
+
| AllDocumentIdsMutationOptions
|
|
114
|
+
| AllDocumentsMutationOptions
|
|
115
|
+
| BaseMutationOptions
|
|
116
|
+
| FirstDocumentIdMutationOptions
|
|
117
|
+
| FirstDocumentMutationOptions
|
|
118
|
+
): Observable<
|
|
119
|
+
SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
|
|
120
|
+
> {
|
|
121
|
+
validators.requireDocumentId('createIfNotExists', doc)
|
|
122
|
+
return _create<R>(client, httpRequest, doc, 'createIfNotExists', options)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @internal */
|
|
126
|
+
export function _createOrReplace<R extends Record<string, Any>>(
|
|
127
|
+
client: ObservableSanityClient | SanityClient,
|
|
128
|
+
httpRequest: HttpRequest,
|
|
129
|
+
doc: IdentifiedSanityDocumentStub<R>,
|
|
130
|
+
options?:
|
|
131
|
+
| AllDocumentIdsMutationOptions
|
|
132
|
+
| AllDocumentsMutationOptions
|
|
133
|
+
| BaseMutationOptions
|
|
134
|
+
| FirstDocumentIdMutationOptions
|
|
135
|
+
| FirstDocumentMutationOptions
|
|
136
|
+
): Observable<
|
|
137
|
+
SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
|
|
138
|
+
> {
|
|
139
|
+
validators.requireDocumentId('createOrReplace', doc)
|
|
140
|
+
return _create<R>(client, httpRequest, doc, 'createOrReplace', options)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** @internal */
|
|
144
|
+
export function _delete<R extends Record<string, Any>>(
|
|
145
|
+
client: ObservableSanityClient | SanityClient,
|
|
146
|
+
httpRequest: HttpRequest,
|
|
147
|
+
selection: string | MutationSelection,
|
|
148
|
+
options?:
|
|
149
|
+
| AllDocumentIdsMutationOptions
|
|
150
|
+
| AllDocumentsMutationOptions
|
|
151
|
+
| BaseMutationOptions
|
|
152
|
+
| FirstDocumentIdMutationOptions
|
|
153
|
+
| FirstDocumentMutationOptions
|
|
154
|
+
): Observable<
|
|
155
|
+
SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
|
|
156
|
+
> {
|
|
157
|
+
return _dataRequest(
|
|
158
|
+
client,
|
|
159
|
+
httpRequest,
|
|
160
|
+
'mutate',
|
|
161
|
+
{mutations: [{delete: getSelection(selection)}]},
|
|
162
|
+
options
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @internal */
|
|
167
|
+
export function _mutate<R extends Record<string, Any>>(
|
|
168
|
+
client: SanityClient | ObservableSanityClient,
|
|
169
|
+
httpRequest: HttpRequest,
|
|
170
|
+
mutations: Mutation<R>[] | Patch | ObservablePatch | Transaction | ObservableTransaction,
|
|
171
|
+
options?:
|
|
172
|
+
| AllDocumentIdsMutationOptions
|
|
173
|
+
| AllDocumentsMutationOptions
|
|
174
|
+
| BaseMutationOptions
|
|
175
|
+
| FirstDocumentIdMutationOptions
|
|
176
|
+
| FirstDocumentMutationOptions
|
|
177
|
+
): Observable<
|
|
178
|
+
SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
|
|
179
|
+
> {
|
|
180
|
+
const mut =
|
|
181
|
+
mutations instanceof Patch ||
|
|
182
|
+
mutations instanceof ObservablePatch ||
|
|
183
|
+
mutations instanceof Transaction ||
|
|
184
|
+
mutations instanceof ObservableTransaction
|
|
185
|
+
? mutations.serialize()
|
|
186
|
+
: mutations
|
|
187
|
+
|
|
188
|
+
const muts = Array.isArray(mut) ? mut : [mut]
|
|
189
|
+
const transactionId = options && (options as Any).transactionId
|
|
190
|
+
return _dataRequest(client, httpRequest, 'mutate', {mutations: muts, transactionId}, options)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @internal
|
|
195
|
+
*/
|
|
196
|
+
export function _dataRequest(
|
|
197
|
+
client: SanityClient | ObservableSanityClient,
|
|
198
|
+
httpRequest: HttpRequest,
|
|
199
|
+
endpoint: string,
|
|
200
|
+
body: Any,
|
|
201
|
+
options: Any = {}
|
|
202
|
+
): Any {
|
|
203
|
+
const isMutation = endpoint === 'mutate'
|
|
204
|
+
const isQuery = endpoint === 'query'
|
|
205
|
+
|
|
206
|
+
// Check if the query string is within a configured threshold,
|
|
207
|
+
// in which case we can use GET. Otherwise, use POST.
|
|
208
|
+
const strQuery = isMutation ? '' : encodeQueryString(body)
|
|
209
|
+
const useGet = !isMutation && strQuery.length < getQuerySizeLimit
|
|
210
|
+
const stringQuery = useGet ? strQuery : ''
|
|
211
|
+
const returnFirst = options.returnFirst
|
|
212
|
+
const {timeout, token, tag, headers} = options
|
|
213
|
+
|
|
214
|
+
const uri = _getDataUrl(client, endpoint, stringQuery)
|
|
215
|
+
|
|
216
|
+
const reqOptions = {
|
|
217
|
+
method: useGet ? 'GET' : 'POST',
|
|
218
|
+
uri: uri,
|
|
219
|
+
json: true,
|
|
220
|
+
body: useGet ? undefined : body,
|
|
221
|
+
query: isMutation && getMutationQuery(options),
|
|
222
|
+
timeout,
|
|
223
|
+
headers,
|
|
224
|
+
token,
|
|
225
|
+
tag,
|
|
226
|
+
canUseCdn: isQuery,
|
|
227
|
+
signal: options.signal,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return _requestObservable(client, httpRequest, reqOptions).pipe(
|
|
231
|
+
filter(isResponse),
|
|
232
|
+
map(getBody),
|
|
233
|
+
map((res) => {
|
|
234
|
+
if (!isMutation) {
|
|
235
|
+
return res
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Should we return documents?
|
|
239
|
+
const results = res.results || []
|
|
240
|
+
if (options.returnDocuments) {
|
|
241
|
+
return returnFirst
|
|
242
|
+
? results[0] && results[0].document
|
|
243
|
+
: results.map((mut: Any) => mut.document)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Return a reduced subset
|
|
247
|
+
const key = returnFirst ? 'documentId' : 'documentIds'
|
|
248
|
+
const ids = returnFirst ? results[0] && results[0].id : results.map((mut: Any) => mut.id)
|
|
249
|
+
return {
|
|
250
|
+
transactionId: res.transactionId,
|
|
251
|
+
results: results,
|
|
252
|
+
[key]: ids,
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @internal
|
|
260
|
+
*/
|
|
261
|
+
export function _create<R extends Record<string, Any>>(
|
|
262
|
+
client: SanityClient | ObservableSanityClient,
|
|
263
|
+
httpRequest: HttpRequest,
|
|
264
|
+
doc: Any,
|
|
265
|
+
op: Any,
|
|
266
|
+
options: Any = {}
|
|
267
|
+
): Observable<
|
|
268
|
+
SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
|
|
269
|
+
> {
|
|
270
|
+
const mutation = {[op]: doc}
|
|
271
|
+
const opts = Object.assign({returnFirst: true, returnDocuments: true}, options)
|
|
272
|
+
return _dataRequest(client, httpRequest, 'mutate', {mutations: [mutation]}, opts)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @internal
|
|
277
|
+
*/
|
|
278
|
+
export function _requestObservable<R>(
|
|
279
|
+
client: SanityClient | ObservableSanityClient,
|
|
280
|
+
httpRequest: HttpRequest,
|
|
281
|
+
options: RequestObservableOptions
|
|
282
|
+
): Observable<HttpRequestEvent<R>> {
|
|
283
|
+
const uri = options.url || (options.uri as string)
|
|
284
|
+
const config = client.config()
|
|
285
|
+
|
|
286
|
+
// If the `canUseCdn`-option is not set we detect it automatically based on the method + URL.
|
|
287
|
+
// Only the /data endpoint is currently available through API-CDN.
|
|
288
|
+
const canUseCdn =
|
|
289
|
+
typeof options.canUseCdn === 'undefined'
|
|
290
|
+
? ['GET', 'HEAD'].indexOf(options.method || 'GET') >= 0 && uri.indexOf('/data/') === 0
|
|
291
|
+
: options.canUseCdn
|
|
292
|
+
|
|
293
|
+
const useCdn = config.useCdn && canUseCdn
|
|
294
|
+
|
|
295
|
+
const tag =
|
|
296
|
+
options.tag && config.requestTagPrefix
|
|
297
|
+
? [config.requestTagPrefix, options.tag].join('.')
|
|
298
|
+
: options.tag || config.requestTagPrefix
|
|
299
|
+
|
|
300
|
+
if (tag) {
|
|
301
|
+
options.query = {tag: validate.requestTag(tag), ...options.query}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (config.unstable_overlayDrafts) {
|
|
305
|
+
if (config.apiVersion !== 'X') {
|
|
306
|
+
// eslint-disable-next-line no-console
|
|
307
|
+
console.error('You need to set `apiVersion` to `X` to use `unstable_overlayDrafts')
|
|
308
|
+
}
|
|
309
|
+
options.query = {overlayDrafts: true, ...options.query}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const reqOptions = requestOptions(
|
|
313
|
+
config,
|
|
314
|
+
Object.assign({}, options, {
|
|
315
|
+
url: _getUrl(client, uri, useCdn),
|
|
316
|
+
})
|
|
317
|
+
) as RequestOptions
|
|
318
|
+
|
|
319
|
+
const request = new Observable<HttpRequestEvent<R>>((subscriber) =>
|
|
320
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- the typings thinks it's optional because it's not required to specify it when calling createClient, but it's always defined in practice since SanityClient provides a default
|
|
321
|
+
httpRequest(reqOptions, config.requester!).subscribe(subscriber)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
return options.signal ? request.pipe(_withAbortSignal(options.signal)) : request
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @internal
|
|
329
|
+
*/
|
|
330
|
+
export function _request<R>(
|
|
331
|
+
client: SanityClient | ObservableSanityClient,
|
|
332
|
+
httpRequest: HttpRequest,
|
|
333
|
+
options: Any
|
|
334
|
+
): Observable<R> {
|
|
335
|
+
const observable = _requestObservable<R>(client, httpRequest, options).pipe(
|
|
336
|
+
filter((event: Any) => event.type === 'response'),
|
|
337
|
+
map((event: Any) => event.body)
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
return observable
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @internal
|
|
345
|
+
*/
|
|
346
|
+
export function _getDataUrl(
|
|
347
|
+
client: SanityClient | ObservableSanityClient,
|
|
348
|
+
operation: string,
|
|
349
|
+
path?: string
|
|
350
|
+
): string {
|
|
351
|
+
const config = client.config()
|
|
352
|
+
const catalog = validators.hasDataset(config)
|
|
353
|
+
const baseUri = `/${operation}/${catalog}`
|
|
354
|
+
const uri = path ? `${baseUri}/${path}` : baseUri
|
|
355
|
+
return `/data${uri}`.replace(/\/($|\?)/, '$1')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @internal
|
|
360
|
+
*/
|
|
361
|
+
export function _getUrl(
|
|
362
|
+
client: SanityClient | ObservableSanityClient,
|
|
363
|
+
uri: string,
|
|
364
|
+
canUseCdn = false
|
|
365
|
+
): string {
|
|
366
|
+
const {url, cdnUrl} = client.config()
|
|
367
|
+
const base = canUseCdn ? cdnUrl : url
|
|
368
|
+
return `${base}/${uri.replace(/^\//, '')}`
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @internal
|
|
373
|
+
*/
|
|
374
|
+
function _withAbortSignal<T>(signal: AbortSignal): MonoTypeOperatorFunction<T> {
|
|
375
|
+
return (input) => {
|
|
376
|
+
return new Observable((observer) => {
|
|
377
|
+
const abort = () => observer.error(_createAbortError(signal))
|
|
378
|
+
|
|
379
|
+
if (signal && signal.aborted) {
|
|
380
|
+
abort()
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
const subscription = input.subscribe(observer)
|
|
384
|
+
signal.addEventListener('abort', abort)
|
|
385
|
+
return () => {
|
|
386
|
+
signal.removeEventListener('abort', abort)
|
|
387
|
+
subscription.unsubscribe()
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// DOMException is supported on most modern browsers and Node.js 18+.
|
|
393
|
+
// @see https://developer.mozilla.org/en-US/docs/Web/API/DOMException#browser_compatibility
|
|
394
|
+
const isDomExceptionSupported = Boolean(globalThis.DOMException)
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @internal
|
|
398
|
+
* @param signal
|
|
399
|
+
* Original source copied from https://github.com/sindresorhus/ky/blob/740732c78aad97e9aec199e9871bdbf0ae29b805/source/errors/DOMException.ts
|
|
400
|
+
* TODO: When targeting Node.js 18, use `signal.throwIfAborted()` (https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/throwIfAborted)
|
|
401
|
+
*/
|
|
402
|
+
function _createAbortError(signal?: AbortSignal) {
|
|
403
|
+
/*
|
|
404
|
+
NOTE: Use DomException with AbortError name as specified in MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort)
|
|
405
|
+
> When abort() is called, the fetch() promise rejects with an Error of type DOMException, with name AbortError.
|
|
406
|
+
*/
|
|
407
|
+
if (isDomExceptionSupported) {
|
|
408
|
+
return new DOMException(signal?.reason ?? 'The operation was aborted.', 'AbortError')
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// DOMException not supported. Fall back to use of error and override name.
|
|
412
|
+
const error = new Error(signal?.reason ?? 'The operation was aborted.')
|
|
413
|
+
error.name = 'AbortError'
|
|
414
|
+
|
|
415
|
+
return error
|
|
416
|
+
}
|