@sanity/client 6.24.2 → 6.24.4
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/csm.d.cts +99 -34
- package/dist/csm.d.ts +99 -34
- package/dist/index.browser.cjs +188 -128
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.d.cts +112 -4
- package/dist/index.browser.d.ts +112 -4
- package/dist/index.browser.js +189 -129
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +189 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -4
- package/dist/index.d.ts +112 -4
- package/dist/index.js +190 -130
- package/dist/index.js.map +1 -1
- package/dist/stega.browser.d.cts +3419 -96
- package/dist/stega.browser.d.ts +3419 -96
- package/dist/stega.d.cts +3419 -96
- package/dist/stega.d.ts +3419 -96
- package/package.json +4 -4
- package/src/data/eventsource.ts +255 -0
- package/src/data/eventsourcePolyfill.ts +7 -0
- package/src/data/listen.ts +31 -142
- package/src/data/live.ts +60 -120
- package/src/data/reconnectOnConnectionFailure.ts +30 -0
- package/src/data/transaction.ts +26 -1
- package/src/defineCreateClient.ts +11 -0
- package/src/types.ts +10 -0
- package/umd/sanityClient.js +988 -144
- package/umd/sanityClient.min.js +2 -2
package/src/data/live.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import {Observable} from 'rxjs'
|
|
1
|
+
import {catchError, concat, EMPTY, mergeMap, Observable, of} from 'rxjs'
|
|
2
|
+
import {map} from 'rxjs/operators'
|
|
2
3
|
|
|
3
4
|
import {CorsOriginError} from '../http/errors'
|
|
4
5
|
import type {ObservableSanityClient, SanityClient} from '../SanityClient'
|
|
5
6
|
import type {
|
|
6
|
-
Any,
|
|
7
7
|
LiveEventMessage,
|
|
8
8
|
LiveEventReconnect,
|
|
9
9
|
LiveEventRestart,
|
|
10
10
|
LiveEventWelcome,
|
|
11
|
+
SyncTag,
|
|
11
12
|
} from '../types'
|
|
12
13
|
import {_getDataUrl} from './dataMethods'
|
|
14
|
+
import {connectEventSource} from './eventsource'
|
|
15
|
+
import {eventSourcePolyfill} from './eventsourcePolyfill'
|
|
16
|
+
import {reconnectOnConnectionFailure} from './reconnectOnConnectionFailure'
|
|
13
17
|
|
|
14
18
|
const requiredApiVersion = '2021-03-26'
|
|
15
19
|
|
|
@@ -72,8 +76,6 @@ export class LiveClient {
|
|
|
72
76
|
if (includeDrafts) {
|
|
73
77
|
url.searchParams.set('includeDrafts', 'true')
|
|
74
78
|
}
|
|
75
|
-
|
|
76
|
-
const listenFor = ['restart', 'message', 'welcome', 'reconnect'] as const
|
|
77
79
|
const esOptions: EventSourceInit & {headers?: Record<string, string>} = {}
|
|
78
80
|
if (includeDrafts && token) {
|
|
79
81
|
esOptions.headers = {
|
|
@@ -84,124 +86,62 @@ export class LiveClient {
|
|
|
84
86
|
esOptions.withCredentials = true
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const event = parseEvent(evt)
|
|
107
|
-
observer.error(new Error(event.message, {cause: event}))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Unless we've explicitly stopped the ES (in which case `stopped` should be true),
|
|
111
|
-
// we should never be in a disconnected state. By default, EventSource will reconnect
|
|
112
|
-
// automatically, in which case it sets readyState to `CONNECTING`, but in some cases
|
|
113
|
-
// (like when a laptop lid is closed), it closes the connection. In these cases we need
|
|
114
|
-
// to explicitly reconnect.
|
|
115
|
-
if (es!.readyState === es!.CLOSED) {
|
|
116
|
-
unsubscribe()
|
|
117
|
-
clearTimeout(reconnectTimer)
|
|
118
|
-
reconnectTimer = setTimeout(open, 100)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function onMessage(evt: Any) {
|
|
123
|
-
const event = parseEvent(evt)
|
|
124
|
-
return event instanceof Error ? observer.error(event) : observer.next(event)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function unsubscribe() {
|
|
128
|
-
if (!es) return
|
|
129
|
-
es.removeEventListener('error', onError)
|
|
130
|
-
for (const type of listenFor) {
|
|
131
|
-
es.removeEventListener(type, onMessage)
|
|
132
|
-
}
|
|
133
|
-
es.close()
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function getEventSource() {
|
|
137
|
-
const EventSourceImplementation: typeof EventSource =
|
|
138
|
-
typeof EventSource === 'undefined' || esOptions.headers || esOptions.withCredentials
|
|
139
|
-
? ((await import('@sanity/eventsource')).default as unknown as typeof EventSource)
|
|
140
|
-
: EventSource
|
|
141
|
-
|
|
142
|
-
// If the listener has been unsubscribed from before we managed to load the module,
|
|
143
|
-
// do not set up the EventSource.
|
|
144
|
-
if (unsubscribed) {
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Detect if CORS is allowed, the way the CORS is checked supports preflight caching, so when the EventSource boots up it knows it sees the preflight was already made and we're good to go
|
|
149
|
-
try {
|
|
150
|
-
await fetch(url, {
|
|
151
|
-
method: 'OPTIONS',
|
|
152
|
-
mode: 'cors',
|
|
153
|
-
credentials: esOptions.withCredentials ? 'include' : 'omit',
|
|
154
|
-
headers: esOptions.headers,
|
|
155
|
-
})
|
|
156
|
-
if (unsubscribed) {
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
} catch {
|
|
160
|
-
// If the request fails, then we assume it was due to CORS, and we rethrow a special error that allows special handling in userland
|
|
161
|
-
throw new CorsOriginError({projectId: projectId!})
|
|
89
|
+
const initEventSource = () =>
|
|
90
|
+
// use polyfill if there is no global EventSource or if we need to set headers
|
|
91
|
+
(typeof EventSource === 'undefined' || esOptions.headers
|
|
92
|
+
? eventSourcePolyfill
|
|
93
|
+
: of(EventSource)
|
|
94
|
+
).pipe(map((EventSource) => new EventSource(url.href, esOptions)))
|
|
95
|
+
|
|
96
|
+
const events = connectEventSource(initEventSource, [
|
|
97
|
+
'message',
|
|
98
|
+
'restart',
|
|
99
|
+
'welcome',
|
|
100
|
+
'reconnect',
|
|
101
|
+
]).pipe(
|
|
102
|
+
reconnectOnConnectionFailure(),
|
|
103
|
+
map((event) => {
|
|
104
|
+
if (event.type === 'message') {
|
|
105
|
+
const {data, ...rest} = event
|
|
106
|
+
// Splat data properties from the eventsource message onto the returned event
|
|
107
|
+
return {...rest, tags: (data as {tags: SyncTag[]}).tags} as LiveEventMessage
|
|
162
108
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
.catch((reason) => {
|
|
184
|
-
observer.error(reason)
|
|
185
|
-
stop()
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function stop() {
|
|
190
|
-
stopped = true
|
|
191
|
-
unsubscribe()
|
|
192
|
-
unsubscribed = true
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return stop
|
|
196
|
-
})
|
|
109
|
+
return event as LiveEventRestart | LiveEventReconnect | LiveEventWelcome
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// Detect if CORS is allowed, the way the CORS is checked supports preflight caching, so when the EventSource boots up it knows it sees the preflight was already made and we're good to go
|
|
114
|
+
const checkCors = fetchObservable(url, {
|
|
115
|
+
method: 'OPTIONS',
|
|
116
|
+
mode: 'cors',
|
|
117
|
+
credentials: esOptions.withCredentials ? 'include' : 'omit',
|
|
118
|
+
headers: esOptions.headers,
|
|
119
|
+
}).pipe(
|
|
120
|
+
mergeMap(() => EMPTY),
|
|
121
|
+
catchError(() => {
|
|
122
|
+
// If the request fails, then we assume it was due to CORS, and we rethrow a special error that allows special handling in userland
|
|
123
|
+
throw new CorsOriginError({projectId: projectId!})
|
|
124
|
+
}),
|
|
125
|
+
)
|
|
126
|
+
return concat(checkCors, events)
|
|
197
127
|
}
|
|
198
128
|
}
|
|
199
129
|
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
130
|
+
function fetchObservable(url: URL, init: RequestInit) {
|
|
131
|
+
return new Observable((observer) => {
|
|
132
|
+
const controller = new AbortController()
|
|
133
|
+
const signal = controller.signal
|
|
134
|
+
fetch(url, {...init, signal: controller.signal}).then(
|
|
135
|
+
(response) => {
|
|
136
|
+
observer.next(response)
|
|
137
|
+
observer.complete()
|
|
138
|
+
},
|
|
139
|
+
(err) => {
|
|
140
|
+
if (!signal.aborted) {
|
|
141
|
+
observer.error(err)
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
return () => controller.abort()
|
|
146
|
+
})
|
|
207
147
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
catchError,
|
|
3
|
+
concat,
|
|
4
|
+
mergeMap,
|
|
5
|
+
Observable,
|
|
6
|
+
of,
|
|
7
|
+
type OperatorFunction,
|
|
8
|
+
throwError,
|
|
9
|
+
timer,
|
|
10
|
+
} from 'rxjs'
|
|
11
|
+
|
|
12
|
+
import {ConnectionFailedError} from './eventsource'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Note: connection failure is not the same as network disconnect which may happen more frequent.
|
|
16
|
+
* The EventSource instance will automatically reconnect in case of a network disconnect, however,
|
|
17
|
+
* in some rare cases a ConnectionFailed Error will be thrown and this operator explicitly retries these
|
|
18
|
+
*/
|
|
19
|
+
export function reconnectOnConnectionFailure<T>(): OperatorFunction<T, T | {type: 'reconnect'}> {
|
|
20
|
+
return function (source: Observable<T>) {
|
|
21
|
+
return source.pipe(
|
|
22
|
+
catchError((err, caught) => {
|
|
23
|
+
if (err instanceof ConnectionFailedError) {
|
|
24
|
+
return concat(of({type: 'reconnect' as const}), timer(1000).pipe(mergeMap(() => caught)))
|
|
25
|
+
}
|
|
26
|
+
return throwError(() => err)
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/data/transaction.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
IdentifiedSanityDocumentStub,
|
|
8
8
|
MultipleMutationResult,
|
|
9
9
|
Mutation,
|
|
10
|
+
MutationSelection,
|
|
10
11
|
PatchOperations,
|
|
11
12
|
SanityDocument,
|
|
12
13
|
SanityDocumentStub,
|
|
@@ -213,6 +214,13 @@ export class Transaction extends BaseTransaction {
|
|
|
213
214
|
* @param patchOps - Operations to perform, or a builder function
|
|
214
215
|
*/
|
|
215
216
|
patch(documentId: string, patchOps?: PatchBuilder | PatchOperations): this
|
|
217
|
+
/**
|
|
218
|
+
* Performs a patch on the given selection. Can either be a builder function or an object of patch operations.
|
|
219
|
+
*
|
|
220
|
+
* @param selection - An object with `query` and optional `params`, defining which document(s) to patch
|
|
221
|
+
* @param patchOps - Operations to perform, or a builder function
|
|
222
|
+
*/
|
|
223
|
+
patch(patch: MutationSelection, patchOps?: PatchBuilder | PatchOperations): this
|
|
216
224
|
/**
|
|
217
225
|
* Adds the given patch instance to the transaction.
|
|
218
226
|
* The operation is added to the current transaction, ready to be commited by `commit()`
|
|
@@ -220,9 +228,15 @@ export class Transaction extends BaseTransaction {
|
|
|
220
228
|
* @param patch - Patch to execute
|
|
221
229
|
*/
|
|
222
230
|
patch(patch: Patch): this
|
|
223
|
-
patch(
|
|
231
|
+
patch(
|
|
232
|
+
patchOrDocumentId: Patch | MutationSelection | string,
|
|
233
|
+
patchOps?: PatchBuilder | PatchOperations,
|
|
234
|
+
): this {
|
|
224
235
|
const isBuilder = typeof patchOps === 'function'
|
|
225
236
|
const isPatch = typeof patchOrDocumentId !== 'string' && patchOrDocumentId instanceof Patch
|
|
237
|
+
const isMutationSelection =
|
|
238
|
+
typeof patchOrDocumentId === 'object' &&
|
|
239
|
+
('query' in patchOrDocumentId || 'id' in patchOrDocumentId)
|
|
226
240
|
|
|
227
241
|
// transaction.patch(client.patch('documentId').inc({visits: 1}))
|
|
228
242
|
if (isPatch) {
|
|
@@ -239,6 +253,17 @@ export class Transaction extends BaseTransaction {
|
|
|
239
253
|
return this._add({patch: patch.serialize()})
|
|
240
254
|
}
|
|
241
255
|
|
|
256
|
+
/**
|
|
257
|
+
* transaction.patch(
|
|
258
|
+
* {query: "*[_type == 'person' && points >= $threshold]", params: { threshold: 100 }},
|
|
259
|
+
* {dec: { points: 100 }, inc: { bonuses: 1 }}
|
|
260
|
+
* )
|
|
261
|
+
*/
|
|
262
|
+
if (isMutationSelection) {
|
|
263
|
+
const patch = new Patch(patchOrDocumentId, patchOps || {}, this.#client)
|
|
264
|
+
return this._add({patch: patch.serialize()})
|
|
265
|
+
}
|
|
266
|
+
|
|
242
267
|
return this._add({patch: {id: patchOrDocumentId, ...patchOps}})
|
|
243
268
|
}
|
|
244
269
|
}
|
|
@@ -4,6 +4,17 @@ import {defineHttpRequest} from './http/request'
|
|
|
4
4
|
import type {Any, ClientConfig, HttpRequest} from './types'
|
|
5
5
|
|
|
6
6
|
export {validateApiPerspective} from './config'
|
|
7
|
+
export {
|
|
8
|
+
ChannelError,
|
|
9
|
+
connectEventSource,
|
|
10
|
+
ConnectionFailedError,
|
|
11
|
+
DisconnectError,
|
|
12
|
+
type EventSourceEvent,
|
|
13
|
+
type EventSourceInstance,
|
|
14
|
+
MessageError,
|
|
15
|
+
MessageParseError,
|
|
16
|
+
type ServerSentEvent,
|
|
17
|
+
} from './data/eventsource'
|
|
7
18
|
export * from './data/patch'
|
|
8
19
|
export * from './data/transaction'
|
|
9
20
|
export {ClientError, CorsOriginError, ServerError} from './http/errors'
|
package/src/types.ts
CHANGED
|
@@ -854,6 +854,15 @@ export type ReconnectEvent = {
|
|
|
854
854
|
type: 'reconnect'
|
|
855
855
|
}
|
|
856
856
|
|
|
857
|
+
/**
|
|
858
|
+
* The listener connection has been established
|
|
859
|
+
* note: it's usually a better option to use the 'welcome' event
|
|
860
|
+
* @public
|
|
861
|
+
*/
|
|
862
|
+
export type OpenEvent = {
|
|
863
|
+
type: 'open'
|
|
864
|
+
}
|
|
865
|
+
|
|
857
866
|
/**
|
|
858
867
|
* The listener has been established, and will start receiving events.
|
|
859
868
|
* Note that this is also emitted upon _reconnection_.
|
|
@@ -872,6 +881,7 @@ export type ListenEvent<R extends Record<string, Any>> =
|
|
|
872
881
|
| DisconnectEvent
|
|
873
882
|
| ReconnectEvent
|
|
874
883
|
| WelcomeEvent
|
|
884
|
+
| OpenEvent
|
|
875
885
|
|
|
876
886
|
/** @public */
|
|
877
887
|
export type ListenEventName =
|