@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.2
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/README.md +12 -0
- package/dist/_exports/_internal.d.ts +163 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +332 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/_exports/agent.d.ts +65 -0
- package/dist/_exports/agent.d.ts.map +1 -0
- package/dist/_exports/agent.js +71 -0
- package/dist/_exports/agent.js.map +1 -0
- package/dist/_exports/comlink.d.ts +118 -0
- package/dist/_exports/comlink.d.ts.map +1 -0
- package/dist/_exports/comlink.js +69 -0
- package/dist/_exports/comlink.js.map +1 -0
- package/dist/authStore-CtE9bLHW.js +754 -0
- package/dist/authStore-CtE9bLHW.js.map +1 -0
- package/dist/authStore-DKFX8fn7.d.ts +3179 -0
- package/dist/authStore-DKFX8fn7.d.ts.map +1 -0
- package/dist/clientStore-BtE2-q2i.js +90 -0
- package/dist/clientStore-BtE2-q2i.js.map +1 -0
- package/dist/createSanityInstance-DUI1bGIg.d.ts +278 -0
- package/dist/createSanityInstance-DUI1bGIg.d.ts.map +1 -0
- package/dist/createStateSourceAction-BOKtqFNm.js +300 -0
- package/dist/createStateSourceAction-BOKtqFNm.js.map +1 -0
- package/dist/getEnv-D5RGkQLu.js +8 -0
- package/dist/getEnv-D5RGkQLu.js.map +1 -0
- package/dist/getNodeState-CR3__B2D.js +67 -0
- package/dist/getNodeState-CR3__B2D.js.map +1 -0
- package/dist/index.d.ts +3 -2332
- package/dist/index.js +4171 -4332
- package/dist/index.js.map +1 -1
- package/dist/logger-DAtJb5RQ.js +173 -0
- package/dist/logger-DAtJb5RQ.js.map +1 -0
- package/dist/object-CxXlSVg7.js +83 -0
- package/dist/object-CxXlSVg7.js.map +1 -0
- package/dist/queryStore-DPf9sN3V.js +1346 -0
- package/dist/queryStore-DPf9sN3V.js.map +1 -0
- package/dist/rolldown-runtime-vyAXikos.js +9 -0
- package/dist/setCleanupTimeout-BiM6q1KF.js +31 -0
- package/dist/setCleanupTimeout-BiM6q1KF.js.map +1 -0
- package/dist/telemetryManager-Dq_qCkeI.js +127 -0
- package/dist/telemetryManager-Dq_qCkeI.js.map +1 -0
- package/dist/version-BChjsi4u.js +9 -0
- package/dist/version-BChjsi4u.js.map +1 -0
- package/package.json +47 -52
- package/src/_exports/_internal.ts +27 -0
- package/src/_exports/agent.ts +19 -0
- package/src/_exports/comlink.ts +16 -0
- package/src/_exports/index.ts +125 -64
- 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 +88 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +367 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +58 -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/comments/addonDatasetStore.test.ts +215 -0
- package/src/comments/addonDatasetStore.ts +249 -0
- package/src/comments/buildCommentThreads.test.ts +83 -0
- package/src/comments/buildCommentThreads.ts +54 -0
- package/src/comments/commentActions.test.ts +540 -0
- package/src/comments/commentActions.ts +555 -0
- package/src/comments/commentFieldPath.test.ts +40 -0
- package/src/comments/commentFieldPath.ts +44 -0
- package/src/comments/commentsConstants.ts +80 -0
- package/src/comments/commentsStore.test.ts +419 -0
- package/src/comments/commentsStore.ts +483 -0
- package/src/comments/normalizeComment.test.ts +116 -0
- package/src/comments/normalizeComment.ts +45 -0
- package/src/comments/observeComments.test.ts +193 -0
- package/src/comments/observeComments.ts +113 -0
- package/src/comments/reducers.test.ts +277 -0
- package/src/comments/reducers.ts +316 -0
- package/src/comments/types.ts +249 -0
- package/src/comments/weakenReferences.test.ts +47 -0
- package/src/comments/weakenReferences.ts +29 -0
- package/src/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +40 -82
- 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 +35 -13
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +15 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +753 -273
- package/src/document/documentStore.ts +197 -75
- 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 +76 -2
- package/src/document/patchOperations.ts +60 -1
- package/src/document/permissions.test.ts +107 -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 +772 -64
- package/src/document/reducers.ts +205 -51
- 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.test.ts +46 -0
- package/src/document/util.ts +50 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +73 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +206 -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 +201 -9
- package/src/presence/bifurTransport.ts +98 -14
- package/src/presence/paths.test.ts +70 -0
- package/src/presence/paths.ts +41 -0
- package/src/presence/presenceStore.test.ts +915 -24
- package/src/presence/presenceStore.ts +576 -104
- package/src/presence/types.ts +175 -3
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/types.ts +0 -15
- 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 -9
- 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/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 +85 -69
- 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 +109 -90
- package/src/store/createActionBinder.ts +36 -28
- package/src/store/createSanityInstance.test.ts +15 -13
- package/src/store/createSanityInstance.ts +14 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +38 -43
- 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 +3 -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
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {DocumentId, getPublishedId} from '@sanity/id-utils'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type DocumentResource,
|
|
5
|
+
isCanvasResource,
|
|
6
|
+
isMediaLibraryResource,
|
|
7
|
+
} from '../config/sanityConfig'
|
|
8
|
+
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
9
|
+
import {type Action, type DocumentAction} from './actions'
|
|
10
|
+
import {getEffectiveDocumentId} from './util'
|
|
11
|
+
|
|
12
|
+
export interface EffectiveDocModel {
|
|
13
|
+
/**
|
|
14
|
+
* If this is `undefined`, the resource has no opinion and the handle's `liveEdit`
|
|
15
|
+
* flag should be respected.
|
|
16
|
+
*/
|
|
17
|
+
liveEdit: boolean | undefined
|
|
18
|
+
/**
|
|
19
|
+
* When `false`, the resource does not support release perspectives. Callers
|
|
20
|
+
* should drop any release perspective and fall back to the standard path.
|
|
21
|
+
*/
|
|
22
|
+
supportsReleases: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const MEDIA_LIBRARY_DRAFTED_TYPES = new Set(['sanity.asset'])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Different resources have different "default" editing models.
|
|
29
|
+
*
|
|
30
|
+
* Canvas uses a liveEdit model.
|
|
31
|
+
* Medial Library is mostly liveEdit except for `sanity.asset`, which retains the
|
|
32
|
+
* draft/published model. Neither resource supports release perspectives.
|
|
33
|
+
*/
|
|
34
|
+
export function getEffectiveDocumentModel(
|
|
35
|
+
resource: DocumentResource | undefined,
|
|
36
|
+
documentType: string | undefined,
|
|
37
|
+
): EffectiveDocModel {
|
|
38
|
+
if (!resource) {
|
|
39
|
+
return {liveEdit: undefined, supportsReleases: true}
|
|
40
|
+
}
|
|
41
|
+
if (isCanvasResource(resource)) {
|
|
42
|
+
return {liveEdit: true, supportsReleases: false}
|
|
43
|
+
}
|
|
44
|
+
if (isMediaLibraryResource(resource)) {
|
|
45
|
+
const isDrafted = documentType ? MEDIA_LIBRARY_DRAFTED_TYPES.has(documentType) : false
|
|
46
|
+
return {liveEdit: !isDrafted, supportsReleases: false}
|
|
47
|
+
}
|
|
48
|
+
return {liveEdit: undefined, supportsReleases: true}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function describeResource(resource: DocumentResource | undefined): string {
|
|
52
|
+
if (resource && isCanvasResource(resource)) return 'Canvas'
|
|
53
|
+
if (resource && isMediaLibraryResource(resource)) return 'Media Library'
|
|
54
|
+
return 'this resource'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Rewrites edit actions to match the editing model of the bound resource.
|
|
59
|
+
*
|
|
60
|
+
* Canvas and Media Library resources do not support release perspectives, and
|
|
61
|
+
* Canvas (plus most Media Library types) does not have a draft/published
|
|
62
|
+
* model. When an edit action arrives with a release perspective for a resource
|
|
63
|
+
* that doesn't support it, the perspective is stripped and a single warning
|
|
64
|
+
* is logged. When the resource forces liveEdit, the action's `liveEdit` flag
|
|
65
|
+
* is set so the dispatcher takes the liveEdit branch.
|
|
66
|
+
*
|
|
67
|
+
* Only `document.edit` actions are normalized today — other action types
|
|
68
|
+
* (publish, unpublish, release actions, etc.) pass through unchanged.
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export function normalizeActionsForResource(
|
|
73
|
+
actions: Action[],
|
|
74
|
+
resource: DocumentResource | undefined,
|
|
75
|
+
): Action[] {
|
|
76
|
+
// collect actions that may have changed in unexpected ways
|
|
77
|
+
const stripped: Array<{documentType: string; documentId: string}> = []
|
|
78
|
+
|
|
79
|
+
const normalized = actions.map((action) => {
|
|
80
|
+
if (action.type !== 'document.edit') return action
|
|
81
|
+
|
|
82
|
+
const {liveEdit: forcedLiveEdit, supportsReleases} = getEffectiveDocumentModel(
|
|
83
|
+
resource,
|
|
84
|
+
action.documentType,
|
|
85
|
+
)
|
|
86
|
+
const shouldRemovePerspective = isReleasePerspective(action.perspective) && !supportsReleases
|
|
87
|
+
const shouldForceLiveEdit = forcedLiveEdit === true && !action.liveEdit
|
|
88
|
+
|
|
89
|
+
if (!shouldRemovePerspective && !shouldForceLiveEdit) return action
|
|
90
|
+
|
|
91
|
+
const corrected: DocumentAction = {...action}
|
|
92
|
+
if (shouldForceLiveEdit) corrected.liveEdit = true
|
|
93
|
+
if (shouldRemovePerspective) corrected.perspective = undefined
|
|
94
|
+
|
|
95
|
+
// ensure we're using the right document ID for the corrected model
|
|
96
|
+
corrected.documentId = getEffectiveDocumentId({
|
|
97
|
+
...corrected,
|
|
98
|
+
documentId: getPublishedId(DocumentId(corrected.documentId)),
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
if (shouldRemovePerspective) {
|
|
102
|
+
stripped.push({documentType: action.documentType, documentId: corrected.documentId})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return corrected
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
if (stripped.length > 0) {
|
|
109
|
+
const docs = stripped.map((e) => `${e.documentType} (${e.documentId})`).join(', ')
|
|
110
|
+
// eslint-disable-next-line no-console
|
|
111
|
+
console.warn(
|
|
112
|
+
`[sanity-sdk] ${describeResource(resource)} does not support release perspectives — falling back to the standard editing path for: ${docs}`,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return normalized
|
|
117
|
+
}
|
|
@@ -16,7 +16,7 @@ import {createSanityInstance} from '../store/createSanityInstance'
|
|
|
16
16
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
17
17
|
import {createFetchDocument, createSharedListener} from './sharedListener'
|
|
18
18
|
|
|
19
|
-
const instance = createSanityInstance()
|
|
19
|
+
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
20
20
|
|
|
21
21
|
vi.mock('../client/clientStore.ts', () => ({getClientState: vi.fn()}))
|
|
22
22
|
vi.mock('@sanity/mutate/_unstable_store', () => ({createDocumentLoaderFromClient: vi.fn()}))
|
|
@@ -43,14 +43,16 @@ describe('createSharedListener', () => {
|
|
|
43
43
|
})
|
|
44
44
|
|
|
45
45
|
it('should call client.listen with the expected parameters', () => {
|
|
46
|
-
createSharedListener(instance
|
|
46
|
+
createSharedListener(instance).events.subscribe()
|
|
47
47
|
expect(fakeClient.listen).toHaveBeenCalledTimes(1)
|
|
48
48
|
expect(fakeClient.listen).toHaveBeenCalledWith(
|
|
49
49
|
'*',
|
|
50
50
|
{},
|
|
51
51
|
{
|
|
52
|
-
events: ['mutation', 'welcome', 'reconnect'],
|
|
52
|
+
events: ['mutation', 'welcome', 'welcomeback', 'reconnect', 'reset'],
|
|
53
|
+
enableResume: true,
|
|
53
54
|
includeResult: false,
|
|
55
|
+
includeAllVersions: true,
|
|
54
56
|
tag: 'document-listener',
|
|
55
57
|
},
|
|
56
58
|
)
|
|
@@ -62,7 +64,7 @@ describe('createSharedListener', () => {
|
|
|
62
64
|
const mutationEvent: MutationEvent = {type: 'mutation'} as MutationEvent
|
|
63
65
|
const reconnectEvent: ReconnectEvent = {type: 'reconnect'} as ReconnectEvent
|
|
64
66
|
|
|
65
|
-
const sharedListener = createSharedListener(instance
|
|
67
|
+
const sharedListener = createSharedListener(instance)
|
|
66
68
|
// Start collecting the emitted events.
|
|
67
69
|
const eventsPromise = lastValueFrom(sharedListener.events.pipe(toArray()))
|
|
68
70
|
|
|
@@ -80,7 +82,7 @@ describe('createSharedListener', () => {
|
|
|
80
82
|
it('should replay the welcome event for new subscribers', async () => {
|
|
81
83
|
const welcomeEvent: WelcomeEvent = {type: 'welcome', listenerName: 'listener'}
|
|
82
84
|
|
|
83
|
-
const sharedListener = createSharedListener(instance
|
|
85
|
+
const sharedListener = createSharedListener(instance)
|
|
84
86
|
// First subscription: emit welcome and complete.
|
|
85
87
|
const firstPromise = lastValueFrom(sharedListener.events.pipe(toArray()))
|
|
86
88
|
fakeListenSubject.next(welcomeEvent)
|
|
@@ -97,7 +99,7 @@ describe('createSharedListener', () => {
|
|
|
97
99
|
const mutationEvent = {type: 'mutation'} as MutationEvent
|
|
98
100
|
const reconnectEvent = {type: 'reconnect'} as ReconnectEvent
|
|
99
101
|
|
|
100
|
-
const sharedListener = createSharedListener(instance
|
|
102
|
+
const sharedListener = createSharedListener(instance)
|
|
101
103
|
const eventsPromise = lastValueFrom(sharedListener.events.pipe(toArray()))
|
|
102
104
|
fakeListenSubject.next(mutationEvent)
|
|
103
105
|
fakeListenSubject.next(reconnectEvent)
|
|
@@ -110,7 +112,7 @@ describe('createSharedListener', () => {
|
|
|
110
112
|
const welcomeEvent = {type: 'welcome'} as WelcomeEvent
|
|
111
113
|
const mutationEvent = {type: 'mutation'} as MutationEvent
|
|
112
114
|
|
|
113
|
-
const sharedListener = createSharedListener(instance
|
|
115
|
+
const sharedListener = createSharedListener(instance)
|
|
114
116
|
|
|
115
117
|
// Subscribe two observers concurrently.
|
|
116
118
|
const subscriber1 = sharedListener.events.pipe(bufferTime(0))
|
|
@@ -133,7 +135,7 @@ describe('createSharedListener', () => {
|
|
|
133
135
|
|
|
134
136
|
it('should propagate errors from the underlying client.listen observable', async () => {
|
|
135
137
|
const errorMessage = 'Test error'
|
|
136
|
-
const sharedListener = createSharedListener(instance
|
|
138
|
+
const sharedListener = createSharedListener(instance)
|
|
137
139
|
|
|
138
140
|
const error$ = sharedListener.events.pipe(
|
|
139
141
|
toArray(),
|
|
@@ -149,7 +151,7 @@ describe('createSharedListener', () => {
|
|
|
149
151
|
it('should stop emitting events after calling dispose', async () => {
|
|
150
152
|
const welcomeEvent: WelcomeEvent = {type: 'welcome', listenerName: 'listener'}
|
|
151
153
|
const mutationEvent: MutationEvent = {type: 'mutation'} as MutationEvent
|
|
152
|
-
const sharedListener = createSharedListener(instance
|
|
154
|
+
const sharedListener = createSharedListener(instance)
|
|
153
155
|
|
|
154
156
|
const events: ListenEvent<SanityDocument>[] = []
|
|
155
157
|
const subscription = sharedListener.events.subscribe((event) => {
|
|
@@ -187,7 +189,7 @@ describe('createFetchDocument', () => {
|
|
|
187
189
|
})
|
|
188
190
|
|
|
189
191
|
it('should call createDocumentLoaderFromClient with the fetched client', async () => {
|
|
190
|
-
const fetchDocument = createFetchDocument(instance
|
|
192
|
+
const fetchDocument = createFetchDocument(instance)
|
|
191
193
|
const accessibleResult = {
|
|
192
194
|
id: 'doc1',
|
|
193
195
|
document: {_id: 'doc1', _rev: 'rev1'},
|
|
@@ -201,7 +203,7 @@ describe('createFetchDocument', () => {
|
|
|
201
203
|
})
|
|
202
204
|
|
|
203
205
|
it('should return the document when it is accessible', async () => {
|
|
204
|
-
const fetchDocument = createFetchDocument(instance
|
|
206
|
+
const fetchDocument = createFetchDocument(instance)
|
|
205
207
|
const accessibleResult = {
|
|
206
208
|
id: 'doc1',
|
|
207
209
|
document: {_id: 'doc1', _rev: 'rev1', title: 'Test Document'},
|
|
@@ -214,7 +216,7 @@ describe('createFetchDocument', () => {
|
|
|
214
216
|
})
|
|
215
217
|
|
|
216
218
|
it('should return null when the document is inaccessible due to existence', async () => {
|
|
217
|
-
const fetchDocument = createFetchDocument(instance
|
|
219
|
+
const fetchDocument = createFetchDocument(instance)
|
|
218
220
|
const inaccessibleResult = {
|
|
219
221
|
accessible: false,
|
|
220
222
|
id: 'doc1',
|
|
@@ -227,7 +229,7 @@ describe('createFetchDocument', () => {
|
|
|
227
229
|
})
|
|
228
230
|
|
|
229
231
|
it('should throw an error when the document is inaccessible due to permissions', async () => {
|
|
230
|
-
const fetchDocument = createFetchDocument(instance
|
|
232
|
+
const fetchDocument = createFetchDocument(instance)
|
|
231
233
|
const inaccessibleResult = {
|
|
232
234
|
accessible: false,
|
|
233
235
|
id: 'doc1',
|
|
@@ -241,7 +243,7 @@ describe('createFetchDocument', () => {
|
|
|
241
243
|
})
|
|
242
244
|
|
|
243
245
|
it('should propagate errors from the loadDocument observable', async () => {
|
|
244
|
-
const fetchDocument = createFetchDocument(instance
|
|
246
|
+
const fetchDocument = createFetchDocument(instance)
|
|
245
247
|
const errorMessage = 'Load document failed'
|
|
246
248
|
fakeLoadDocument.mockReturnValue(throwError(() => new Error(errorMessage)))
|
|
247
249
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import {getClientState} from '../client/clientStore'
|
|
17
17
|
import {type DocumentResource} from '../config/sanityConfig'
|
|
18
18
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
19
|
+
import {dedupeListenerEvents, groupTransactionEvents} from './listenerEventOperators'
|
|
19
20
|
|
|
20
21
|
const API_VERSION = 'v2025-05-06'
|
|
21
22
|
|
|
@@ -26,12 +27,12 @@ export interface SharedListener {
|
|
|
26
27
|
|
|
27
28
|
export function createSharedListener(
|
|
28
29
|
instance: SanityInstance,
|
|
29
|
-
resource
|
|
30
|
+
resource?: DocumentResource,
|
|
30
31
|
): SharedListener {
|
|
31
32
|
const dispose$ = new Subject<void>()
|
|
32
33
|
const events$ = getClientState(instance, {
|
|
33
34
|
apiVersion: API_VERSION,
|
|
34
|
-
|
|
35
|
+
resource,
|
|
35
36
|
}).observable.pipe(
|
|
36
37
|
switchMap((client) =>
|
|
37
38
|
// TODO: it seems like the client.listen method is not emitting disconnected
|
|
@@ -41,8 +42,15 @@ export function createSharedListener(
|
|
|
41
42
|
'*',
|
|
42
43
|
{},
|
|
43
44
|
{
|
|
44
|
-
|
|
45
|
+
// with `enableResume`, a reconnect replays the events missed while
|
|
46
|
+
// offline (emitting `welcomeback`) instead of forcing a refetch of
|
|
47
|
+
// every subscribed document. when the backend cannot resume, it
|
|
48
|
+
// emits `welcome` or `reset`, both of which trigger a refetch
|
|
49
|
+
events: ['mutation', 'welcome', 'welcomeback', 'reconnect', 'reset'],
|
|
50
|
+
enableResume: true,
|
|
45
51
|
includeResult: false,
|
|
52
|
+
// the document store handles version documents, so we need to include all versions
|
|
53
|
+
includeAllVersions: true,
|
|
46
54
|
tag: 'document-listener',
|
|
47
55
|
// // from manual testing, it seems like mendoza patches may be
|
|
48
56
|
// // causing some ambiguity/wonkiness
|
|
@@ -51,6 +59,11 @@ export function createSharedListener(
|
|
|
51
59
|
},
|
|
52
60
|
),
|
|
53
61
|
),
|
|
62
|
+
// resumed events are delivered at-least-once, so drop replayed duplicates
|
|
63
|
+
dedupeListenerEvents(),
|
|
64
|
+
// hold multi-document transactions (e.g. publish) until complete so
|
|
65
|
+
// per-document processing never observes half a transaction
|
|
66
|
+
groupTransactionEvents(),
|
|
54
67
|
takeUntil(dispose$),
|
|
55
68
|
share(),
|
|
56
69
|
)
|
|
@@ -67,13 +80,16 @@ export function createSharedListener(
|
|
|
67
80
|
}
|
|
68
81
|
}
|
|
69
82
|
|
|
70
|
-
export function createFetchDocument(instance: SanityInstance, resource
|
|
83
|
+
export function createFetchDocument(instance: SanityInstance, resource?: DocumentResource) {
|
|
71
84
|
return function (documentId: string): Observable<SanityDocument | null> {
|
|
72
|
-
return getClientState(instance, {
|
|
85
|
+
return getClientState(instance, {
|
|
86
|
+
apiVersion: API_VERSION,
|
|
87
|
+
resource,
|
|
88
|
+
}).observable.pipe(
|
|
73
89
|
switchMap((client) => {
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
const loadDocument = createDocumentLoaderFromClient(client
|
|
90
|
+
// creates a observable request to the /doc/{documentId} endpoint for a given document id
|
|
91
|
+
// should work across all kinds of document IDs (drafts.**, version.**., etc.)
|
|
92
|
+
const loadDocument = createDocumentLoaderFromClient(client)
|
|
77
93
|
return loadDocument(documentId)
|
|
78
94
|
}),
|
|
79
95
|
map((result) => {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {getEditingDocumentId, getEffectiveDocumentId} from './util'
|
|
4
|
+
|
|
5
|
+
const PUBLISHED = 'movie-1'
|
|
6
|
+
const DRAFT = 'drafts.movie-1'
|
|
7
|
+
const VERSION = 'versions.autumn.movie-1'
|
|
8
|
+
|
|
9
|
+
describe('getEditingDocumentId', () => {
|
|
10
|
+
it('resolves the draft by default, which is what the Studio edits', () => {
|
|
11
|
+
expect(getEditingDocumentId({documentId: PUBLISHED})).toBe(DRAFT)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("resolves the draft under the 'drafts' perspective", () => {
|
|
15
|
+
expect(getEditingDocumentId({documentId: PUBLISHED, perspective: 'drafts'})).toBe(DRAFT)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it("resolves the published document under the 'published' perspective", () => {
|
|
19
|
+
expect(getEditingDocumentId({documentId: PUBLISHED, perspective: 'published'})).toBe(PUBLISHED)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('resolves the version under a release perspective', () => {
|
|
23
|
+
expect(
|
|
24
|
+
getEditingDocumentId({documentId: PUBLISHED, perspective: {releaseName: 'autumn'}}),
|
|
25
|
+
).toBe(VERSION)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('resolves the published document for live edit, which has no draft', () => {
|
|
29
|
+
expect(getEditingDocumentId({documentId: PUBLISHED, liveEdit: true})).toBe(PUBLISHED)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('is idempotent when handed an already-specific id', () => {
|
|
33
|
+
expect(getEditingDocumentId({documentId: DRAFT})).toBe(DRAFT)
|
|
34
|
+
expect(getEditingDocumentId({documentId: VERSION, perspective: 'published'})).toBe(PUBLISHED)
|
|
35
|
+
expect(getEditingDocumentId({documentId: DRAFT, perspective: {releaseName: 'autumn'}})).toBe(
|
|
36
|
+
VERSION,
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('differs from getEffectiveDocumentId precisely where it has to', () => {
|
|
41
|
+
// The canonical key collapses a draft onto its published id, which is right for
|
|
42
|
+
// storing state and wrong for anything comparing exact ids.
|
|
43
|
+
expect(getEffectiveDocumentId({documentId: DRAFT, documentType: 'movie'})).toBe(PUBLISHED)
|
|
44
|
+
expect(getEditingDocumentId({documentId: DRAFT})).toBe(DRAFT)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
|
|
2
|
+
|
|
3
|
+
import {type DocumentHandle} from '../config/sanityConfig'
|
|
4
|
+
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The id of the specific document a user is editing under a given perspective:
|
|
8
|
+
* the draft, the published document, or a release version.
|
|
9
|
+
*
|
|
10
|
+
* Distinct from {@link getEffectiveDocumentId}, which resolves the canonical key a
|
|
11
|
+
* document's state is stored under and so collapses a draft onto its published id.
|
|
12
|
+
* This one keeps them apart, because some things compare the exact id. Presence is
|
|
13
|
+
* the motivating case: the Studio's field indicators match the id its form is on,
|
|
14
|
+
* exactly, so an app reporting the published id shows up in the Studio at document
|
|
15
|
+
* level while never lighting up a single field.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Resolved from the handle alone, which is right whenever a draft or version
|
|
19
|
+
* exists, so for any document actually being edited. The Studio additionally falls
|
|
20
|
+
* back to whichever document exists, so for a published document nobody has edited
|
|
21
|
+
* yet its form sits on the published id where this returns the draft id. Matching
|
|
22
|
+
* that would mean reading document state, which presence deliberately does not
|
|
23
|
+
* depend on.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function getEditingDocumentId(
|
|
28
|
+
doc: Pick<DocumentHandle, 'documentId' | 'liveEdit' | 'perspective'>,
|
|
29
|
+
): string {
|
|
30
|
+
if (doc.liveEdit) {
|
|
31
|
+
return getPublishedId(DocumentId(doc.documentId))
|
|
32
|
+
}
|
|
33
|
+
if (isReleasePerspective(doc.perspective)) {
|
|
34
|
+
return getVersionId(DocumentId(doc.documentId), doc.perspective.releaseName)
|
|
35
|
+
}
|
|
36
|
+
if (doc.perspective === 'published') {
|
|
37
|
+
return getPublishedId(DocumentId(doc.documentId))
|
|
38
|
+
}
|
|
39
|
+
return getDraftId(DocumentId(doc.documentId))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getEffectiveDocumentId(doc: DocumentHandle): string {
|
|
43
|
+
if (doc.liveEdit) {
|
|
44
|
+
return doc.documentId
|
|
45
|
+
} else if (isReleasePerspective(doc.perspective)) {
|
|
46
|
+
return getVersionId(DocumentId(doc.documentId), doc.perspective.releaseName)
|
|
47
|
+
} else {
|
|
48
|
+
return getPublishedId(DocumentId(doc.documentId))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -39,7 +39,6 @@ describe('favoritesStore', () => {
|
|
|
39
39
|
resourceId: 'res456',
|
|
40
40
|
resourceType: 'studio' as const,
|
|
41
41
|
schemaName: 'movieSchema',
|
|
42
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
const mockContextNoSchema = {
|
|
@@ -47,13 +46,12 @@ describe('favoritesStore', () => {
|
|
|
47
46
|
documentType: 'movie',
|
|
48
47
|
resourceId: 'res456',
|
|
49
48
|
resourceType: 'studio' as const,
|
|
50
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
describe('createFavoriteKey', () => {
|
|
54
52
|
beforeEach(() => {
|
|
55
53
|
vi.resetAllMocks()
|
|
56
|
-
instance = createSanityInstance()
|
|
54
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
57
55
|
setupMockStateSource()
|
|
58
56
|
})
|
|
59
57
|
|
|
@@ -101,7 +99,7 @@ describe('favoritesStore', () => {
|
|
|
101
99
|
describe('fetcher', () => {
|
|
102
100
|
beforeEach(() => {
|
|
103
101
|
vi.resetAllMocks()
|
|
104
|
-
instance = createSanityInstance()
|
|
102
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
105
103
|
setupMockStateSource()
|
|
106
104
|
})
|
|
107
105
|
|
|
@@ -148,8 +146,9 @@ describe('favoritesStore', () => {
|
|
|
148
146
|
await firstValueFrom(state.observable)
|
|
149
147
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
150
148
|
// Second subscriber should use cached response
|
|
151
|
-
const
|
|
152
|
-
|
|
149
|
+
const state2 = getFavoritesState(instance!, mockContext)
|
|
150
|
+
const sub2 = state2.subscribe()
|
|
151
|
+
await firstValueFrom(state2.observable)
|
|
153
152
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
154
153
|
// Cleanup
|
|
155
154
|
sub1()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {expectTypeOf, test} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
4
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
5
|
+
import {type FetcherSnapshot} from '../store/fetcherStore'
|
|
6
|
+
import {
|
|
7
|
+
type Installation,
|
|
8
|
+
installation,
|
|
9
|
+
type InstallationAccess,
|
|
10
|
+
type InstallationActiveConfig,
|
|
11
|
+
type InstallationBase,
|
|
12
|
+
type InstallationInclude,
|
|
13
|
+
type InstallationInterface,
|
|
14
|
+
installations,
|
|
15
|
+
type InstallationsResponse,
|
|
16
|
+
} from './installations'
|
|
17
|
+
|
|
18
|
+
const instance = {} as SanityInstance
|
|
19
|
+
|
|
20
|
+
type IncludeKeys = 'activeConfig' | 'access' | 'interfaces'
|
|
21
|
+
|
|
22
|
+
// Whether key `K` of `T` is optional, without an `{}`-based check.
|
|
23
|
+
type KeyModifier<T, K extends keyof T> =
|
|
24
|
+
Pick<T, K> extends Required<Pick<T, K>> ? 'required' : 'optional'
|
|
25
|
+
|
|
26
|
+
test('Installation — no includes: only the base shape', () => {
|
|
27
|
+
expectTypeOf<Installation>().toEqualTypeOf<InstallationBase>()
|
|
28
|
+
expectTypeOf<Installation<never>>().toEqualTypeOf<InstallationBase>()
|
|
29
|
+
expectTypeOf<Extract<keyof Installation, IncludeKeys>>().toEqualTypeOf<never>()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('InstallationBase — the application sub-object carries name and reference', () => {
|
|
33
|
+
expectTypeOf<InstallationBase['application']['name']>().toEqualTypeOf<string>()
|
|
34
|
+
expectTypeOf<InstallationBase['application']['reference']>().toEqualTypeOf<string>()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('Installation — each token adds its top-level field, required, others absent', () => {
|
|
38
|
+
expectTypeOf<Installation<'access'>['access']>().toEqualTypeOf<InstallationAccess[]>()
|
|
39
|
+
expectTypeOf<
|
|
40
|
+
Extract<keyof Installation<'access'>, 'activeConfig' | 'interfaces'>
|
|
41
|
+
>().toEqualTypeOf<never>()
|
|
42
|
+
|
|
43
|
+
expectTypeOf<Installation<'interfaces'>['interfaces']>().toEqualTypeOf<InstallationInterface[]>()
|
|
44
|
+
expectTypeOf<
|
|
45
|
+
Installation<'activeConfig'>['activeConfig']
|
|
46
|
+
>().toEqualTypeOf<InstallationActiveConfig | null>()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('Installation — multiple tokens add each field', () => {
|
|
50
|
+
type Both = Installation<'access' | 'interfaces'>
|
|
51
|
+
expectTypeOf<Both['access']>().toEqualTypeOf<InstallationAccess[]>()
|
|
52
|
+
expectTypeOf<Both['interfaces']>().toEqualTypeOf<InstallationInterface[]>()
|
|
53
|
+
expectTypeOf<Extract<keyof Both, 'activeConfig'>>().toEqualTypeOf<never>()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('Installation — a wide include array makes the fields optional', () => {
|
|
57
|
+
type Wide = Installation<InstallationInclude>
|
|
58
|
+
expectTypeOf<KeyModifier<Wide, 'access'>>().toEqualTypeOf<'optional'>()
|
|
59
|
+
expectTypeOf<KeyModifier<Wide, 'interfaces'>>().toEqualTypeOf<'optional'>()
|
|
60
|
+
expectTypeOf<KeyModifier<Wide, 'activeConfig'>>().toEqualTypeOf<'optional'>()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('installations.resolveState — resolves the wide list envelope', () => {
|
|
64
|
+
expectTypeOf(
|
|
65
|
+
installations.resolveState(instance, {organizationId: 'org_1'}),
|
|
66
|
+
).resolves.toEqualTypeOf<InstallationsResponse<InstallationInclude>>()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('installation.getState — exposes the snapshot envelope', () => {
|
|
70
|
+
expectTypeOf(installation.getState(instance, 'inst_1')).toEqualTypeOf<
|
|
71
|
+
StateSource<FetcherSnapshot<Installation<InstallationInclude>>>
|
|
72
|
+
>()
|
|
73
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {type SanityClient} from '@sanity/client'
|
|
2
|
+
import {of} from 'rxjs'
|
|
3
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {getClientState} from '../client/clientStore'
|
|
6
|
+
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
8
|
+
import {installation, installations} from './installations'
|
|
9
|
+
|
|
10
|
+
vi.mock('../client/clientStore')
|
|
11
|
+
|
|
12
|
+
describe('installations', () => {
|
|
13
|
+
let instance: SanityInstance
|
|
14
|
+
const request = vi.fn()
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
18
|
+
request.mockReset()
|
|
19
|
+
vi.mocked(getClientState).mockReturnValue({
|
|
20
|
+
observable: of({observable: {request}} as unknown as SanityClient),
|
|
21
|
+
} as StateSource<SanityClient>)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
instance.dispose()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('lists installations for an organization on the global client', async () => {
|
|
29
|
+
const response = {nextCursor: null, data: [{id: 'inst1'}]}
|
|
30
|
+
request.mockReturnValue(of(response))
|
|
31
|
+
|
|
32
|
+
const result = await installations.resolveState(instance, {
|
|
33
|
+
organizationId: 'org1',
|
|
34
|
+
include: ['interfaces'],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(result).toEqual(response)
|
|
38
|
+
expect(getClientState).toHaveBeenCalledWith(instance, {
|
|
39
|
+
apiVersion: 'vX',
|
|
40
|
+
scope: 'global',
|
|
41
|
+
})
|
|
42
|
+
expect(request).toHaveBeenCalledWith({
|
|
43
|
+
uri: '/installations',
|
|
44
|
+
query: {organizationId: 'org1', include: 'interfaces'},
|
|
45
|
+
tag: 'installations.list',
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('fetches a single installation by id', async () => {
|
|
50
|
+
const inst = {id: 'inst1', applicationId: 'app1'}
|
|
51
|
+
request.mockReturnValue(of(inst))
|
|
52
|
+
|
|
53
|
+
const result = await installation.resolveState(instance, 'inst1')
|
|
54
|
+
|
|
55
|
+
expect(result).toEqual(inst)
|
|
56
|
+
expect(request).toHaveBeenCalledWith({
|
|
57
|
+
uri: '/installations/inst1',
|
|
58
|
+
query: {},
|
|
59
|
+
tag: 'installations.get',
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
})
|