@sanity/sdk 0.0.0-alpha.21 → 0.0.0-alpha.23
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.d.ts +428 -325
- package/dist/index.js +1618 -1553
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
- package/src/_exports/index.ts +31 -30
- package/src/auth/authStore.test.ts +149 -104
- package/src/auth/authStore.ts +51 -100
- package/src/auth/handleAuthCallback.test.ts +67 -34
- package/src/auth/handleAuthCallback.ts +8 -7
- package/src/auth/logout.test.ts +61 -29
- package/src/auth/logout.ts +26 -28
- package/src/auth/refreshStampedToken.test.ts +9 -9
- package/src/auth/refreshStampedToken.ts +62 -56
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
- package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
- package/src/client/clientStore.test.ts +131 -67
- package/src/client/clientStore.ts +117 -116
- package/src/comlink/controller/actions/destroyController.test.ts +38 -13
- package/src/comlink/controller/actions/destroyController.ts +11 -15
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
- package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
- package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
- package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
- package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
- package/src/comlink/controller/actions/releaseChannel.ts +22 -21
- package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
- package/src/comlink/controller/comlinkControllerStore.ts +44 -5
- package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
- package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
- package/src/comlink/node/actions/releaseNode.test.ts +75 -55
- package/src/comlink/node/actions/releaseNode.ts +19 -21
- package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
- package/src/comlink/node/comlinkNodeStore.ts +22 -5
- package/src/config/authConfig.ts +79 -0
- package/src/config/sanityConfig.ts +48 -0
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +18 -5
- package/src/document/actions.test.ts +22 -10
- package/src/document/actions.ts +44 -56
- package/src/document/applyDocumentActions.test.ts +96 -36
- package/src/document/applyDocumentActions.ts +140 -99
- package/src/document/documentStore.test.ts +103 -155
- package/src/document/documentStore.ts +247 -237
- package/src/document/listen.ts +56 -55
- package/src/document/patchOperations.ts +0 -43
- package/src/document/permissions.test.ts +25 -12
- package/src/document/permissions.ts +11 -4
- package/src/document/processActions.test.ts +41 -8
- package/src/document/reducers.test.ts +87 -16
- package/src/document/reducers.ts +2 -2
- package/src/document/sharedListener.test.ts +34 -16
- package/src/document/sharedListener.ts +33 -11
- package/src/preview/getPreviewState.test.ts +40 -39
- package/src/preview/getPreviewState.ts +68 -56
- package/src/preview/previewConstants.ts +43 -0
- package/src/preview/previewQuery.test.ts +1 -1
- package/src/preview/previewQuery.ts +4 -5
- package/src/preview/previewStore.test.ts +13 -58
- package/src/preview/previewStore.ts +7 -21
- package/src/preview/resolvePreview.test.ts +33 -104
- package/src/preview/resolvePreview.ts +11 -21
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
- package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
- package/src/preview/util.ts +1 -0
- package/src/project/project.test.ts +3 -3
- package/src/project/project.ts +28 -5
- package/src/projection/getProjectionState.test.ts +69 -49
- package/src/projection/getProjectionState.ts +42 -50
- package/src/projection/projectionQuery.ts +1 -1
- package/src/projection/projectionStore.test.ts +13 -51
- package/src/projection/projectionStore.ts +6 -18
- package/src/projection/resolveProjection.test.ts +32 -127
- package/src/projection/resolveProjection.ts +15 -28
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +105 -90
- package/src/projection/subscribeToStateAndFetchBatches.ts +94 -81
- package/src/projection/util.ts +2 -0
- package/src/projects/projects.test.ts +13 -4
- package/src/projects/projects.ts +6 -1
- package/src/query/queryStore.test.ts +10 -47
- package/src/query/queryStore.ts +151 -133
- package/src/query/queryStoreConstants.ts +2 -0
- package/src/store/createActionBinder.test.ts +153 -0
- package/src/store/createActionBinder.ts +176 -0
- package/src/store/createSanityInstance.test.ts +84 -0
- package/src/store/createSanityInstance.ts +124 -0
- package/src/store/createStateSourceAction.test.ts +196 -0
- package/src/store/createStateSourceAction.ts +260 -0
- package/src/store/createStoreInstance.test.ts +81 -0
- package/src/store/createStoreInstance.ts +80 -0
- package/src/store/createStoreState.test.ts +85 -0
- package/src/store/createStoreState.ts +92 -0
- package/src/store/defineStore.test.ts +18 -0
- package/src/store/defineStore.ts +81 -0
- package/src/users/reducers.test.ts +318 -0
- package/src/users/reducers.ts +88 -0
- package/src/users/types.ts +46 -4
- package/src/users/usersConstants.ts +4 -0
- package/src/users/usersStore.test.ts +350 -223
- package/src/users/usersStore.ts +285 -149
- package/src/utils/createFetcherStore.test.ts +6 -7
- package/src/utils/createFetcherStore.ts +150 -153
- package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
- package/src/auth/fetchLoginUrls.test.ts +0 -163
- package/src/auth/fetchLoginUrls.ts +0 -74
- package/src/common/createLiveEventSubscriber.test.ts +0 -121
- package/src/common/createLiveEventSubscriber.ts +0 -55
- package/src/common/types.ts +0 -4
- package/src/instance/identity.test.ts +0 -46
- package/src/instance/identity.ts +0 -29
- package/src/instance/sanityInstance.test.ts +0 -77
- package/src/instance/sanityInstance.ts +0 -57
- package/src/instance/types.ts +0 -37
- package/src/preview/getPreviewProjection.ts +0 -45
- package/src/resources/README.md +0 -370
- package/src/resources/createAction.test.ts +0 -101
- package/src/resources/createAction.ts +0 -44
- package/src/resources/createResource.test.ts +0 -112
- package/src/resources/createResource.ts +0 -102
- package/src/resources/createStateSourceAction.test.ts +0 -114
- package/src/resources/createStateSourceAction.ts +0 -83
- package/src/resources/createStore.test.ts +0 -67
- package/src/resources/createStore.ts +0 -46
- package/src/store/createStore.test.ts +0 -108
- package/src/store/createStore.ts +0 -106
- /package/src/{common/util.ts → utils/hashString.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {type Action
|
|
1
|
+
import {type Action} from '@sanity/client'
|
|
2
2
|
import {getPublishedId} from '@sanity/client/csm'
|
|
3
3
|
import {type SanityDocument} from '@sanity/types'
|
|
4
4
|
import {type ExprNode} from 'groq-js'
|
|
@@ -26,16 +26,17 @@ import {
|
|
|
26
26
|
} from 'rxjs'
|
|
27
27
|
|
|
28
28
|
import {getClientState} from '../client/clientStore'
|
|
29
|
-
import {type
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {createStateSourceAction, type StateSource} from '../
|
|
29
|
+
import {type DocumentHandle} from '../config/sanityConfig'
|
|
30
|
+
import {bindActionByDataset, type StoreAction} from '../store/createActionBinder'
|
|
31
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
32
|
+
import {createStateSourceAction, type StateSource} from '../store/createStateSourceAction'
|
|
33
|
+
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
33
34
|
import {getDraftId} from '../utils/ids'
|
|
34
35
|
import {type DocumentAction} from './actions'
|
|
35
36
|
import {API_VERSION, INITIAL_OUTGOING_THROTTLE_TIME} from './documentConstants'
|
|
36
37
|
import {type DocumentEvent, getDocumentEvents} from './events'
|
|
37
38
|
import {listen, OutOfSyncError} from './listen'
|
|
38
|
-
import {type
|
|
39
|
+
import {type JsonMatch, jsonMatch, type JsonMatchPath} from './patchOperations'
|
|
39
40
|
import {calculatePermissions, createGrantsLookup, type DatasetAcl, type Grant} from './permissions'
|
|
40
41
|
import {ActionError} from './processActions'
|
|
41
42
|
import {
|
|
@@ -52,7 +53,7 @@ import {
|
|
|
52
53
|
transitionAppliedTransactionsToOutgoing,
|
|
53
54
|
type UnverifiedDocumentRevision,
|
|
54
55
|
} from './reducers'
|
|
55
|
-
import {createFetchDocument, createSharedListener} from './sharedListener'
|
|
56
|
+
import {createFetchDocument, createSharedListener, type SharedListener} from './sharedListener'
|
|
56
57
|
|
|
57
58
|
export interface DocumentStoreState {
|
|
58
59
|
documentStates: {[TDocumentId in string]?: DocumentState}
|
|
@@ -61,7 +62,7 @@ export interface DocumentStoreState {
|
|
|
61
62
|
outgoing?: OutgoingTransaction
|
|
62
63
|
grants?: Record<Grant, ExprNode>
|
|
63
64
|
error?: unknown
|
|
64
|
-
sharedListener:
|
|
65
|
+
sharedListener: SharedListener
|
|
65
66
|
fetchDocument: (documentId: string) => Observable<SanityDocument | null>
|
|
66
67
|
events: Subject<DocumentEvent>
|
|
67
68
|
}
|
|
@@ -101,7 +102,7 @@ export interface DocumentState {
|
|
|
101
102
|
unverifiedRevisions?: {[TTransactionId in string]?: UnverifiedDocumentRevision}
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
export const documentStore =
|
|
105
|
+
export const documentStore = defineStore<DocumentStoreState>({
|
|
105
106
|
name: 'Document',
|
|
106
107
|
getInitialState: (instance) => ({
|
|
107
108
|
documentStates: {},
|
|
@@ -112,17 +113,18 @@ export const documentStore = createResource<DocumentStoreState>({
|
|
|
112
113
|
fetchDocument: createFetchDocument(instance),
|
|
113
114
|
events: new Subject(),
|
|
114
115
|
}),
|
|
115
|
-
initialize() {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
initialize(context) {
|
|
117
|
+
const {sharedListener} = context.state.get()
|
|
118
|
+
const subscriptions = [
|
|
119
|
+
subscribeToQueuedAndApplyNextTransaction(context),
|
|
120
|
+
subscribeToSubscriptionsAndListenToDocuments(context),
|
|
121
|
+
subscribeToAppliedAndSubmitNextTransaction(context),
|
|
122
|
+
subscribeToClientAndFetchDatasetAcl(context),
|
|
123
|
+
]
|
|
120
124
|
|
|
121
125
|
return () => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
appliedSubscription.unsubscribe()
|
|
125
|
-
clientSubscription.unsubscribe()
|
|
126
|
+
sharedListener.dispose()
|
|
127
|
+
subscriptions.forEach((subscription) => subscription.unsubscribe())
|
|
126
128
|
}
|
|
127
129
|
},
|
|
128
130
|
})
|
|
@@ -132,18 +134,18 @@ export function getDocumentState<
|
|
|
132
134
|
TDocument extends SanityDocument,
|
|
133
135
|
TPath extends JsonMatchPath<TDocument>,
|
|
134
136
|
>(
|
|
135
|
-
instance: SanityInstance
|
|
137
|
+
instance: SanityInstance,
|
|
136
138
|
doc: string | DocumentHandle<TDocument>,
|
|
137
139
|
path: TPath,
|
|
138
140
|
): StateSource<JsonMatch<TDocument, TPath> | undefined>
|
|
139
141
|
/** @beta */
|
|
140
142
|
export function getDocumentState<TDocument extends SanityDocument>(
|
|
141
|
-
instance: SanityInstance
|
|
143
|
+
instance: SanityInstance,
|
|
142
144
|
doc: string | DocumentHandle<TDocument>,
|
|
143
145
|
): StateSource<TDocument | null>
|
|
144
146
|
/** @beta */
|
|
145
147
|
export function getDocumentState(
|
|
146
|
-
instance: SanityInstance
|
|
148
|
+
instance: SanityInstance,
|
|
147
149
|
doc: string | DocumentHandle,
|
|
148
150
|
path?: string,
|
|
149
151
|
): StateSource<unknown>
|
|
@@ -153,32 +155,35 @@ export function getDocumentState(
|
|
|
153
155
|
): StateSource<unknown> {
|
|
154
156
|
return _getDocumentState(...args)
|
|
155
157
|
}
|
|
156
|
-
const _getDocumentState =
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
const _getDocumentState = bindActionByDataset(
|
|
159
|
+
documentStore,
|
|
160
|
+
createStateSourceAction({
|
|
161
|
+
selector: ({state: {error, documentStates}}, doc: string | DocumentHandle, path?: string) => {
|
|
162
|
+
const documentId = typeof doc === 'string' ? doc : doc.documentId
|
|
163
|
+
if (error) throw error
|
|
164
|
+
const draftId = getDraftId(documentId)
|
|
165
|
+
const publishedId = getPublishedId(documentId)
|
|
166
|
+
const draft = documentStates[draftId]?.local
|
|
167
|
+
const published = documentStates[publishedId]?.local
|
|
164
168
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
})
|
|
169
|
+
const document = draft ?? published
|
|
170
|
+
if (document === undefined) return undefined
|
|
171
|
+
if (path) return jsonMatch(document, path).at(0)?.value
|
|
172
|
+
return document
|
|
173
|
+
},
|
|
174
|
+
onSubscribe: (context, doc: string | DocumentHandle) =>
|
|
175
|
+
manageSubscriberIds(context, typeof doc === 'string' ? doc : doc.documentId),
|
|
176
|
+
}),
|
|
177
|
+
)
|
|
173
178
|
|
|
174
179
|
/** @beta */
|
|
175
180
|
export function resolveDocument<TDocument extends SanityDocument>(
|
|
176
|
-
instance: SanityInstance
|
|
181
|
+
instance: SanityInstance,
|
|
177
182
|
doc: string | DocumentHandle<TDocument>,
|
|
178
183
|
): Promise<TDocument | null>
|
|
179
184
|
/** @beta */
|
|
180
185
|
export function resolveDocument(
|
|
181
|
-
instance: SanityInstance
|
|
186
|
+
instance: SanityInstance,
|
|
182
187
|
doc: string | DocumentHandle,
|
|
183
188
|
): Promise<SanityDocument | null>
|
|
184
189
|
/** @beta */
|
|
@@ -187,228 +192,233 @@ export function resolveDocument(
|
|
|
187
192
|
): Promise<SanityDocument | null> {
|
|
188
193
|
return _resolveDocument(...args)
|
|
189
194
|
}
|
|
190
|
-
const _resolveDocument =
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
const _resolveDocument = bindActionByDataset(
|
|
196
|
+
documentStore,
|
|
197
|
+
({instance}, doc: string | DocumentHandle) => {
|
|
198
|
+
const documentId = typeof doc === 'string' ? doc : doc.documentId
|
|
193
199
|
return firstValueFrom(
|
|
194
|
-
getDocumentState(
|
|
200
|
+
getDocumentState(instance, documentId).observable.pipe(filter((i) => i !== undefined)),
|
|
195
201
|
)
|
|
196
|
-
}
|
|
197
|
-
|
|
202
|
+
},
|
|
203
|
+
)
|
|
198
204
|
|
|
199
205
|
/** @beta */
|
|
200
|
-
export const getDocumentSyncStatus =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
export const getDocumentSyncStatus = bindActionByDataset(
|
|
207
|
+
documentStore,
|
|
208
|
+
createStateSourceAction({
|
|
209
|
+
selector: (
|
|
210
|
+
{state: {error, documentStates: documents, outgoing, applied, queued}},
|
|
211
|
+
doc: DocumentHandle,
|
|
212
|
+
) => {
|
|
213
|
+
const documentId = typeof doc === 'string' ? doc : doc.documentId
|
|
214
|
+
if (error) throw error
|
|
215
|
+
const draftId = getDraftId(documentId)
|
|
216
|
+
const publishedId = getPublishedId(documentId)
|
|
209
217
|
|
|
210
|
-
|
|
211
|
-
|
|
218
|
+
const draft = documents[draftId]
|
|
219
|
+
const published = documents[publishedId]
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
})
|
|
221
|
+
if (draft === undefined || published === undefined) return undefined
|
|
222
|
+
return !queued.length && !applied.length && !outgoing
|
|
223
|
+
},
|
|
224
|
+
onSubscribe: (context, doc: DocumentHandle) => manageSubscriberIds(context, doc.documentId),
|
|
225
|
+
}),
|
|
226
|
+
)
|
|
218
227
|
|
|
219
228
|
/** @beta */
|
|
220
|
-
export const getPermissionsState =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
export const getPermissionsState = bindActionByDataset(
|
|
230
|
+
documentStore,
|
|
231
|
+
createStateSourceAction({
|
|
232
|
+
selector: calculatePermissions,
|
|
233
|
+
onSubscribe: (context, actions) =>
|
|
234
|
+
manageSubscriberIds(context, getDocumentIdsFromActions(actions)),
|
|
235
|
+
}) as StoreAction<
|
|
236
|
+
DocumentStoreState,
|
|
237
|
+
[DocumentAction | DocumentAction[]],
|
|
238
|
+
StateSource<ReturnType<typeof calculatePermissions>>
|
|
239
|
+
>,
|
|
240
|
+
)
|
|
224
241
|
|
|
225
242
|
/** @beta */
|
|
226
|
-
export const resolvePermissions =
|
|
227
|
-
|
|
243
|
+
export const resolvePermissions = bindActionByDataset(
|
|
244
|
+
documentStore,
|
|
245
|
+
({instance}, actions: DocumentAction | DocumentAction[]) => {
|
|
228
246
|
return firstValueFrom(
|
|
229
|
-
getPermissionsState(
|
|
247
|
+
getPermissionsState(instance, actions).observable.pipe(filter((i) => i !== undefined)),
|
|
230
248
|
)
|
|
231
|
-
}
|
|
232
|
-
|
|
249
|
+
},
|
|
250
|
+
)
|
|
233
251
|
|
|
234
252
|
/** @beta */
|
|
235
|
-
export const subscribeDocumentEvents =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
253
|
+
export const subscribeDocumentEvents = bindActionByDataset(
|
|
254
|
+
documentStore,
|
|
255
|
+
({state}, eventHandler: (e: DocumentEvent) => void) => {
|
|
256
|
+
const {events} = state.get()
|
|
239
257
|
const subscription = events.subscribe(eventHandler)
|
|
240
258
|
return () => subscription.unsubscribe()
|
|
241
|
-
}
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
const subscribeToQueuedAndApplyNextTransaction = createInternalAction(
|
|
245
|
-
({state}: ActionContext<DocumentStoreState>) => {
|
|
246
|
-
const {events} = state.get()
|
|
247
|
-
|
|
248
|
-
return function () {
|
|
249
|
-
return state.observable
|
|
250
|
-
.pipe(
|
|
251
|
-
map(applyFirstQueuedTransaction),
|
|
252
|
-
distinctUntilChanged(),
|
|
253
|
-
tap((next) => state.set('applyFirstQueuedTransaction', next)),
|
|
254
|
-
catchError((error, caught) => {
|
|
255
|
-
if (error instanceof ActionError) {
|
|
256
|
-
state.set('removeQueuedTransaction', (prev) =>
|
|
257
|
-
removeQueuedTransaction(prev, error.transactionId),
|
|
258
|
-
)
|
|
259
|
-
events.next({
|
|
260
|
-
type: 'error',
|
|
261
|
-
message: error.message,
|
|
262
|
-
documentId: error.documentId,
|
|
263
|
-
transactionId: error.transactionId,
|
|
264
|
-
error,
|
|
265
|
-
})
|
|
266
|
-
return caught
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
throw error
|
|
270
|
-
}),
|
|
271
|
-
)
|
|
272
|
-
.subscribe({error: (error) => state.set('setError', {error})})
|
|
273
|
-
}
|
|
274
259
|
},
|
|
275
260
|
)
|
|
276
261
|
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
distinctUntilChanged(),
|
|
299
|
-
withLatestFrom(getClientState(instance, {apiVersion: API_VERSION}).observable),
|
|
300
|
-
concatMap(([outgoing, client]) => {
|
|
301
|
-
if (!outgoing) return EMPTY
|
|
302
|
-
return client.observable
|
|
303
|
-
.action(outgoing.outgoingActions as Action[], {
|
|
304
|
-
transactionId: outgoing.transactionId,
|
|
305
|
-
skipCrossDatasetReferenceValidation: true,
|
|
306
|
-
})
|
|
307
|
-
.pipe(
|
|
308
|
-
catchError((error) => {
|
|
309
|
-
state.set('revertOutgoingTransaction', revertOutgoingTransaction)
|
|
310
|
-
events.next({type: 'reverted', message: error.message, outgoing, error})
|
|
311
|
-
return EMPTY
|
|
312
|
-
}),
|
|
313
|
-
map((result) => ({result, outgoing})),
|
|
314
|
-
)
|
|
315
|
-
}),
|
|
316
|
-
tap(({outgoing, result}) => {
|
|
317
|
-
state.set('cleanupOutgoingTransaction', cleanupOutgoingTransaction)
|
|
318
|
-
for (const e of getDocumentEvents(outgoing)) events.next(e)
|
|
319
|
-
events.next({type: 'accepted', outgoing, result})
|
|
320
|
-
}),
|
|
321
|
-
)
|
|
322
|
-
.subscribe({error: (error) => state.set('setError', {error})})
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
)
|
|
262
|
+
const subscribeToQueuedAndApplyNextTransaction = ({state}: StoreContext<DocumentStoreState>) => {
|
|
263
|
+
const {events} = state.get()
|
|
264
|
+
return state.observable
|
|
265
|
+
.pipe(
|
|
266
|
+
map(applyFirstQueuedTransaction),
|
|
267
|
+
distinctUntilChanged(),
|
|
268
|
+
tap((next) => state.set('applyFirstQueuedTransaction', next)),
|
|
269
|
+
catchError((error, caught) => {
|
|
270
|
+
if (error instanceof ActionError) {
|
|
271
|
+
state.set('removeQueuedTransaction', (prev) =>
|
|
272
|
+
removeQueuedTransaction(prev, error.transactionId),
|
|
273
|
+
)
|
|
274
|
+
events.next({
|
|
275
|
+
type: 'error',
|
|
276
|
+
message: error.message,
|
|
277
|
+
documentId: error.documentId,
|
|
278
|
+
transactionId: error.transactionId,
|
|
279
|
+
error,
|
|
280
|
+
})
|
|
281
|
+
return caught
|
|
282
|
+
}
|
|
326
283
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
284
|
+
throw error
|
|
285
|
+
}),
|
|
286
|
+
)
|
|
287
|
+
.subscribe({error: (error) => state.set('setError', {error})})
|
|
288
|
+
}
|
|
330
289
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
distinctUntilChanged((curr, next) => {
|
|
337
|
-
if (curr.length !== next.length) return false
|
|
338
|
-
const currSet = new Set(curr)
|
|
339
|
-
return next.every((i) => currSet.has(i))
|
|
340
|
-
}),
|
|
341
|
-
startWith(new Set<string>()),
|
|
342
|
-
pairwise(),
|
|
343
|
-
switchMap((pair) => {
|
|
344
|
-
const [curr, next] = pair.map((ids) => new Set(ids))
|
|
345
|
-
const added = Array.from(next).filter((i) => !curr.has(i))
|
|
346
|
-
const removed = Array.from(curr).filter((i) => !next.has(i))
|
|
290
|
+
const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
291
|
+
state,
|
|
292
|
+
instance,
|
|
293
|
+
}: StoreContext<DocumentStoreState>) => {
|
|
294
|
+
const {events} = state.get()
|
|
347
295
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
296
|
+
return state.observable
|
|
297
|
+
.pipe(
|
|
298
|
+
throttle(
|
|
299
|
+
(s) =>
|
|
300
|
+
// if there is no outgoing transaction, we can throttle by the
|
|
301
|
+
// initial outgoing throttle time…
|
|
302
|
+
!s.outgoing
|
|
303
|
+
? timer(INITIAL_OUTGOING_THROTTLE_TIME)
|
|
304
|
+
: // …otherwise, wait until the outgoing has been cleared
|
|
305
|
+
state.observable.pipe(first(({outgoing}) => !outgoing)),
|
|
306
|
+
{leading: false, trailing: true},
|
|
307
|
+
),
|
|
308
|
+
map(transitionAppliedTransactionsToOutgoing),
|
|
309
|
+
distinctUntilChanged((a, b) => a.outgoing?.transactionId === b.outgoing?.transactionId),
|
|
310
|
+
tap((next) => state.set('transitionAppliedTransactionsToOutgoing', next)),
|
|
311
|
+
map((s) => s.outgoing),
|
|
312
|
+
distinctUntilChanged(),
|
|
313
|
+
withLatestFrom(getClientState(instance, {apiVersion: API_VERSION}).observable),
|
|
314
|
+
concatMap(([outgoing, client]) => {
|
|
315
|
+
if (!outgoing) return EMPTY
|
|
316
|
+
return client.observable
|
|
317
|
+
.action(outgoing.outgoingActions as Action[], {
|
|
318
|
+
transactionId: outgoing.transactionId,
|
|
319
|
+
skipCrossDatasetReferenceValidation: true,
|
|
320
|
+
})
|
|
321
|
+
.pipe(
|
|
322
|
+
catchError((error) => {
|
|
323
|
+
state.set('revertOutgoingTransaction', revertOutgoingTransaction)
|
|
324
|
+
events.next({type: 'reverted', message: error.message, outgoing, error})
|
|
325
|
+
return EMPTY
|
|
326
|
+
}),
|
|
327
|
+
map((result) => ({result, outgoing})),
|
|
328
|
+
)
|
|
329
|
+
}),
|
|
330
|
+
tap(({outgoing, result}) => {
|
|
331
|
+
state.set('cleanupOutgoingTransaction', cleanupOutgoingTransaction)
|
|
332
|
+
for (const e of getDocumentEvents(outgoing)) events.next(e)
|
|
333
|
+
events.next({type: 'accepted', outgoing, result})
|
|
334
|
+
}),
|
|
335
|
+
)
|
|
336
|
+
.subscribe({error: (error) => state.set('setError', {error})})
|
|
337
|
+
}
|
|
359
338
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
339
|
+
const subscribeToSubscriptionsAndListenToDocuments = (
|
|
340
|
+
context: StoreContext<DocumentStoreState>,
|
|
341
|
+
) => {
|
|
342
|
+
const {state} = context
|
|
343
|
+
const {events} = state.get()
|
|
365
344
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
applyRemoteDocument(prev, remote, events),
|
|
382
|
-
),
|
|
383
|
-
),
|
|
384
|
-
)
|
|
385
|
-
}),
|
|
386
|
-
),
|
|
387
|
-
),
|
|
388
|
-
)
|
|
389
|
-
.subscribe({error: (error) => state.set('setError', {error})})
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
)
|
|
345
|
+
return state.observable
|
|
346
|
+
.pipe(
|
|
347
|
+
filter((s) => !!s.grants),
|
|
348
|
+
map((s) => Object.keys(s.documentStates)),
|
|
349
|
+
distinctUntilChanged((curr, next) => {
|
|
350
|
+
if (curr.length !== next.length) return false
|
|
351
|
+
const currSet = new Set(curr)
|
|
352
|
+
return next.every((i) => currSet.has(i))
|
|
353
|
+
}),
|
|
354
|
+
startWith(new Set<string>()),
|
|
355
|
+
pairwise(),
|
|
356
|
+
switchMap((pair) => {
|
|
357
|
+
const [curr, next] = pair.map((ids) => new Set(ids))
|
|
358
|
+
const added = Array.from(next).filter((i) => !curr.has(i))
|
|
359
|
+
const removed = Array.from(curr).filter((i) => !next.has(i))
|
|
393
360
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
361
|
+
// NOTE: the order of which these go out is somewhat important
|
|
362
|
+
// because that determines the order `applyRemoteDocument` is called
|
|
363
|
+
// which in turn determines which document version get populated
|
|
364
|
+
// first. because we prefer drafts, it's better to have those go out
|
|
365
|
+
// first so that the published document doesn't flash for a frame
|
|
366
|
+
const changes = [
|
|
367
|
+
...added.map((id) => ({id, add: true})),
|
|
368
|
+
...removed.map((id) => ({id, add: false})),
|
|
369
|
+
].sort((a, b) => {
|
|
370
|
+
const aIsDraft = a.id === getDraftId(a.id)
|
|
371
|
+
const bIsDraft = b.id === getDraftId(b.id)
|
|
397
372
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
client.observable.request<DatasetAcl>({
|
|
403
|
-
uri: `/projects/${projectId}/datasets/${dataset}/acl`,
|
|
404
|
-
tag: 'acl.get',
|
|
405
|
-
}),
|
|
406
|
-
),
|
|
407
|
-
tap((datasetAcl) => state.set('setGrants', {grants: createGrantsLookup(datasetAcl)})),
|
|
408
|
-
)
|
|
409
|
-
.subscribe({
|
|
410
|
-
error: (error) => state.set('setError', {error}),
|
|
373
|
+
if (aIsDraft && bIsDraft) return a.id.localeCompare(b.id, 'en-US')
|
|
374
|
+
if (aIsDraft) return -1
|
|
375
|
+
if (bIsDraft) return 1
|
|
376
|
+
return a.id.localeCompare(b.id, 'en-US')
|
|
411
377
|
})
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
)
|
|
378
|
+
|
|
379
|
+
return of<{id: string; add: boolean}[]>(...changes)
|
|
380
|
+
}),
|
|
381
|
+
groupBy((i) => i.id),
|
|
382
|
+
mergeMap((group) =>
|
|
383
|
+
group.pipe(
|
|
384
|
+
switchMap((e) => {
|
|
385
|
+
if (!e.add) return EMPTY
|
|
386
|
+
return listen(context, e.id).pipe(
|
|
387
|
+
catchError((error) => {
|
|
388
|
+
// retry on `OutOfSyncError`
|
|
389
|
+
if (error instanceof OutOfSyncError) listen(context, e.id)
|
|
390
|
+
throw error
|
|
391
|
+
}),
|
|
392
|
+
tap((remote) =>
|
|
393
|
+
state.set('applyRemoteDocument', (prev) =>
|
|
394
|
+
applyRemoteDocument(prev, remote, events),
|
|
395
|
+
),
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
}),
|
|
399
|
+
),
|
|
400
|
+
),
|
|
401
|
+
)
|
|
402
|
+
.subscribe({error: (error) => state.set('setError', {error})})
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const subscribeToClientAndFetchDatasetAcl = ({
|
|
406
|
+
instance,
|
|
407
|
+
state,
|
|
408
|
+
}: StoreContext<DocumentStoreState>) => {
|
|
409
|
+
const {projectId, dataset} = instance.config
|
|
410
|
+
return getClientState(instance, {apiVersion: API_VERSION})
|
|
411
|
+
.observable.pipe(
|
|
412
|
+
switchMap((client) =>
|
|
413
|
+
client.observable.request<DatasetAcl>({
|
|
414
|
+
uri: `/projects/${projectId}/datasets/${dataset}/acl`,
|
|
415
|
+
tag: 'acl.get',
|
|
416
|
+
withCredentials: true,
|
|
417
|
+
}),
|
|
418
|
+
),
|
|
419
|
+
tap((datasetAcl) => state.set('setGrants', {grants: createGrantsLookup(datasetAcl)})),
|
|
420
|
+
)
|
|
421
|
+
.subscribe({
|
|
422
|
+
error: (error) => state.set('setError', {error}),
|
|
423
|
+
})
|
|
424
|
+
}
|