@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.
Files changed (127) hide show
  1. package/dist/index.d.ts +428 -325
  2. package/dist/index.js +1618 -1553
  3. package/dist/index.js.map +1 -1
  4. package/package.json +6 -7
  5. package/src/_exports/index.ts +31 -30
  6. package/src/auth/authStore.test.ts +149 -104
  7. package/src/auth/authStore.ts +51 -100
  8. package/src/auth/handleAuthCallback.test.ts +67 -34
  9. package/src/auth/handleAuthCallback.ts +8 -7
  10. package/src/auth/logout.test.ts +61 -29
  11. package/src/auth/logout.ts +26 -28
  12. package/src/auth/refreshStampedToken.test.ts +9 -9
  13. package/src/auth/refreshStampedToken.ts +62 -56
  14. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
  15. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
  16. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
  17. package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
  18. package/src/client/clientStore.test.ts +131 -67
  19. package/src/client/clientStore.ts +117 -116
  20. package/src/comlink/controller/actions/destroyController.test.ts +38 -13
  21. package/src/comlink/controller/actions/destroyController.ts +11 -15
  22. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
  23. package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
  24. package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
  25. package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
  26. package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
  27. package/src/comlink/controller/actions/releaseChannel.ts +22 -21
  28. package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
  29. package/src/comlink/controller/comlinkControllerStore.ts +44 -5
  30. package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
  31. package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
  32. package/src/comlink/node/actions/releaseNode.test.ts +75 -55
  33. package/src/comlink/node/actions/releaseNode.ts +19 -21
  34. package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
  35. package/src/comlink/node/comlinkNodeStore.ts +22 -5
  36. package/src/config/authConfig.ts +79 -0
  37. package/src/config/sanityConfig.ts +48 -0
  38. package/src/datasets/datasets.test.ts +2 -2
  39. package/src/datasets/datasets.ts +18 -5
  40. package/src/document/actions.test.ts +22 -10
  41. package/src/document/actions.ts +44 -56
  42. package/src/document/applyDocumentActions.test.ts +96 -36
  43. package/src/document/applyDocumentActions.ts +140 -99
  44. package/src/document/documentStore.test.ts +103 -155
  45. package/src/document/documentStore.ts +247 -237
  46. package/src/document/listen.ts +56 -55
  47. package/src/document/patchOperations.ts +0 -43
  48. package/src/document/permissions.test.ts +25 -12
  49. package/src/document/permissions.ts +11 -4
  50. package/src/document/processActions.test.ts +41 -8
  51. package/src/document/reducers.test.ts +87 -16
  52. package/src/document/reducers.ts +2 -2
  53. package/src/document/sharedListener.test.ts +34 -16
  54. package/src/document/sharedListener.ts +33 -11
  55. package/src/preview/getPreviewState.test.ts +40 -39
  56. package/src/preview/getPreviewState.ts +68 -56
  57. package/src/preview/previewConstants.ts +43 -0
  58. package/src/preview/previewQuery.test.ts +1 -1
  59. package/src/preview/previewQuery.ts +4 -5
  60. package/src/preview/previewStore.test.ts +13 -58
  61. package/src/preview/previewStore.ts +7 -21
  62. package/src/preview/resolvePreview.test.ts +33 -104
  63. package/src/preview/resolvePreview.ts +11 -21
  64. package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
  65. package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
  66. package/src/preview/util.ts +1 -0
  67. package/src/project/project.test.ts +3 -3
  68. package/src/project/project.ts +28 -5
  69. package/src/projection/getProjectionState.test.ts +69 -49
  70. package/src/projection/getProjectionState.ts +42 -50
  71. package/src/projection/projectionQuery.ts +1 -1
  72. package/src/projection/projectionStore.test.ts +13 -51
  73. package/src/projection/projectionStore.ts +6 -18
  74. package/src/projection/resolveProjection.test.ts +32 -127
  75. package/src/projection/resolveProjection.ts +15 -28
  76. package/src/projection/subscribeToStateAndFetchBatches.test.ts +105 -90
  77. package/src/projection/subscribeToStateAndFetchBatches.ts +94 -81
  78. package/src/projection/util.ts +2 -0
  79. package/src/projects/projects.test.ts +13 -4
  80. package/src/projects/projects.ts +6 -1
  81. package/src/query/queryStore.test.ts +10 -47
  82. package/src/query/queryStore.ts +151 -133
  83. package/src/query/queryStoreConstants.ts +2 -0
  84. package/src/store/createActionBinder.test.ts +153 -0
  85. package/src/store/createActionBinder.ts +176 -0
  86. package/src/store/createSanityInstance.test.ts +84 -0
  87. package/src/store/createSanityInstance.ts +124 -0
  88. package/src/store/createStateSourceAction.test.ts +196 -0
  89. package/src/store/createStateSourceAction.ts +260 -0
  90. package/src/store/createStoreInstance.test.ts +81 -0
  91. package/src/store/createStoreInstance.ts +80 -0
  92. package/src/store/createStoreState.test.ts +85 -0
  93. package/src/store/createStoreState.ts +92 -0
  94. package/src/store/defineStore.test.ts +18 -0
  95. package/src/store/defineStore.ts +81 -0
  96. package/src/users/reducers.test.ts +318 -0
  97. package/src/users/reducers.ts +88 -0
  98. package/src/users/types.ts +46 -4
  99. package/src/users/usersConstants.ts +4 -0
  100. package/src/users/usersStore.test.ts +350 -223
  101. package/src/users/usersStore.ts +285 -149
  102. package/src/utils/createFetcherStore.test.ts +6 -7
  103. package/src/utils/createFetcherStore.ts +150 -153
  104. package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
  105. package/src/auth/fetchLoginUrls.test.ts +0 -163
  106. package/src/auth/fetchLoginUrls.ts +0 -74
  107. package/src/common/createLiveEventSubscriber.test.ts +0 -121
  108. package/src/common/createLiveEventSubscriber.ts +0 -55
  109. package/src/common/types.ts +0 -4
  110. package/src/instance/identity.test.ts +0 -46
  111. package/src/instance/identity.ts +0 -29
  112. package/src/instance/sanityInstance.test.ts +0 -77
  113. package/src/instance/sanityInstance.ts +0 -57
  114. package/src/instance/types.ts +0 -37
  115. package/src/preview/getPreviewProjection.ts +0 -45
  116. package/src/resources/README.md +0 -370
  117. package/src/resources/createAction.test.ts +0 -101
  118. package/src/resources/createAction.ts +0 -44
  119. package/src/resources/createResource.test.ts +0 -112
  120. package/src/resources/createResource.ts +0 -102
  121. package/src/resources/createStateSourceAction.test.ts +0 -114
  122. package/src/resources/createStateSourceAction.ts +0 -83
  123. package/src/resources/createStore.test.ts +0 -67
  124. package/src/resources/createStore.ts +0 -46
  125. package/src/store/createStore.test.ts +0 -108
  126. package/src/store/createStore.ts +0 -106
  127. /package/src/{common/util.ts → utils/hashString.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import {type Action, type ListenEvent} from '@sanity/client'
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 SanityInstance} from '../instance/types'
30
- import {type ActionContext, createAction, createInternalAction} from '../resources/createAction'
31
- import {createResource} from '../resources/createResource'
32
- import {createStateSourceAction, type StateSource} from '../resources/createStateSourceAction'
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 DocumentHandle, type JsonMatch, jsonMatch, type JsonMatchPath} from './patchOperations'
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: Observable<ListenEvent<SanityDocument>>
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 = createResource<DocumentStoreState>({
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 queuedTransactionSubscription = subscribeToQueuedAndApplyNextTransaction(this)
117
- const subscriptionsSubscription = subscribeToSubscriptionsAndListenToDocuments(this)
118
- const appliedSubscription = subscribeToAppliedAndSubmitNextTransaction(this)
119
- const clientSubscription = subscribeToClientAndFetchDatasetAcl(this)
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
- queuedTransactionSubscription.unsubscribe()
123
- subscriptionsSubscription.unsubscribe()
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 | ActionContext<DocumentStoreState>,
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 | ActionContext<DocumentStoreState>,
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 | ActionContext<DocumentStoreState>,
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 = createStateSourceAction(documentStore, {
157
- selector: ({error, documentStates}, doc: string | DocumentHandle, path?: string) => {
158
- const documentId = typeof doc === 'string' ? doc : doc._id
159
- if (error) throw error
160
- const draftId = getDraftId(documentId)
161
- const publishedId = getPublishedId(documentId)
162
- const draft = documentStates[draftId]?.local
163
- const published = documentStates[publishedId]?.local
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
- const document = draft ?? published
166
- if (document === undefined) return undefined
167
- if (path) return jsonMatch(document, path).at(0)?.value
168
- return document
169
- },
170
- onSubscribe: ({state}, doc: string | DocumentHandle) =>
171
- manageSubscriberIds(state, typeof doc === 'string' ? doc : doc._id),
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 | ActionContext<DocumentStoreState>,
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 | ActionContext<DocumentStoreState>,
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 = createAction(documentStore, () => {
191
- return function (doc: string | DocumentHandle) {
192
- const documentId = typeof doc === 'string' ? doc : doc._id
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(this, documentId).observable.pipe(filter((i) => i !== undefined)),
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 = createStateSourceAction(documentStore, {
201
- selector: (
202
- {error, documentStates: documents, outgoing, applied, queued},
203
- doc: DocumentHandle,
204
- ) => {
205
- const documentId = doc._id
206
- if (error) throw error
207
- const draftId = getDraftId(documentId)
208
- const publishedId = getPublishedId(documentId)
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
- const draft = documents[draftId]
211
- const published = documents[publishedId]
218
+ const draft = documents[draftId]
219
+ const published = documents[publishedId]
212
220
 
213
- if (draft === undefined || published === undefined) return undefined
214
- return !queued.length && !applied.length && !outgoing
215
- },
216
- onSubscribe: ({state}, doc: DocumentHandle) => manageSubscriberIds(state, doc._id),
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 = createStateSourceAction(documentStore, {
221
- selector: calculatePermissions,
222
- onSubscribe: ({state}, actions) => manageSubscriberIds(state, getDocumentIdsFromActions(actions)),
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 = createAction(documentStore, () => {
227
- return function (actions: DocumentAction | DocumentAction[]) {
243
+ export const resolvePermissions = bindActionByDataset(
244
+ documentStore,
245
+ ({instance}, actions: DocumentAction | DocumentAction[]) => {
228
246
  return firstValueFrom(
229
- getPermissionsState(this, actions).observable.pipe(filter((i) => i !== undefined)),
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 = createAction(documentStore, ({state}) => {
236
- const {events} = state.get()
237
-
238
- return function (eventHandler: (e: DocumentEvent) => void): () => void {
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 subscribeToAppliedAndSubmitNextTransaction = createInternalAction(
278
- ({state, instance}: ActionContext<DocumentStoreState>) => {
279
- const {events} = state.get()
280
-
281
- return function () {
282
- return state.observable
283
- .pipe(
284
- throttle(
285
- (s) =>
286
- // if there is no outgoing transaction, we can throttle by the
287
- // initial outgoing throttle time…
288
- !s.outgoing
289
- ? timer(INITIAL_OUTGOING_THROTTLE_TIME)
290
- : // …otherwise, wait until the outgoing has been cleared
291
- state.observable.pipe(first(({outgoing}) => !outgoing)),
292
- {leading: false, trailing: true},
293
- ),
294
- map(transitionAppliedTransactionsToOutgoing),
295
- distinctUntilChanged((a, b) => a.outgoing?.transactionId === b.outgoing?.transactionId),
296
- tap((next) => state.set('transitionAppliedTransactionsToOutgoing', next)),
297
- map((s) => s.outgoing),
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
- const subscribeToSubscriptionsAndListenToDocuments = createInternalAction(
328
- ({state}: ActionContext<DocumentStoreState>) => {
329
- const {events} = state.get()
284
+ throw error
285
+ }),
286
+ )
287
+ .subscribe({error: (error) => state.set('setError', {error})})
288
+ }
330
289
 
331
- return function () {
332
- return state.observable
333
- .pipe(
334
- filter((s) => !!s.grants),
335
- map((s) => Object.keys(s.documentStates)),
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
- // NOTE: the order of which these go out is somewhat important
349
- // because that determines the order `applyRemoteDocument` is called
350
- // which in turn determines which document version get populated
351
- // first. because we prefer drafts, it's better to have those go out
352
- // first so that the published document doesn't flash for a frame
353
- const changes = [
354
- ...added.map((id) => ({id, add: true})),
355
- ...removed.map((id) => ({id, add: false})),
356
- ].sort((a, b) => {
357
- const aIsDraft = a.id === getDraftId(a.id)
358
- const bIsDraft = b.id === getDraftId(b.id)
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
- if (aIsDraft && bIsDraft) return a.id.localeCompare(b.id, 'en-US')
361
- if (aIsDraft) return -1
362
- if (bIsDraft) return 1
363
- return a.id.localeCompare(b.id, 'en-US')
364
- })
339
+ const subscribeToSubscriptionsAndListenToDocuments = (
340
+ context: StoreContext<DocumentStoreState>,
341
+ ) => {
342
+ const {state} = context
343
+ const {events} = state.get()
365
344
 
366
- return of<{id: string; add: boolean}[]>(...changes)
367
- }),
368
- groupBy((i) => i.id),
369
- mergeMap((group) =>
370
- group.pipe(
371
- switchMap((e) => {
372
- if (!e.add) return EMPTY
373
- return listen(this, e.id).pipe(
374
- catchError((error) => {
375
- // retry on `OutOfSyncError`
376
- if (error instanceof OutOfSyncError) listen(this, e.id)
377
- throw error
378
- }),
379
- tap((remote) =>
380
- state.set('applyRemoteDocument', (prev) =>
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
- const subscribeToClientAndFetchDatasetAcl = createInternalAction(
395
- ({instance, state}: ActionContext<DocumentStoreState>) => {
396
- const {projectId, dataset} = instance.identity
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
- return function () {
399
- return getClientState(instance, {apiVersion: API_VERSION})
400
- .observable.pipe(
401
- switchMap((client) =>
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
+ }