@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.1
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/_chunks-dts/createGroqSearchFilter.d.ts +1147 -0
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -0
- package/dist/_chunks-es/_internal.js +134 -0
- package/dist/_chunks-es/_internal.js.map +1 -0
- package/dist/_chunks-es/createGroqSearchFilter.js +1864 -0
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
- package/dist/_chunks-es/telemetryManager.js +93 -0
- package/dist/_chunks-es/telemetryManager.js.map +1 -0
- package/dist/_exports/_internal.d.ts +95 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +24 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/index.d.ts +1079 -1076
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1967 -2302
- package/dist/index.js.map +1 -1
- package/package.json +48 -47
- package/src/_exports/_internal.ts +26 -0
- package/src/_exports/index.ts +120 -11
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- package/src/agent/agentActions.test.ts +16 -60
- package/src/agent/agentActions.ts +25 -38
- package/src/applications/applications.test-d.ts +83 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +364 -0
- package/src/applications/userApplications.test.ts +126 -0
- package/src/applications/userApplications.ts +261 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authMode.test.ts +25 -0
- package/src/auth/authMode.ts +6 -3
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +59 -26
- package/src/auth/getOrganizationVerificationState.test.ts +18 -17
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/auth/handleAuthCallback.test.ts +35 -1
- package/src/auth/handleAuthCallback.ts +25 -6
- package/src/auth/logout.test.ts +75 -2
- package/src/auth/logout.ts +23 -4
- package/src/auth/refreshStampedToken.test.ts +219 -469
- package/src/auth/refreshStampedToken.ts +79 -195
- package/src/auth/studioAuth.ts +1 -1
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +146 -13
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +37 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +2 -0
- package/src/client/clientStore.test.ts +62 -37
- package/src/client/clientStore.ts +45 -75
- package/src/client/liveEvents.test.ts +151 -0
- package/src/client/liveEvents.ts +107 -0
- package/src/comlink/controller/actions/destroyController.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateController.test.ts +4 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -1
- package/src/comlink/node/actions/getOrCreateNode.test.ts +9 -3
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +7 -4
- package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
- package/src/comlink/node/getNodeState.test.ts +1 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +109 -79
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +10 -4
- package/src/document/actions.test.ts +115 -33
- package/src/document/actions.ts +212 -15
- package/src/document/applyDocumentActions.test.ts +38 -12
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +7 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +673 -278
- package/src/document/documentStore.ts +162 -71
- package/src/document/events.test.ts +57 -2
- package/src/document/events.ts +82 -26
- package/src/document/listen.test.ts +56 -0
- package/src/document/listen.ts +74 -21
- package/src/document/listenerEventOperators.test.ts +311 -0
- package/src/document/listenerEventOperators.ts +217 -0
- package/src/document/patchOperations.test.ts +49 -2
- package/src/document/patchOperations.ts +50 -0
- package/src/document/permissions.test.ts +105 -60
- package/src/document/permissions.ts +44 -19
- package/src/document/processActions/create.ts +138 -0
- package/src/document/processActions/delete.ts +100 -0
- package/src/document/processActions/discard.ts +63 -0
- package/src/document/processActions/edit.ts +154 -0
- package/src/document/processActions/processActions.ts +218 -0
- package/src/document/processActions/publish.ts +120 -0
- package/src/document/processActions/releaseArchive.ts +77 -0
- package/src/document/processActions/releaseCreate.ts +59 -0
- package/src/document/processActions/releaseDelete.ts +65 -0
- package/src/document/processActions/releaseEdit.ts +37 -0
- package/src/document/processActions/releasePublish.ts +45 -0
- package/src/document/processActions/releaseSchedule.ts +87 -0
- package/src/document/processActions/releaseUtil.ts +31 -0
- package/src/document/processActions/shared.ts +308 -0
- package/src/document/processActions/unpublish.ts +85 -0
- package/src/document/processActions.test.ts +1129 -121
- package/src/document/reducers.test.ts +639 -64
- package/src/document/reducers.ts +187 -48
- package/src/document/resourceRules.test.ts +178 -0
- package/src/document/resourceRules.ts +117 -0
- package/src/document/sharedListener.test.ts +16 -14
- package/src/document/sharedListener.ts +24 -8
- package/src/document/util.ts +14 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +68 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +202 -0
- package/src/organization/organization.test-d.ts +36 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +138 -0
- package/src/organizations/organizations.test-d.ts +56 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +107 -0
- package/src/presence/bifurTransport.test.ts +46 -6
- package/src/presence/bifurTransport.ts +19 -2
- package/src/presence/presenceStore.test.ts +94 -23
- package/src/presence/presenceStore.ts +85 -29
- package/src/preview/getPreviewState.test.ts +137 -0
- package/src/preview/getPreviewState.ts +69 -0
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/resolvePreview.test.ts +64 -0
- package/src/preview/resolvePreview.ts +43 -0
- package/src/preview/types.ts +10 -0
- package/src/project/project.test-d.ts +37 -0
- package/src/project/project.test.ts +157 -12
- package/src/project/project.ts +138 -25
- package/src/projection/getProjectionState.test.ts +18 -20
- package/src/projection/getProjectionState.ts +23 -3
- package/src/projection/projectionQuery.ts +5 -5
- package/src/projection/projectionStore.test.ts +1 -1
- package/src/projection/resolveProjection.test.ts +1 -2
- package/src/projection/resolveProjection.ts +16 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +32 -23
- package/src/projection/types.ts +8 -0
- package/src/projects/projects.test-d.ts +32 -0
- package/src/projects/projects.test.ts +108 -42
- package/src/projects/projects.ts +63 -27
- package/src/query/queryStore.test.ts +259 -123
- package/src/query/queryStore.ts +78 -63
- package/src/query/reducers.test.ts +0 -42
- package/src/query/reducers.ts +2 -18
- package/src/releases/getPerspectiveState.test.ts +17 -23
- package/src/releases/getPerspectiveState.ts +8 -20
- package/src/releases/observeReleases.test.ts +127 -0
- package/src/releases/observeReleases.ts +102 -0
- package/src/releases/releasesStore.test.ts +100 -53
- package/src/releases/releasesStore.ts +69 -44
- package/src/releases/utils/sortReleases.test.ts +38 -2
- package/src/releases/utils/sortReleases.ts +15 -10
- package/src/store/createActionBinder.test.ts +87 -90
- package/src/store/createActionBinder.ts +79 -25
- package/src/store/createSanityInstance.test.ts +83 -13
- package/src/store/createSanityInstance.ts +77 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +39 -44
- package/src/store/createStoreInstance.test.ts +4 -2
- package/src/store/fetcherStore.test-d.ts +121 -0
- package/src/store/fetcherStore.test.ts +299 -0
- package/src/store/fetcherStore.ts +801 -0
- package/src/telemetry/environment.test.ts +119 -0
- package/src/telemetry/environment.ts +92 -0
- package/src/telemetry/events.ts +42 -0
- package/src/telemetry/initTelemetry.test.ts +432 -0
- package/src/telemetry/initTelemetry.ts +218 -0
- package/src/telemetry/telemetryManager.test.ts +327 -0
- package/src/telemetry/telemetryManager.ts +199 -0
- package/src/users/reducers.test.ts +6 -1
- package/src/users/reducers.ts +5 -6
- package/src/users/types.ts +4 -4
- package/src/users/usersStore.test.ts +16 -12
- package/src/users/usersStore.ts +5 -1
- package/src/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/createFetcherStore.test.ts +7 -5
- package/src/utils/createFetcherStore.ts +8 -5
- package/src/utils/getStagingApiHost.test.ts +21 -0
- package/src/utils/getStagingApiHost.ts +14 -0
- package/src/utils/ids.test.ts +1 -29
- package/src/utils/ids.ts +0 -10
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/logger.test.ts +12 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
- package/src/utils/setCleanupTimeout.ts +37 -0
- package/src/document/processActions.ts +0 -707
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {type Action} from '@sanity/client'
|
|
2
|
-
import {getPublishedId} from '@sanity/
|
|
1
|
+
import {type Action, type Mutation} from '@sanity/client'
|
|
2
|
+
import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
|
|
3
3
|
import {jsonMatch} from '@sanity/json-match'
|
|
4
|
-
import {type SanityDocument} from '
|
|
4
|
+
import {type SanityDocument} from 'groq'
|
|
5
5
|
import {type ExprNode} from 'groq-js'
|
|
6
6
|
import {
|
|
7
7
|
catchError,
|
|
@@ -17,15 +17,18 @@ import {
|
|
|
17
17
|
Observable,
|
|
18
18
|
of,
|
|
19
19
|
pairwise,
|
|
20
|
+
retry,
|
|
20
21
|
startWith,
|
|
21
22
|
Subject,
|
|
22
23
|
switchMap,
|
|
23
24
|
tap,
|
|
24
25
|
throttle,
|
|
26
|
+
throwError,
|
|
25
27
|
timer,
|
|
26
28
|
withLatestFrom,
|
|
27
29
|
} from 'rxjs'
|
|
28
30
|
|
|
31
|
+
import {getCurrentUserState} from '../auth/authStore'
|
|
29
32
|
import {type ClientOptions, getClientState} from '../client/clientStore'
|
|
30
33
|
import {
|
|
31
34
|
type DocumentHandle,
|
|
@@ -34,6 +37,7 @@ import {
|
|
|
34
37
|
isDatasetResource,
|
|
35
38
|
isMediaLibraryResource,
|
|
36
39
|
} from '../config/sanityConfig'
|
|
40
|
+
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
37
41
|
import {
|
|
38
42
|
bindActionByResource,
|
|
39
43
|
type BoundResourceKey,
|
|
@@ -42,11 +46,20 @@ import {
|
|
|
42
46
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
43
47
|
import {createStateSourceAction, type StateSource} from '../store/createStateSourceAction'
|
|
44
48
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
45
|
-
import {getDraftId} from '../utils/ids'
|
|
46
49
|
import {type DocumentAction} from './actions'
|
|
47
|
-
import {
|
|
48
|
-
|
|
50
|
+
import {
|
|
51
|
+
API_VERSION,
|
|
52
|
+
INITIAL_OUTGOING_THROTTLE_TIME,
|
|
53
|
+
OUT_OF_SYNC_RETRY_BASE_DELAY,
|
|
54
|
+
OUT_OF_SYNC_RETRY_MAX_DELAY,
|
|
55
|
+
} from './documentConstants'
|
|
56
|
+
import {
|
|
57
|
+
type DocumentEvent,
|
|
58
|
+
type DocumentTransactionSubmissionResult,
|
|
59
|
+
getDocumentEvents,
|
|
60
|
+
} from './events'
|
|
49
61
|
import {listen, OutOfSyncError} from './listen'
|
|
62
|
+
import {type JsonMatch} from './patchOperations'
|
|
50
63
|
import {
|
|
51
64
|
calculatePermissions,
|
|
52
65
|
createGrantsLookup,
|
|
@@ -54,13 +67,13 @@ import {
|
|
|
54
67
|
type DocumentPermissionsResult,
|
|
55
68
|
type Grant,
|
|
56
69
|
} from './permissions'
|
|
57
|
-
import {ActionError} from './processActions'
|
|
70
|
+
import {ActionError} from './processActions/processActions'
|
|
71
|
+
import {isReleaseAction} from './processActions/releaseUtil'
|
|
58
72
|
import {
|
|
59
73
|
type AppliedTransaction,
|
|
60
74
|
applyFirstQueuedTransaction,
|
|
61
75
|
applyRemoteDocument,
|
|
62
76
|
cleanupOutgoingTransaction,
|
|
63
|
-
getDocumentIdsFromActions,
|
|
64
77
|
manageSubscriberIds,
|
|
65
78
|
type OutgoingTransaction,
|
|
66
79
|
type QueuedTransaction,
|
|
@@ -77,6 +90,10 @@ export interface DocumentStoreState {
|
|
|
77
90
|
applied: AppliedTransaction[]
|
|
78
91
|
outgoing?: OutgoingTransaction
|
|
79
92
|
grants?: Record<Grant, ExprNode>
|
|
93
|
+
/**
|
|
94
|
+
* The current user's identity (their user ID).
|
|
95
|
+
*/
|
|
96
|
+
identity?: string
|
|
80
97
|
error?: unknown
|
|
81
98
|
sharedListener: SharedListener
|
|
82
99
|
fetchDocument: (documentId: string) => Observable<SanityDocument | null>
|
|
@@ -116,6 +133,14 @@ export interface DocumentState {
|
|
|
116
133
|
* local transactions on top of this new remote.
|
|
117
134
|
*/
|
|
118
135
|
unverifiedRevisions?: {[TTransactionId in string]?: UnverifiedDocumentRevision}
|
|
136
|
+
/**
|
|
137
|
+
* transaction IDs recently submitted by this client for this document,
|
|
138
|
+
* newest last. used to label `remote-patches` events with the correct
|
|
139
|
+
* `origin` even after the corresponding `unverifiedRevisions` entry has
|
|
140
|
+
* been pruned (e.g. by a sync event that raced the listener echo). capped
|
|
141
|
+
* to the most recent entries.
|
|
142
|
+
*/
|
|
143
|
+
recentOwnTransactionIds?: string[]
|
|
119
144
|
}
|
|
120
145
|
|
|
121
146
|
export const documentStore = defineStore<DocumentStoreState, BoundResourceKey>({
|
|
@@ -136,6 +161,7 @@ export const documentStore = defineStore<DocumentStoreState, BoundResourceKey>({
|
|
|
136
161
|
subscribeToSubscriptionsAndListenToDocuments(context),
|
|
137
162
|
subscribeToAppliedAndSubmitNextTransaction(context),
|
|
138
163
|
subscribeToClientAndFetchDatasetAcl(context),
|
|
164
|
+
subscribeToCurrentUserAndSetIdentity(context),
|
|
139
165
|
]
|
|
140
166
|
|
|
141
167
|
return () => {
|
|
@@ -158,6 +184,29 @@ export interface DocumentOptions<
|
|
|
158
184
|
path?: TPath
|
|
159
185
|
}
|
|
160
186
|
|
|
187
|
+
/** @beta */
|
|
188
|
+
export function getDocumentState<
|
|
189
|
+
TDocumentType extends string = string,
|
|
190
|
+
TDataset extends string = string,
|
|
191
|
+
TProjectId extends string = string,
|
|
192
|
+
>(
|
|
193
|
+
instance: SanityInstance,
|
|
194
|
+
options: DocumentOptions<undefined, TDocumentType, TDataset, TProjectId>,
|
|
195
|
+
): StateSource<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`> | undefined | null>
|
|
196
|
+
|
|
197
|
+
/** @beta */
|
|
198
|
+
export function getDocumentState<
|
|
199
|
+
TPath extends string = string,
|
|
200
|
+
TDocumentType extends string = string,
|
|
201
|
+
TDataset extends string = string,
|
|
202
|
+
TProjectId extends string = string,
|
|
203
|
+
>(
|
|
204
|
+
instance: SanityInstance,
|
|
205
|
+
options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
|
|
206
|
+
): StateSource<
|
|
207
|
+
JsonMatch<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>, TPath> | undefined
|
|
208
|
+
>
|
|
209
|
+
|
|
161
210
|
/** @beta */
|
|
162
211
|
export function getDocumentState<TData>(
|
|
163
212
|
instance: SanityInstance,
|
|
@@ -175,29 +224,28 @@ const _getDocumentState = bindActionByResource(
|
|
|
175
224
|
documentStore,
|
|
176
225
|
createStateSourceAction({
|
|
177
226
|
selector: ({state: {error, documentStates}}, options: DocumentOptions<string | undefined>) => {
|
|
178
|
-
const {documentId, path, liveEdit} = options
|
|
227
|
+
const {documentId: docId, path, liveEdit, perspective} = options
|
|
228
|
+
const documentId = DocumentId(docId)
|
|
179
229
|
if (error) throw error
|
|
230
|
+
let document: SanityDocument | null | undefined
|
|
180
231
|
|
|
181
232
|
if (liveEdit) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
233
|
+
document = documentStates[documentId]?.local
|
|
234
|
+
} else {
|
|
235
|
+
let version: SanityDocument | null | undefined
|
|
236
|
+
if (isReleasePerspective(perspective)) {
|
|
237
|
+
const versionId = getVersionId(documentId, perspective.releaseName)
|
|
238
|
+
version = documentStates[versionId]?.local
|
|
239
|
+
// early exit if we don't have the version document and we're in a release perspective
|
|
240
|
+
if (version === undefined) return undefined
|
|
241
|
+
}
|
|
242
|
+
const draft = documentStates[getDraftId(documentId)]?.local
|
|
243
|
+
const published = documentStates[getPublishedId(documentId)]?.local
|
|
244
|
+
// early exit if we don't have all the documents for draft/published logic
|
|
245
|
+
if (draft === undefined || published === undefined) return undefined
|
|
246
|
+
document = version ?? draft ?? published
|
|
190
247
|
}
|
|
191
248
|
|
|
192
|
-
// Standard draft/published logic
|
|
193
|
-
const draftId = getDraftId(documentId)
|
|
194
|
-
const publishedId = getPublishedId(documentId)
|
|
195
|
-
const draft = documentStates[draftId]?.local
|
|
196
|
-
const published = documentStates[publishedId]?.local
|
|
197
|
-
|
|
198
|
-
// wait for draft and published to be loaded before returning a value
|
|
199
|
-
if (draft === undefined || published === undefined) return undefined
|
|
200
|
-
const document = draft ?? published
|
|
201
249
|
if (!path) return document
|
|
202
250
|
const result = jsonMatch(document, path).next()
|
|
203
251
|
if (result.done) return undefined
|
|
@@ -205,20 +253,24 @@ const _getDocumentState = bindActionByResource(
|
|
|
205
253
|
return value
|
|
206
254
|
},
|
|
207
255
|
onSubscribe: (context, options: DocumentOptions<string | undefined>) =>
|
|
208
|
-
manageSubscriberIds(context, options
|
|
256
|
+
manageSubscriberIds(context, [options]),
|
|
209
257
|
}),
|
|
210
258
|
)
|
|
211
259
|
|
|
212
260
|
/** @beta */
|
|
213
|
-
export function resolveDocument<
|
|
261
|
+
export function resolveDocument<
|
|
262
|
+
TDocumentType extends string = string,
|
|
263
|
+
TDataset extends string = string,
|
|
264
|
+
TProjectId extends string = string,
|
|
265
|
+
>(
|
|
214
266
|
instance: SanityInstance,
|
|
215
|
-
docHandle: DocumentHandle,
|
|
216
|
-
): Promise<
|
|
267
|
+
docHandle: DocumentHandle<TDocumentType, TDataset, TProjectId>,
|
|
268
|
+
): Promise<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`> | null>
|
|
217
269
|
/** @beta */
|
|
218
|
-
export function resolveDocument(
|
|
270
|
+
export function resolveDocument<TData extends SanityDocument>(
|
|
219
271
|
instance: SanityInstance,
|
|
220
|
-
docHandle: DocumentHandle,
|
|
221
|
-
): Promise<
|
|
272
|
+
docHandle: DocumentHandle<string, string, string>,
|
|
273
|
+
): Promise<TData | null>
|
|
222
274
|
/** @beta */
|
|
223
275
|
export function resolveDocument(
|
|
224
276
|
...args: Parameters<typeof _resolveDocument>
|
|
@@ -245,32 +297,32 @@ export const getDocumentSyncStatus = bindActionByResource(
|
|
|
245
297
|
{state: {error, documentStates: documents, outgoing, applied, queued}},
|
|
246
298
|
doc: DocumentHandle,
|
|
247
299
|
) => {
|
|
248
|
-
const documentId = typeof doc === 'string' ? doc : doc.documentId
|
|
300
|
+
const documentId = DocumentId(typeof doc === 'string' ? doc : doc.documentId)
|
|
249
301
|
if (error) throw error
|
|
250
302
|
|
|
251
303
|
if (doc.liveEdit) {
|
|
252
304
|
// For liveEdit documents, only check the single document
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
305
|
+
if (documents[documentId] === undefined) return undefined
|
|
306
|
+
} else {
|
|
307
|
+
const version = isReleasePerspective(doc.perspective)
|
|
308
|
+
? documents[getVersionId(documentId, doc.perspective.releaseName)]
|
|
309
|
+
: undefined
|
|
310
|
+
if (isReleasePerspective(doc.perspective) && version === undefined) return undefined
|
|
311
|
+
// Standard draft/published logic
|
|
312
|
+
const draft = documents[getDraftId(documentId)]
|
|
313
|
+
const published = documents[getPublishedId(documentId)]
|
|
314
|
+
if (draft === undefined || published === undefined) return undefined
|
|
256
315
|
}
|
|
257
|
-
|
|
258
|
-
// Standard draft/published logic
|
|
259
|
-
const draftId = getDraftId(documentId)
|
|
260
|
-
const publishedId = getPublishedId(documentId)
|
|
261
|
-
|
|
262
|
-
const draft = documents[draftId]
|
|
263
|
-
const published = documents[publishedId]
|
|
264
|
-
|
|
265
|
-
if (draft === undefined || published === undefined) return undefined
|
|
266
316
|
return !queued.length && !applied.length && !outgoing
|
|
267
317
|
},
|
|
268
|
-
onSubscribe: (context, doc: DocumentHandle) =>
|
|
318
|
+
onSubscribe: (context, doc: DocumentHandle) => {
|
|
319
|
+
return manageSubscriberIds(context, [doc])
|
|
320
|
+
},
|
|
269
321
|
}),
|
|
270
322
|
)
|
|
271
323
|
|
|
272
324
|
type PermissionsStateOptions = {
|
|
273
|
-
resource
|
|
325
|
+
resource?: DocumentResource
|
|
274
326
|
actions: DocumentAction[]
|
|
275
327
|
}
|
|
276
328
|
|
|
@@ -279,8 +331,9 @@ export const getPermissionsState = bindActionByResource(
|
|
|
279
331
|
documentStore,
|
|
280
332
|
createStateSourceAction({
|
|
281
333
|
selector: calculatePermissions,
|
|
282
|
-
onSubscribe: (context, {actions}: PermissionsStateOptions) =>
|
|
283
|
-
manageSubscriberIds(context,
|
|
334
|
+
onSubscribe: (context, {actions}: PermissionsStateOptions) => {
|
|
335
|
+
manageSubscriberIds(context, actions)
|
|
336
|
+
},
|
|
284
337
|
}) as StoreAction<
|
|
285
338
|
DocumentStoreState,
|
|
286
339
|
[PermissionsStateOptions],
|
|
@@ -301,7 +354,7 @@ export const resolvePermissions = bindActionByResource(
|
|
|
301
354
|
/** @beta */
|
|
302
355
|
export const subscribeDocumentEvents = bindActionByResource(
|
|
303
356
|
documentStore,
|
|
304
|
-
({state}, options: {resource
|
|
357
|
+
({state}, options: {resource?: DocumentResource; eventHandler: (e: DocumentEvent) => void}) => {
|
|
305
358
|
const {events} = state.get()
|
|
306
359
|
const subscription = events.subscribe(options.eventHandler)
|
|
307
360
|
return () => subscription.unsubscribe()
|
|
@@ -365,24 +418,48 @@ const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
|
365
418
|
withLatestFrom(
|
|
366
419
|
getClientState(instance, {
|
|
367
420
|
apiVersion: API_VERSION,
|
|
368
|
-
|
|
421
|
+
resource,
|
|
369
422
|
}).observable,
|
|
370
423
|
),
|
|
371
424
|
concatMap(([outgoing, client]) => {
|
|
372
425
|
if (!outgoing) return EMPTY
|
|
426
|
+
|
|
427
|
+
const revertOnError = catchError((error: unknown) => {
|
|
428
|
+
state.set('revertOutgoingTransaction', revertOutgoingTransaction)
|
|
429
|
+
const message = error instanceof Error ? error.message : 'Request failed'
|
|
430
|
+
events.next({type: 'reverted', message, outgoing, error})
|
|
431
|
+
return EMPTY
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
const toResult = map((result: unknown) => ({
|
|
435
|
+
result: result as DocumentTransactionSubmissionResult,
|
|
436
|
+
outgoing,
|
|
437
|
+
}))
|
|
438
|
+
|
|
439
|
+
// liveEdit transactions route to the mutations API; everything else routes
|
|
440
|
+
// to the actions API. processActions rejects transactions that mix the two,
|
|
441
|
+
// and reducers won't batch across that boundary, so a batch is always
|
|
442
|
+
// entirely liveEdit or entirely not.
|
|
443
|
+
if (outgoing.actions.some((action) => !isReleaseAction(action) && action.liveEdit)) {
|
|
444
|
+
return client.observable
|
|
445
|
+
.mutate(outgoing.outgoingMutations as Mutation[], {
|
|
446
|
+
transactionId: outgoing.transactionId,
|
|
447
|
+
visibility: 'async',
|
|
448
|
+
returnDocuments: false,
|
|
449
|
+
returnFirst: false,
|
|
450
|
+
tag: 'document.mutate',
|
|
451
|
+
skipCrossDatasetReferenceValidation: true,
|
|
452
|
+
})
|
|
453
|
+
.pipe(revertOnError, toResult)
|
|
454
|
+
}
|
|
455
|
+
|
|
373
456
|
return client.observable
|
|
374
457
|
.action(outgoing.outgoingActions as Action[], {
|
|
375
458
|
transactionId: outgoing.transactionId,
|
|
376
459
|
skipCrossDatasetReferenceValidation: true,
|
|
460
|
+
tag: 'document.action',
|
|
377
461
|
})
|
|
378
|
-
.pipe(
|
|
379
|
-
catchError((error) => {
|
|
380
|
-
state.set('revertOutgoingTransaction', revertOutgoingTransaction)
|
|
381
|
-
events.next({type: 'reverted', message: error.message, outgoing, error})
|
|
382
|
-
return EMPTY
|
|
383
|
-
}),
|
|
384
|
-
map((result) => ({result, outgoing})),
|
|
385
|
-
)
|
|
462
|
+
.pipe(revertOnError, toResult)
|
|
386
463
|
}),
|
|
387
464
|
tap(({outgoing, result}) => {
|
|
388
465
|
state.set('cleanupOutgoingTransaction', cleanupOutgoingTransaction)
|
|
@@ -424,8 +501,8 @@ const subscribeToSubscriptionsAndListenToDocuments = (
|
|
|
424
501
|
...added.map((id) => ({id, add: true})),
|
|
425
502
|
...removed.map((id) => ({id, add: false})),
|
|
426
503
|
].sort((a, b) => {
|
|
427
|
-
const aIsDraft = a.id === getDraftId(a.id)
|
|
428
|
-
const bIsDraft = b.id === getDraftId(b.id)
|
|
504
|
+
const aIsDraft = a.id === getDraftId(DocumentId(a.id))
|
|
505
|
+
const bIsDraft = b.id === getDraftId(DocumentId(b.id))
|
|
429
506
|
|
|
430
507
|
if (aIsDraft && bIsDraft) return a.id.localeCompare(b.id, 'en-US')
|
|
431
508
|
if (aIsDraft) return -1
|
|
@@ -441,10 +518,15 @@ const subscribeToSubscriptionsAndListenToDocuments = (
|
|
|
441
518
|
switchMap((e) => {
|
|
442
519
|
if (!e.add) return EMPTY
|
|
443
520
|
return listen(context, e.id).pipe(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
521
|
+
retry({
|
|
522
|
+
delay: (error, retryCount) => {
|
|
523
|
+
if (!(error instanceof OutOfSyncError)) return throwError(() => error)
|
|
524
|
+
const backoff = Math.min(
|
|
525
|
+
OUT_OF_SYNC_RETRY_BASE_DELAY * 2 ** (retryCount - 1),
|
|
526
|
+
OUT_OF_SYNC_RETRY_MAX_DELAY,
|
|
527
|
+
)
|
|
528
|
+
return timer(backoff)
|
|
529
|
+
},
|
|
448
530
|
}),
|
|
449
531
|
tap((remote) =>
|
|
450
532
|
state.set('applyRemoteDocument', (prev) =>
|
|
@@ -464,10 +546,7 @@ const subscribeToClientAndFetchDatasetAcl = ({
|
|
|
464
546
|
state,
|
|
465
547
|
key: {resource},
|
|
466
548
|
}: StoreContext<DocumentStoreState, BoundResourceKey>) => {
|
|
467
|
-
const clientOptions: ClientOptions = {apiVersion: API_VERSION}
|
|
468
|
-
if (resource) {
|
|
469
|
-
clientOptions.resource = resource
|
|
470
|
-
}
|
|
549
|
+
const clientOptions: ClientOptions = {apiVersion: API_VERSION, resource}
|
|
471
550
|
|
|
472
551
|
let uri: string
|
|
473
552
|
if (resource && isDatasetResource(resource)) {
|
|
@@ -486,7 +565,6 @@ const subscribeToClientAndFetchDatasetAcl = ({
|
|
|
486
565
|
client.observable.request<DatasetAcl>({
|
|
487
566
|
uri,
|
|
488
567
|
tag: 'acl.get',
|
|
489
|
-
withCredentials: true,
|
|
490
568
|
}),
|
|
491
569
|
),
|
|
492
570
|
tap((datasetAcl) => state.set('setGrants', {grants: createGrantsLookup(datasetAcl)})),
|
|
@@ -495,3 +573,16 @@ const subscribeToClientAndFetchDatasetAcl = ({
|
|
|
495
573
|
error: (error) => state.set('setError', {error}),
|
|
496
574
|
})
|
|
497
575
|
}
|
|
576
|
+
|
|
577
|
+
const subscribeToCurrentUserAndSetIdentity = ({
|
|
578
|
+
instance,
|
|
579
|
+
state,
|
|
580
|
+
}: StoreContext<DocumentStoreState, BoundResourceKey>) =>
|
|
581
|
+
getCurrentUserState(instance).observable.subscribe({
|
|
582
|
+
next: (currentUser) => state.set('setIdentity', {identity: currentUser?.id}),
|
|
583
|
+
// A transient identity-fetch failure (network blip, expired token, or a
|
|
584
|
+
// normal logout/re-login transition) should not brick all document
|
|
585
|
+
// operations. Reset the identity to `undefined` and keep going — GROQ's
|
|
586
|
+
// `identity()` then evaluates to null, matching the unauthenticated state.
|
|
587
|
+
error: () => state.set('setIdentity', {identity: undefined}),
|
|
588
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {describe, expect, it} from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {type DocumentAction} from '../document/actions'
|
|
3
|
+
import {type Action, type DocumentAction} from '../document/actions'
|
|
4
4
|
import {type DocumentEvent, getDocumentEvents} from '../document/events'
|
|
5
5
|
import {type OutgoingTransaction} from '../document/reducers'
|
|
6
6
|
|
|
@@ -41,11 +41,66 @@ describe('getDocumentEvents', () => {
|
|
|
41
41
|
expect(events).toHaveLength(outgoing.actions.length)
|
|
42
42
|
events.forEach((event) => {
|
|
43
43
|
const action = outgoing.actions.find(
|
|
44
|
-
(a) => 'documentId' in event && event.documentId === a.documentId,
|
|
44
|
+
(a) => 'documentId' in a && 'documentId' in event && event.documentId === a.documentId,
|
|
45
45
|
)
|
|
46
46
|
expect(action).toBeDefined()
|
|
47
47
|
expect(event.type).toEqual(expectedMap[action!.type])
|
|
48
48
|
expect((event as Extract<DocumentEvent, {outgoing: unknown}>).outgoing).toBe(outgoing)
|
|
49
49
|
})
|
|
50
50
|
})
|
|
51
|
+
|
|
52
|
+
it('emits created/edited/deleted events for release.create/edit/delete with release doc IDs', () => {
|
|
53
|
+
const outgoing: OutgoingTransaction = {
|
|
54
|
+
transactionId: 'txn-release',
|
|
55
|
+
actions: [
|
|
56
|
+
{type: 'release.create', releaseId: 'r1'} as Action,
|
|
57
|
+
{type: 'release.edit', releaseId: 'r2', patch: {set: {}}} as Action,
|
|
58
|
+
{type: 'release.delete', releaseId: 'r3'} as Action,
|
|
59
|
+
],
|
|
60
|
+
disableBatching: false,
|
|
61
|
+
batchedTransactionIds: [],
|
|
62
|
+
outgoingActions: [],
|
|
63
|
+
outgoingMutations: [],
|
|
64
|
+
base: {},
|
|
65
|
+
working: {},
|
|
66
|
+
previous: {},
|
|
67
|
+
previousRevs: {},
|
|
68
|
+
timestamp: '2025-02-06T00:00:00.000Z',
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const events = getDocumentEvents(outgoing)
|
|
72
|
+
expect(events).toEqual([
|
|
73
|
+
{type: 'created', documentId: '_.releases.r1', outgoing},
|
|
74
|
+
{type: 'edited', documentId: '_.releases.r2', outgoing},
|
|
75
|
+
{type: 'deleted', documentId: '_.releases.r3', outgoing},
|
|
76
|
+
])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('skips release actions that have no local mutation (publish/schedule/archive/etc.)', () => {
|
|
80
|
+
const outgoing: OutgoingTransaction = {
|
|
81
|
+
transactionId: 'txn-release-noop',
|
|
82
|
+
actions: [
|
|
83
|
+
{type: 'release.publish', releaseId: 'r1'} as Action,
|
|
84
|
+
{
|
|
85
|
+
type: 'release.schedule',
|
|
86
|
+
releaseId: 'r2',
|
|
87
|
+
publishAt: '2026-01-01T00:00:00.000Z',
|
|
88
|
+
} as Action,
|
|
89
|
+
{type: 'release.unschedule', releaseId: 'r3'} as Action,
|
|
90
|
+
{type: 'release.archive', releaseId: 'r4'} as Action,
|
|
91
|
+
{type: 'release.unarchive', releaseId: 'r5'} as Action,
|
|
92
|
+
],
|
|
93
|
+
disableBatching: false,
|
|
94
|
+
batchedTransactionIds: [],
|
|
95
|
+
outgoingActions: [],
|
|
96
|
+
outgoingMutations: [],
|
|
97
|
+
base: {},
|
|
98
|
+
working: {},
|
|
99
|
+
previous: {},
|
|
100
|
+
previousRevs: {},
|
|
101
|
+
timestamp: '2025-02-06T00:00:00.000Z',
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
expect(getDocumentEvents(outgoing)).toEqual([])
|
|
105
|
+
})
|
|
51
106
|
})
|
package/src/document/events.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import {type SanityClient} from '@sanity/client'
|
|
1
|
+
import {type MultipleMutationResult, type SanityClient} from '@sanity/client'
|
|
2
|
+
import {type PatchOperations} from '@sanity/types'
|
|
2
3
|
|
|
3
|
-
import {type DocumentAction} from './actions'
|
|
4
|
+
import {type DocumentAction, type ReleaseAction} from './actions'
|
|
5
|
+
import {getReleaseDocumentId, isReleaseAction} from './processActions/releaseUtil'
|
|
4
6
|
import {type OutgoingTransaction} from './reducers'
|
|
5
7
|
|
|
8
|
+
/** @beta Response body from submitting an outgoing transaction (actions or mutations API). */
|
|
9
|
+
export type DocumentTransactionSubmissionResult =
|
|
10
|
+
| Awaited<ReturnType<SanityClient['action']>>
|
|
11
|
+
| MultipleMutationResult
|
|
12
|
+
|
|
6
13
|
/** @beta */
|
|
7
14
|
export type DocumentEvent =
|
|
8
15
|
| ActionErrorEvent
|
|
@@ -15,6 +22,7 @@ export type DocumentEvent =
|
|
|
15
22
|
| DocumentPublishedEvent
|
|
16
23
|
| DocumentUnpublishedEvent
|
|
17
24
|
| DocumentDiscardedEvent
|
|
25
|
+
| DocumentRemotePatchesEvent
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
28
|
* @beta
|
|
@@ -35,7 +43,7 @@ export interface ActionErrorEvent {
|
|
|
35
43
|
export interface TransactionAcceptedEvent {
|
|
36
44
|
type: 'accepted'
|
|
37
45
|
outgoing: OutgoingTransaction
|
|
38
|
-
result:
|
|
46
|
+
result: DocumentTransactionSubmissionResult
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
49
|
* @beta
|
|
@@ -113,31 +121,79 @@ export interface DocumentDiscardedEvent {
|
|
|
113
121
|
outgoing: OutgoingTransaction
|
|
114
122
|
}
|
|
115
123
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
/**
|
|
125
|
+
* @beta
|
|
126
|
+
* Event emitted when patches for a document are observed through the listener.
|
|
127
|
+
*
|
|
128
|
+
* Unlike the whole-document snapshots the store keeps, these are the raw patch
|
|
129
|
+
* operations from the transaction that produced the change, preserving the
|
|
130
|
+
* operational intent of the edit (e.g. keyed array inserts/unsets). Consumers
|
|
131
|
+
* that maintain their own local state, like collaborative text editors, can
|
|
132
|
+
* apply these directly instead of diffing document snapshots.
|
|
133
|
+
*
|
|
134
|
+
* `origin` is `'local'` when the transaction was submitted by this document
|
|
135
|
+
* store instance (an own edit observed back through the listener) and
|
|
136
|
+
* `'remote'` when it came from another client.
|
|
137
|
+
*/
|
|
138
|
+
export interface DocumentRemotePatchesEvent {
|
|
139
|
+
type: 'remote-patches'
|
|
140
|
+
/** The ID of the document version the patches apply to (e.g. a draft ID). */
|
|
141
|
+
documentId: string
|
|
142
|
+
/** The transaction ID that carried these patches. */
|
|
143
|
+
transactionId: string
|
|
144
|
+
/** The revision the document was at before this transaction. */
|
|
145
|
+
previousRev?: string
|
|
146
|
+
/** The timestamp of the transaction. */
|
|
147
|
+
timestamp: string
|
|
148
|
+
/** The patch operations, rooted at the document. */
|
|
149
|
+
patches: PatchOperations[]
|
|
150
|
+
/** Whether the transaction originated from this client or another one. */
|
|
151
|
+
origin: 'local' | 'remote'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Release actions that write a mutation to the local release doc map onto
|
|
155
|
+
// the regular per-document events with `documentId = '_.releases.<releaseId>'`.
|
|
156
|
+
// The other release actions (publish/schedule/unschedule/archive/unarchive)
|
|
157
|
+
// don't mutate local state, so they aren't in the map and get skipped — they
|
|
158
|
+
// surface through the transaction-level `accepted`/`reverted` events instead.
|
|
159
|
+
const actionMap = {
|
|
160
|
+
'document.create': 'created',
|
|
161
|
+
'document.delete': 'deleted',
|
|
162
|
+
'document.discard': 'discarded',
|
|
163
|
+
'document.edit': 'edited',
|
|
164
|
+
'document.publish': 'published',
|
|
165
|
+
'document.unpublish': 'unpublished',
|
|
166
|
+
'release.create': 'created',
|
|
167
|
+
'release.edit': 'edited',
|
|
168
|
+
'release.delete': 'deleted',
|
|
169
|
+
} satisfies Partial<Record<DocumentAction['type'] | ReleaseAction['type'], DocumentEvent['type']>>
|
|
128
170
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
type MappedActionType = keyof typeof actionMap
|
|
172
|
+
|
|
173
|
+
export function getDocumentEvents(outgoing: OutgoingTransaction): DocumentEvent[] {
|
|
174
|
+
const documentIdsByAction = outgoing.actions.reduce(
|
|
175
|
+
(acc, action) => {
|
|
176
|
+
if (!(action.type in actionMap)) return acc
|
|
177
|
+
const documentId = isReleaseAction(action)
|
|
178
|
+
? getReleaseDocumentId(action.releaseId)
|
|
179
|
+
: action.documentId
|
|
180
|
+
if (!documentId) return acc
|
|
181
|
+
const type = action.type as MappedActionType
|
|
182
|
+
const ids = acc[type] ?? new Set<string>()
|
|
183
|
+
ids.add(documentId)
|
|
184
|
+
acc[type] = ids
|
|
185
|
+
return acc
|
|
186
|
+
},
|
|
187
|
+
{} as Partial<Record<MappedActionType, Set<string>>>,
|
|
188
|
+
)
|
|
137
189
|
|
|
138
|
-
return documentIdsByAction.flatMap(([actionType, documentIds]) =>
|
|
139
|
-
Array.from(documentIds).map(
|
|
140
|
-
(documentId): DocumentEvent => ({
|
|
190
|
+
return Object.entries(documentIdsByAction).flatMap(([actionType, documentIds]) =>
|
|
191
|
+
Array.from(documentIds ?? []).map(
|
|
192
|
+
(documentId): DocumentEvent => ({
|
|
193
|
+
type: actionMap[actionType as MappedActionType],
|
|
194
|
+
documentId,
|
|
195
|
+
outgoing,
|
|
196
|
+
}),
|
|
141
197
|
),
|
|
142
198
|
)
|
|
143
199
|
}
|
|
@@ -205,4 +205,60 @@ describe('sortListenerEvents operator', () => {
|
|
|
205
205
|
const events = await lastValueFrom(source$.pipe(sortListenerEvents(), toArray()))
|
|
206
206
|
expect(events).toEqual([other])
|
|
207
207
|
})
|
|
208
|
+
|
|
209
|
+
it('should discard replayed events already reflected in the base', async () => {
|
|
210
|
+
// The snapshot is at rev2; a resumed listener replays the event that
|
|
211
|
+
// produced rev2. It should be discarded instead of clogging the buffer.
|
|
212
|
+
const sync = createSyncEvent('rev2')
|
|
213
|
+
const replayed = createMutationEvent({
|
|
214
|
+
id: 'replayed',
|
|
215
|
+
previousRev: 'rev1',
|
|
216
|
+
resultRev: 'rev2',
|
|
217
|
+
})
|
|
218
|
+
const source$ = of(sync, replayed)
|
|
219
|
+
const events = await lastValueFrom(source$.pipe(sortListenerEvents(), toArray()))
|
|
220
|
+
expect(events).toEqual([sync])
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should discard the replayed part of a chain and apply the rest', async () => {
|
|
224
|
+
// The snapshot is at rev2; the listener replays rev1->rev2 followed by a
|
|
225
|
+
// genuinely new rev2->rev3. Only the new event should be emitted.
|
|
226
|
+
const sync = createSyncEvent('rev2')
|
|
227
|
+
const replayed = createMutationEvent({
|
|
228
|
+
id: 'replayed',
|
|
229
|
+
previousRev: 'rev1',
|
|
230
|
+
resultRev: 'rev2',
|
|
231
|
+
})
|
|
232
|
+
const fresh = createMutationEvent({
|
|
233
|
+
id: 'fresh',
|
|
234
|
+
previousRev: 'rev2',
|
|
235
|
+
resultRev: 'rev3',
|
|
236
|
+
})
|
|
237
|
+
const source$ = of(sync, replayed, fresh)
|
|
238
|
+
const events = await lastValueFrom(source$.pipe(sortListenerEvents(), toArray()))
|
|
239
|
+
expect(events).toEqual([sync, fresh])
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('should discard a multi-event replayed chain that leads up to the base', async () => {
|
|
243
|
+
const sync = createSyncEvent('rev3')
|
|
244
|
+
// two replayed events that together lead up to the base revision
|
|
245
|
+
const replayedA = createMutationEvent({
|
|
246
|
+
id: 'replayedA',
|
|
247
|
+
previousRev: 'rev1',
|
|
248
|
+
resultRev: 'rev2',
|
|
249
|
+
})
|
|
250
|
+
const replayedB = createMutationEvent({
|
|
251
|
+
id: 'replayedB',
|
|
252
|
+
previousRev: 'rev2',
|
|
253
|
+
resultRev: 'rev3',
|
|
254
|
+
})
|
|
255
|
+
const fresh = createMutationEvent({
|
|
256
|
+
id: 'fresh',
|
|
257
|
+
previousRev: 'rev3',
|
|
258
|
+
resultRev: 'rev4',
|
|
259
|
+
})
|
|
260
|
+
const source$ = of(sync, replayedA, replayedB, fresh)
|
|
261
|
+
const events = await lastValueFrom(source$.pipe(sortListenerEvents(), toArray()))
|
|
262
|
+
expect(events).toEqual([sync, fresh])
|
|
263
|
+
})
|
|
208
264
|
})
|