@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
package/src/project/project.ts
CHANGED
|
@@ -1,35 +1,148 @@
|
|
|
1
1
|
import {switchMap} from 'rxjs'
|
|
2
2
|
|
|
3
3
|
import {getClientState} from '../client/clientStore'
|
|
4
|
-
import {
|
|
4
|
+
import {type ProjectHandle} from '../config/sanityConfig'
|
|
5
|
+
import {projects} from '../projects/projects'
|
|
6
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {defineFetcher} from '../store/fetcherStore'
|
|
8
|
+
import {buildQuery} from '../utils/buildQuery'
|
|
5
9
|
|
|
6
10
|
const API_VERSION = 'v2025-02-19'
|
|
7
11
|
|
|
8
12
|
/** @public */
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
getKey: (_instance, options: ProjectHandle) => {
|
|
15
|
-
const projectId = options?.projectId
|
|
16
|
-
if (!projectId) {
|
|
17
|
-
throw new Error('A projectId is required to use the project API.')
|
|
18
|
-
}
|
|
19
|
-
return projectId
|
|
20
|
-
},
|
|
21
|
-
fetcher: (instance) => (options: ProjectHandle) => {
|
|
22
|
-
const projectId = options.projectId
|
|
23
|
-
|
|
24
|
-
return getClientState(instance, {
|
|
25
|
-
apiVersion: API_VERSION,
|
|
26
|
-
scope: 'global',
|
|
27
|
-
projectId,
|
|
28
|
-
}).observable.pipe(switchMap((client) => client.observable.projects.getById(projectId!)))
|
|
29
|
-
},
|
|
30
|
-
})
|
|
13
|
+
export interface ProjectMemberRole {
|
|
14
|
+
name: string
|
|
15
|
+
title: string
|
|
16
|
+
description: string
|
|
17
|
+
}
|
|
31
18
|
|
|
32
19
|
/** @public */
|
|
33
|
-
export
|
|
20
|
+
export interface ProjectMember {
|
|
21
|
+
id: string
|
|
22
|
+
createdAt: string
|
|
23
|
+
updatedAt: string
|
|
24
|
+
isCurrentUser: boolean
|
|
25
|
+
isRobot: boolean
|
|
26
|
+
roles: ProjectMemberRole[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @public */
|
|
30
|
+
export interface ProjectMetadata {
|
|
31
|
+
color?: string
|
|
32
|
+
externalStudioHost?: string
|
|
33
|
+
initialTemplate?: string
|
|
34
|
+
cliInitializedAt?: string
|
|
35
|
+
integration: 'manage' | 'cli'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The base fields returned from `/projects` for every project.
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export interface ProjectBase {
|
|
43
|
+
id: string
|
|
44
|
+
displayName: string
|
|
45
|
+
studioHost: string | null
|
|
46
|
+
organizationId: string
|
|
47
|
+
metadata: ProjectMetadata
|
|
48
|
+
isBlocked: boolean
|
|
49
|
+
isDisabled: boolean
|
|
50
|
+
isDisabledByUser: boolean
|
|
51
|
+
activityFeedEnabled: boolean
|
|
52
|
+
createdAt: string
|
|
53
|
+
updatedAt: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A `Project` with `members` and/or `features` conditionally included
|
|
58
|
+
* based on the query options used to fetch it.
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export type Project<
|
|
62
|
+
IncludeMembers extends boolean = true,
|
|
63
|
+
IncludeFeatures extends boolean = true,
|
|
64
|
+
> = ProjectBase &
|
|
65
|
+
// `boolean extends T` is non-distributive — true only when T is the wide
|
|
66
|
+
// `boolean`, in which case the field is optional. Literal `true`/`false`
|
|
67
|
+
// fall through to the strict branch.
|
|
68
|
+
(boolean extends IncludeMembers
|
|
69
|
+
? {members?: ProjectMember[]}
|
|
70
|
+
: IncludeMembers extends true
|
|
71
|
+
? {members: ProjectMember[]}
|
|
72
|
+
: unknown) &
|
|
73
|
+
(boolean extends IncludeFeatures
|
|
74
|
+
? {features?: string[]}
|
|
75
|
+
: IncludeFeatures extends true
|
|
76
|
+
? {features: string[]}
|
|
77
|
+
: unknown)
|
|
78
|
+
|
|
34
79
|
/** @public */
|
|
35
|
-
export
|
|
80
|
+
export interface ProjectOptions<
|
|
81
|
+
IncludeMembers extends boolean = true,
|
|
82
|
+
IncludeFeatures extends boolean = true,
|
|
83
|
+
> extends ProjectHandle {
|
|
84
|
+
includeMembers?: IncludeMembers
|
|
85
|
+
includeFeatures?: IncludeFeatures
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeProjectOptions(options?: ProjectOptions<boolean, boolean>) {
|
|
89
|
+
return {
|
|
90
|
+
includeMembers: options?.includeMembers ?? true,
|
|
91
|
+
includeFeatures: options?.includeFeatures ?? true,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveProjectId(instance: SanityInstance, options?: ProjectOptions<boolean, boolean>) {
|
|
96
|
+
const projectId = options?.projectId ?? instance.config.projectId
|
|
97
|
+
if (!projectId) {
|
|
98
|
+
throw new Error('A projectId is required to use the project API.')
|
|
99
|
+
}
|
|
100
|
+
return projectId
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @internal */
|
|
104
|
+
export function getProjectCacheKey(
|
|
105
|
+
instance: SanityInstance,
|
|
106
|
+
options?: ProjectOptions<boolean, boolean>,
|
|
107
|
+
): string {
|
|
108
|
+
const projectId = resolveProjectId(instance, options)
|
|
109
|
+
const {includeMembers, includeFeatures} = normalizeProjectOptions(options)
|
|
110
|
+
const membersKey = includeMembers ? ':members' : ''
|
|
111
|
+
const featuresKey = includeFeatures ? ':features' : ''
|
|
112
|
+
return `project:${projectId}${membersKey}${featuresKey}`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Fetcher for a single project (`GET /projects/:id`), on the shared fetcher
|
|
117
|
+
* cache. Pre-seeds from the default {@link projects} list entry when it can
|
|
118
|
+
* satisfy the read — the default list carries features but not members, so
|
|
119
|
+
* only `includeMembers: false` reads seed.
|
|
120
|
+
*
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
123
|
+
export const project = defineFetcher<
|
|
124
|
+
[options?: ProjectOptions<boolean, boolean>],
|
|
125
|
+
Project<boolean, boolean>
|
|
126
|
+
>({
|
|
127
|
+
name: 'project',
|
|
128
|
+
getKey: getProjectCacheKey,
|
|
129
|
+
fetch: (instance) => (options) =>
|
|
130
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
131
|
+
switchMap((client) =>
|
|
132
|
+
client.observable.request<Project<boolean, boolean>>({
|
|
133
|
+
uri: `/projects/${resolveProjectId(instance, options)}`,
|
|
134
|
+
query: buildQuery(normalizeProjectOptions(options)),
|
|
135
|
+
tag: 'projects.get',
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
tags: (data) => [{type: 'project', id: data.id}],
|
|
140
|
+
initialData: (instance, options) => {
|
|
141
|
+
if (normalizeProjectOptions(options).includeMembers) return undefined
|
|
142
|
+
const snapshot = projects.getState(instance).getCurrent()
|
|
143
|
+
if (snapshot.status !== 'success') return undefined
|
|
144
|
+
const projectId = resolveProjectId(instance, options)
|
|
145
|
+
const match = snapshot.data.find((item) => item.id === projectId)
|
|
146
|
+
return match && {data: match, dataUpdatedAt: snapshot.dataUpdatedAt}
|
|
147
|
+
},
|
|
148
|
+
})
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {NEVER} from 'rxjs'
|
|
2
2
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {type DocumentResource} from '../config/sanityConfig'
|
|
5
4
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
6
5
|
import {type StoreState} from '../store/createStoreState'
|
|
7
6
|
import {hashString} from '../utils/hashString'
|
|
@@ -31,8 +30,7 @@ vi.mock('./subscribeToStateAndFetchBatches.ts')
|
|
|
31
30
|
|
|
32
31
|
describe('getProjectionState', () => {
|
|
33
32
|
let instance: SanityInstance
|
|
34
|
-
const
|
|
35
|
-
const docHandle = {documentId: 'exampleId', documentType: 'exampleType', resource}
|
|
33
|
+
const docHandle = {documentId: 'exampleId', documentType: 'exampleType'}
|
|
36
34
|
const projection1 = '{exampleProjection1}'
|
|
37
35
|
const hash1 = hashString(projection1)
|
|
38
36
|
const projection2 = '{exampleProjection2}'
|
|
@@ -50,7 +48,7 @@ describe('getProjectionState', () => {
|
|
|
50
48
|
return NEVER.subscribe()
|
|
51
49
|
})
|
|
52
50
|
|
|
53
|
-
instance = createSanityInstance()
|
|
51
|
+
instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
|
|
54
52
|
vi.useFakeTimers() // Enable fake timers for each test
|
|
55
53
|
})
|
|
56
54
|
|
|
@@ -128,7 +126,7 @@ describe('getProjectionState', () => {
|
|
|
128
126
|
expect(state.get().subscriptions).toEqual({})
|
|
129
127
|
expect(state.get().documentProjections).toEqual({})
|
|
130
128
|
|
|
131
|
-
const unsubscribe1 = projectionState1.subscribe(vi.fn()) // Should use ID
|
|
129
|
+
const unsubscribe1 = projectionState1.subscribe(vi.fn()) // Should use ID 2
|
|
132
130
|
expect(state.get().subscriptions).toEqual({
|
|
133
131
|
[docHandle.documentId]: {[hash1]: {testSubId_2: true}},
|
|
134
132
|
})
|
|
@@ -136,7 +134,7 @@ describe('getProjectionState', () => {
|
|
|
136
134
|
[docHandle.documentId]: {[hash1]: projection1},
|
|
137
135
|
})
|
|
138
136
|
|
|
139
|
-
const unsubscribe2 = projectionState2.subscribe(vi.fn()) // Should use ID
|
|
137
|
+
const unsubscribe2 = projectionState2.subscribe(vi.fn()) // Should use ID 3
|
|
140
138
|
expect(state.get().subscriptions).toEqual({
|
|
141
139
|
[docHandle.documentId]: {
|
|
142
140
|
[hash1]: {testSubId_2: true},
|
|
@@ -150,10 +148,12 @@ describe('getProjectionState', () => {
|
|
|
150
148
|
},
|
|
151
149
|
})
|
|
152
150
|
|
|
153
|
-
|
|
151
|
+
// should share ID 2 from the first subscription -- we only care if ANY subscription exists
|
|
152
|
+
// so we can include the document / projection in a bulk fetch
|
|
153
|
+
const unsubscribe3 = projectionState1.subscribe(vi.fn())
|
|
154
154
|
expect(state.get().subscriptions).toEqual({
|
|
155
155
|
[docHandle.documentId]: {
|
|
156
|
-
[hash1]: {testSubId_2: true
|
|
156
|
+
[hash1]: {testSubId_2: true},
|
|
157
157
|
[hash2]: {testSubId_3: true},
|
|
158
158
|
},
|
|
159
159
|
})
|
|
@@ -167,23 +167,20 @@ describe('getProjectionState', () => {
|
|
|
167
167
|
})
|
|
168
168
|
|
|
169
169
|
// --- Test Unsubscribe ---
|
|
170
|
-
unsubscribe1() // Unsubscribes
|
|
171
|
-
expect(state.get().subscriptions[docHandle.documentId]?.[hash1]).toEqual({
|
|
172
|
-
testSubId_2: true,
|
|
173
|
-
testSubId_4: true,
|
|
174
|
-
})
|
|
170
|
+
unsubscribe1() // Unsubscribes first subscription consumer
|
|
171
|
+
expect(state.get().subscriptions[docHandle.documentId]?.[hash1]).toEqual({testSubId_2: true})
|
|
175
172
|
vi.advanceTimersByTime(PROJECTION_STATE_CLEAR_DELAY)
|
|
176
|
-
expect(state.get().subscriptions[docHandle.documentId]?.[hash1]).toEqual({
|
|
173
|
+
expect(state.get().subscriptions[docHandle.documentId]?.[hash1]).toEqual({testSubId_2: true})
|
|
177
174
|
expect(state.get().documentProjections[docHandle.documentId]?.[hash1]).toEqual(projection1)
|
|
178
175
|
|
|
179
|
-
unsubscribe3() //
|
|
176
|
+
unsubscribe3() // Also unsubscibes first projection subscription, should be removed
|
|
180
177
|
vi.advanceTimersByTime(PROJECTION_STATE_CLEAR_DELAY)
|
|
181
178
|
expect(state.get().subscriptions[docHandle.documentId]?.[hash1]).toBeUndefined()
|
|
182
179
|
expect(state.get().documentProjections[docHandle.documentId]?.[hash1]).toBeUndefined()
|
|
183
180
|
expect(state.get().subscriptions[docHandle.documentId]?.[hash2]).toEqual({testSubId_3: true})
|
|
184
181
|
expect(state.get().documentProjections[docHandle.documentId]?.[hash2]).toEqual(projection2)
|
|
185
182
|
|
|
186
|
-
unsubscribe2() // Unsubscribes
|
|
183
|
+
unsubscribe2() // Unsubscribes second projection subscription, should be removed
|
|
187
184
|
vi.advanceTimersByTime(PROJECTION_STATE_CLEAR_DELAY)
|
|
188
185
|
expect(state.get().subscriptions[docHandle.documentId]).toBeUndefined()
|
|
189
186
|
expect(state.get().documentProjections[docHandle.documentId]).toBeUndefined()
|
|
@@ -205,14 +202,14 @@ describe('getProjectionState', () => {
|
|
|
205
202
|
}))
|
|
206
203
|
|
|
207
204
|
const unsubscribe1 = projectionState.subscribe(vi.fn()) // Should use ID 2
|
|
208
|
-
const unsubscribe2 = projectionState.subscribe(vi.fn()) // Should
|
|
205
|
+
const unsubscribe2 = projectionState.subscribe(vi.fn()) // Should reuse ID 2
|
|
209
206
|
|
|
210
207
|
expect(state.get().values[docHandle.documentId]?.[hash]).toEqual({
|
|
211
208
|
data: initialData,
|
|
212
209
|
isPending: true,
|
|
213
210
|
})
|
|
214
211
|
|
|
215
|
-
unsubscribe1() // Unsubscribes
|
|
212
|
+
unsubscribe1() // Unsubscribes first subscription consumer
|
|
216
213
|
vi.advanceTimersByTime(PROJECTION_STATE_CLEAR_DELAY)
|
|
217
214
|
expect(state.get().values[docHandle.documentId]?.[hash]).toEqual({
|
|
218
215
|
data: initialData,
|
|
@@ -221,9 +218,10 @@ describe('getProjectionState', () => {
|
|
|
221
218
|
expect(Object.keys(state.get().subscriptions[docHandle.documentId]?.[hash] ?? {}).length).toBe(
|
|
222
219
|
1,
|
|
223
220
|
)
|
|
224
|
-
|
|
221
|
+
// should still have the same subscription ID since we're not unsubscribing the second consumer
|
|
222
|
+
expect(state.get().subscriptions[docHandle.documentId]?.[hash]).toEqual({testSubId_2: true})
|
|
225
223
|
|
|
226
|
-
unsubscribe2() // Unsubscribes
|
|
224
|
+
unsubscribe2() // Unsubscribes second subscription consumer
|
|
227
225
|
expect(state.get().values[docHandle.documentId]?.[hash]).toEqual({
|
|
228
226
|
data: initialData,
|
|
229
227
|
isPending: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {DocumentId, getPublishedId} from '@sanity/id-utils'
|
|
2
|
-
import {
|
|
2
|
+
import {type SanityProjectionResult} from 'groq'
|
|
3
3
|
|
|
4
4
|
import {type DocumentHandle} from '../config/sanityConfig'
|
|
5
5
|
import {bindActionByResourceAndPerspective} from '../store/createActionBinder'
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
} from '../store/createStateSourceAction'
|
|
12
12
|
import {hashString} from '../utils/hashString'
|
|
13
13
|
import {insecureRandomId} from '../utils/ids'
|
|
14
|
+
import {omitProperty} from '../utils/object'
|
|
15
|
+
import {setCleanupTimeout} from '../utils/setCleanupTimeout'
|
|
14
16
|
import {projectionStore} from './projectionStore'
|
|
15
17
|
import {type ProjectionStoreState, type ProjectionValuePending} from './types'
|
|
16
18
|
import {PROJECTION_STATE_CLEAR_DELAY, STABLE_EMPTY_PROJECTION, validateProjection} from './util'
|
|
@@ -24,6 +26,24 @@ export interface ProjectionOptions<
|
|
|
24
26
|
projection: TProjection
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* @beta
|
|
31
|
+
*/
|
|
32
|
+
export function getProjectionState<
|
|
33
|
+
TProjection extends string = string,
|
|
34
|
+
TDocumentType extends string = string,
|
|
35
|
+
TDataset extends string = string,
|
|
36
|
+
TProjectId extends string = string,
|
|
37
|
+
>(
|
|
38
|
+
instance: SanityInstance,
|
|
39
|
+
options: ProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
|
|
40
|
+
): StateSource<
|
|
41
|
+
| ProjectionValuePending<
|
|
42
|
+
SanityProjectionResult<TProjection, TDocumentType, `${TProjectId}.${TDataset}`>
|
|
43
|
+
>
|
|
44
|
+
| undefined
|
|
45
|
+
>
|
|
46
|
+
|
|
27
47
|
/**
|
|
28
48
|
* @beta
|
|
29
49
|
*/
|
|
@@ -91,9 +111,9 @@ export const _getProjectionState = bindActionByResourceAndPerspective(
|
|
|
91
111
|
}))
|
|
92
112
|
|
|
93
113
|
return () => {
|
|
94
|
-
|
|
114
|
+
setCleanupTimeout(() => {
|
|
95
115
|
state.set('removeSubscription', (prev): Partial<ProjectionStoreState> => {
|
|
96
|
-
const documentSubscriptionsForHash =
|
|
116
|
+
const documentSubscriptionsForHash = omitProperty(
|
|
97
117
|
prev.subscriptions[documentId]?.[projectionHash],
|
|
98
118
|
subscriptionId,
|
|
99
119
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {type ClientPerspective} from '@sanity/client'
|
|
2
|
+
import {DocumentId, getPublishedId} from '@sanity/id-utils'
|
|
2
3
|
|
|
3
|
-
import {type
|
|
4
|
-
import {getPublishedId} from '../utils/ids'
|
|
4
|
+
import {type ReleasePerspective} from '../config/sanityConfig'
|
|
5
5
|
import {
|
|
6
6
|
type DocumentProjections,
|
|
7
7
|
type DocumentProjectionValues,
|
|
@@ -67,7 +67,7 @@ interface ProcessProjectionQueryOptions {
|
|
|
67
67
|
ids: Set<string>
|
|
68
68
|
results: ProjectionQueryResult[]
|
|
69
69
|
documentStatuses?: ProjectionStoreState['documentStatuses']
|
|
70
|
-
perspective:
|
|
70
|
+
perspective: ClientPerspective | ReleasePerspective
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export function processProjectionQuery({
|
|
@@ -84,7 +84,7 @@ export function processProjectionQuery({
|
|
|
84
84
|
} = {}
|
|
85
85
|
|
|
86
86
|
for (const result of results) {
|
|
87
|
-
const originalId = getPublishedId(result._id)
|
|
87
|
+
const originalId = getPublishedId(DocumentId(result._id))
|
|
88
88
|
const hash = result.__projectionHash
|
|
89
89
|
|
|
90
90
|
if (!ids.has(originalId)) continue
|
|
@@ -6,14 +6,6 @@ import {createStoreInstance} from '../store/createStoreInstance'
|
|
|
6
6
|
import {projectionStore} from './projectionStore'
|
|
7
7
|
import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
|
|
8
8
|
|
|
9
|
-
// Mock the module with a factory function
|
|
10
|
-
vi.mock('../common/createLiveEventSubscriber', () => {
|
|
11
|
-
const mockLiveSubscriber = vi.fn()
|
|
12
|
-
return {
|
|
13
|
-
createLiveEventSubscriber: vi.fn(() => mockLiveSubscriber),
|
|
14
|
-
}
|
|
15
|
-
})
|
|
16
|
-
|
|
17
9
|
vi.mock('./subscribeToStateAndFetchBatches')
|
|
18
10
|
|
|
19
11
|
describe('projectionStore', () => {
|
|
@@ -24,7 +16,7 @@ describe('projectionStore', () => {
|
|
|
24
16
|
new Observable(subscriber).subscribe(),
|
|
25
17
|
)
|
|
26
18
|
|
|
27
|
-
const instance = createSanityInstance()
|
|
19
|
+
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
28
20
|
|
|
29
21
|
const {state, dispose} = createStoreInstance(
|
|
30
22
|
instance,
|
|
@@ -23,7 +23,7 @@ describe('resolveProjection', () => {
|
|
|
23
23
|
} as ProjectionValuePending<Record<string, unknown>>),
|
|
24
24
|
} as StateSource<ProjectionValuePending<Record<string, unknown>>>)
|
|
25
25
|
|
|
26
|
-
instance = createSanityInstance()
|
|
26
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
afterEach(() => {
|
|
@@ -34,7 +34,6 @@ describe('resolveProjection', () => {
|
|
|
34
34
|
const docHandle = createDocumentHandle({
|
|
35
35
|
documentId: 'doc123',
|
|
36
36
|
documentType: 'movie',
|
|
37
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
38
37
|
})
|
|
39
38
|
const projection = '{title}'
|
|
40
39
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {type SanityProjectionResult} from 'groq'
|
|
1
2
|
import {filter, firstValueFrom} from 'rxjs'
|
|
2
3
|
|
|
3
4
|
import {bindActionByResourceAndPerspective} from '../store/createActionBinder'
|
|
@@ -6,6 +7,21 @@ import {getProjectionState, type ProjectionOptions} from './getProjectionState'
|
|
|
6
7
|
import {projectionStore} from './projectionStore'
|
|
7
8
|
import {type ProjectionValuePending} from './types'
|
|
8
9
|
|
|
10
|
+
/** @beta */
|
|
11
|
+
export function resolveProjection<
|
|
12
|
+
TProjection extends string = string,
|
|
13
|
+
TDocumentType extends string = string,
|
|
14
|
+
TDataset extends string = string,
|
|
15
|
+
TProjectId extends string = string,
|
|
16
|
+
>(
|
|
17
|
+
instance: SanityInstance,
|
|
18
|
+
options: ProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
|
|
19
|
+
): Promise<
|
|
20
|
+
ProjectionValuePending<
|
|
21
|
+
SanityProjectionResult<TProjection, TDocumentType, `${TProjectId}.${TDataset}`>
|
|
22
|
+
>
|
|
23
|
+
>
|
|
24
|
+
|
|
9
25
|
/** @beta */
|
|
10
26
|
export function resolveProjection<TData extends object>(
|
|
11
27
|
instance: SanityInstance,
|
|
@@ -23,7 +23,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
23
23
|
|
|
24
24
|
beforeEach(() => {
|
|
25
25
|
vi.clearAllMocks()
|
|
26
|
-
instance = createSanityInstance()
|
|
26
|
+
instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
27
27
|
state = createStoreState<ProjectionStoreState>({
|
|
28
28
|
documentProjections: {},
|
|
29
29
|
documentStatuses: {},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {DocumentId} from '@sanity/id-utils'
|
|
2
2
|
import {
|
|
3
3
|
combineLatest,
|
|
4
4
|
debounceTime,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import {getQueryState, resolveQuery} from '../query/queryStore'
|
|
20
20
|
import {type BoundPerspectiveKey} from '../store/createActionBinder'
|
|
21
21
|
import {type StoreContext} from '../store/defineStore'
|
|
22
|
+
import {isDeepEqual} from '../utils/object'
|
|
22
23
|
import {
|
|
23
24
|
createProjectionQuery,
|
|
24
25
|
processProjectionQuery,
|
|
@@ -45,18 +46,18 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
45
46
|
}: StoreContext<ProjectionStoreState, BoundPerspectiveKey>): Subscription => {
|
|
46
47
|
const documentProjections$ = state.observable.pipe(
|
|
47
48
|
map((s) => s.documentProjections),
|
|
48
|
-
distinctUntilChanged(
|
|
49
|
+
distinctUntilChanged(isDeepEqual),
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
const activeDocumentIds$ = state.observable.pipe(
|
|
52
|
-
map(({subscriptions}) => new Set(Object.keys(subscriptions))),
|
|
53
|
+
map(({subscriptions}) => new Set(Object.keys(subscriptions).map((id) => DocumentId(id)))),
|
|
53
54
|
distinctUntilChanged(isSetEqual),
|
|
54
55
|
)
|
|
55
56
|
|
|
56
57
|
const pendingUpdateSubscription = activeDocumentIds$
|
|
57
58
|
.pipe(
|
|
58
59
|
debounceTime(BATCH_DEBOUNCE_TIME),
|
|
59
|
-
startWith(new Set<
|
|
60
|
+
startWith(new Set<DocumentId>()),
|
|
60
61
|
pairwise(),
|
|
61
62
|
tap(([prevIds, currIds]) => {
|
|
62
63
|
const newIds = [...currIds].filter((id) => !prevIds.has(id))
|
|
@@ -88,7 +89,7 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
88
89
|
|
|
89
90
|
const queryTrigger$ = combineLatest([activeDocumentIds$, documentProjections$]).pipe(
|
|
90
91
|
debounceTime(BATCH_DEBOUNCE_TIME),
|
|
91
|
-
distinctUntilChanged(
|
|
92
|
+
distinctUntilChanged(isDeepEqual),
|
|
92
93
|
)
|
|
93
94
|
|
|
94
95
|
const queryExecutionSubscription = queryTrigger$
|
|
@@ -105,10 +106,12 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
105
106
|
|
|
106
107
|
const projectionQuery$ = new Observable<ProjectionQueryResult[]>((observer) => {
|
|
107
108
|
const {getCurrent, observable} = getQueryState<ProjectionQueryResult[]>(instance, {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
...{
|
|
110
|
+
query,
|
|
111
|
+
params,
|
|
112
|
+
tag: PROJECTION_TAG,
|
|
113
|
+
perspective,
|
|
114
|
+
},
|
|
112
115
|
resource,
|
|
113
116
|
})
|
|
114
117
|
|
|
@@ -116,11 +119,13 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
116
119
|
if (getCurrent() === undefined) {
|
|
117
120
|
return from(
|
|
118
121
|
resolveQuery<ProjectionQueryResult[]>(instance, {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
...{
|
|
123
|
+
query,
|
|
124
|
+
params,
|
|
125
|
+
tag: PROJECTION_TAG,
|
|
126
|
+
signal: controller.signal,
|
|
127
|
+
perspective,
|
|
128
|
+
},
|
|
124
129
|
resource,
|
|
125
130
|
}),
|
|
126
131
|
).pipe(switchMap(() => observable))
|
|
@@ -140,10 +145,12 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
140
145
|
|
|
141
146
|
const statusQuery$ = new Observable<StatusQueryResult[]>((observer) => {
|
|
142
147
|
const {getCurrent, observable} = getQueryState<StatusQueryResult[]>(instance, {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
...{
|
|
149
|
+
query: statusQuery,
|
|
150
|
+
params: statusParams,
|
|
151
|
+
tag: PROJECTION_TAG,
|
|
152
|
+
perspective: 'raw',
|
|
153
|
+
},
|
|
147
154
|
resource,
|
|
148
155
|
})
|
|
149
156
|
|
|
@@ -151,11 +158,13 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
151
158
|
if (getCurrent() === undefined) {
|
|
152
159
|
return from(
|
|
153
160
|
resolveQuery<StatusQueryResult[]>(instance, {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
...{
|
|
162
|
+
query: statusQuery,
|
|
163
|
+
params: statusParams,
|
|
164
|
+
tag: PROJECTION_TAG,
|
|
165
|
+
signal: controller.signal,
|
|
166
|
+
perspective: 'raw',
|
|
167
|
+
},
|
|
159
168
|
resource,
|
|
160
169
|
}),
|
|
161
170
|
).pipe(switchMap(() => observable))
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {expectTypeOf, test} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {type Project, type ProjectMember} from '../project/project'
|
|
4
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
5
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
6
|
+
import {type FetcherSnapshot} from '../store/fetcherStore'
|
|
7
|
+
import {projects} from './projects'
|
|
8
|
+
|
|
9
|
+
const instance = {} as SanityInstance
|
|
10
|
+
|
|
11
|
+
test('projects.resolveState — resolves the wide-boolean list shape', () => {
|
|
12
|
+
expectTypeOf(projects.resolveState(instance)).resolves.toEqualTypeOf<
|
|
13
|
+
Project<boolean, boolean>[]
|
|
14
|
+
>()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('Project<boolean, boolean> — members and features are optional', () => {
|
|
18
|
+
type Item = Project<boolean, boolean>
|
|
19
|
+
expectTypeOf<Item['members']>().toEqualTypeOf<ProjectMember[] | undefined>()
|
|
20
|
+
expectTypeOf<Item['features']>().toEqualTypeOf<string[] | undefined>()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('projects.getState — exposes the snapshot envelope', () => {
|
|
24
|
+
expectTypeOf(projects.getState(instance)).toEqualTypeOf<
|
|
25
|
+
StateSource<FetcherSnapshot<Project<boolean, boolean>[]>>
|
|
26
|
+
>()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('projects — rejects non-boolean flag values', () => {
|
|
30
|
+
// @ts-expect-error includeMembers must be a boolean
|
|
31
|
+
void projects.resolveState(instance, {includeMembers: 'yes'})
|
|
32
|
+
})
|