@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/presence/types.ts
CHANGED
|
@@ -1,27 +1,199 @@
|
|
|
1
1
|
import {type SanityClient} from '@sanity/client'
|
|
2
|
+
import {type Path} from '@sanity/types'
|
|
2
3
|
import {type Observable} from 'rxjs'
|
|
3
4
|
|
|
5
|
+
import {type PerspectiveHandle} from '../config/sanityConfig'
|
|
4
6
|
import {type SanityUser} from '../users/types'
|
|
5
7
|
|
|
6
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* One end of a text selection: a Portable Text path plus a character offset
|
|
10
|
+
* within the span it addresses.
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export interface PresenceSelectionPoint {
|
|
14
|
+
path: Path
|
|
15
|
+
offset: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A Portable Text caret or selected range, as exchanged with the Studio.
|
|
20
|
+
*
|
|
21
|
+
* Structurally identical to `EditorSelection` from `@portabletext/editor`, and
|
|
22
|
+
* declared separately so that `@sanity/sdk` stays free of React dependencies. A
|
|
23
|
+
* value from the editor can be passed straight through, and vice versa.
|
|
24
|
+
*
|
|
25
|
+
* `offset` is a character offset within the span that `path` addresses.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export type PresenceSelection = {
|
|
29
|
+
anchor: PresenceSelectionPoint
|
|
30
|
+
focus: PresenceSelectionPoint
|
|
31
|
+
backward?: boolean
|
|
32
|
+
} | null
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Somewhere a participant is, as it arrives over the wire.
|
|
36
|
+
*
|
|
37
|
+
* Prefer {@link DocumentPresence} from `getDocumentPresence`, which is scoped to one
|
|
38
|
+
* document, flattened for rendering, and types `path` accurately.
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
7
41
|
export interface PresenceLocation {
|
|
8
42
|
type: 'document'
|
|
43
|
+
/**
|
|
44
|
+
* The specific document the participant is in, so a draft, published, or version
|
|
45
|
+
* id rather than a bare document id.
|
|
46
|
+
*/
|
|
9
47
|
documentId: string
|
|
48
|
+
/**
|
|
49
|
+
* The focused field path, empty when they are in the document but not a field.
|
|
50
|
+
*
|
|
51
|
+
* Declared as `string[]`, but segments can also be numbers or `{_key: string}`
|
|
52
|
+
* objects when the focus is inside an array or a Portable Text field, because
|
|
53
|
+
* that is what other clients send. Guard for those rather than assuming strings.
|
|
54
|
+
* Widening the declared type is a breaking change, so it waits for the next major;
|
|
55
|
+
* {@link DocumentPresence.path} is typed correctly today.
|
|
56
|
+
*/
|
|
10
57
|
path: string[]
|
|
58
|
+
/**
|
|
59
|
+
* When the participant said they were last active, on *their* clock.
|
|
60
|
+
*
|
|
61
|
+
* Fine for display, unreliable for deciding whether someone is still here: it is
|
|
62
|
+
* subject to clock skew between machines. Presence already drops participants that
|
|
63
|
+
* stop announcing, so a session appearing here is one the SDK still considers
|
|
64
|
+
* live.
|
|
65
|
+
*/
|
|
11
66
|
lastActiveAt: string
|
|
67
|
+
/** A Portable Text caret, when the focused field is a Portable Text field. */
|
|
68
|
+
selection?: PresenceSelection
|
|
12
69
|
}
|
|
13
70
|
|
|
14
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* A location as it travels over the wire. Identical in shape to
|
|
73
|
+
* {@link PresenceLocation}, except `path` is a full `Path`, which is what is
|
|
74
|
+
* really exchanged. See the note on {@link PresenceLocation.path}.
|
|
75
|
+
*
|
|
76
|
+
* Internal: nothing in the public API accepts or returns this. Apps describe
|
|
77
|
+
* where they are with {@link ReportPresenceOptions} and read presence as
|
|
78
|
+
* {@link PresenceLocation}.
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
export interface WirePresenceLocation extends Omit<PresenceLocation, 'path'> {
|
|
82
|
+
path: Path
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Which specific document a presence id resolves to.
|
|
87
|
+
*
|
|
88
|
+
* Presence compares exact document ids, so a client in a draft and a client in the
|
|
89
|
+
* published document are in different places even for the same `documentId`. Pass
|
|
90
|
+
* the perspective you are editing under and the SDK resolves it; the React hooks
|
|
91
|
+
* take it from `ResourceProvider` when you do not pass one.
|
|
92
|
+
* @public
|
|
93
|
+
*/
|
|
94
|
+
export interface PresencePerspectiveOptions extends PerspectiveHandle {
|
|
95
|
+
/** Live-edit documents have no draft, so presence resolves to the published id. */
|
|
96
|
+
liveEdit?: boolean
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* What an app reports about where the current user is.
|
|
101
|
+
*
|
|
102
|
+
* Omit `path` for document-level presence, or pass one for field-level presence.
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
export interface ReportPresenceOptions extends PresencePerspectiveOptions {
|
|
106
|
+
/**
|
|
107
|
+
* The document the user is in.
|
|
108
|
+
*
|
|
109
|
+
* Pass the plain document id; the perspective decides which specific document that
|
|
110
|
+
* resolves to. An already-specific id is accepted and re-resolved, so passing
|
|
111
|
+
* `drafts.abc` while the perspective is `published` reports the published document.
|
|
112
|
+
*/
|
|
113
|
+
documentId: string
|
|
114
|
+
/**
|
|
115
|
+
* The focused field path. Supports keyed and numeric segments, so array items
|
|
116
|
+
* and Portable Text spans can be addressed.
|
|
117
|
+
*/
|
|
118
|
+
path?: Path
|
|
119
|
+
/** A Portable Text caret, when the focused field is a Portable Text field. */
|
|
120
|
+
selection?: PresenceSelection
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One participant's session and everywhere it currently is.
|
|
125
|
+
*
|
|
126
|
+
* Keyed by session, not by user: the same person in two tabs is two entries with
|
|
127
|
+
* the same `user`.
|
|
128
|
+
* @public
|
|
129
|
+
*/
|
|
15
130
|
export interface UserPresence {
|
|
16
131
|
user: SanityUser
|
|
17
132
|
locations: PresenceLocation[]
|
|
133
|
+
/** Identifies one tab. Stable while that tab is open, and not reused. */
|
|
134
|
+
sessionId: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* One participant at one location within a single document, flattened so it can
|
|
139
|
+
* be rendered directly against a field.
|
|
140
|
+
*
|
|
141
|
+
* Unlike {@link PresenceLocation.path}, `path` here is typed as a full `Path`, so
|
|
142
|
+
* keyed and numeric segments are described rather than cast away.
|
|
143
|
+
* @beta
|
|
144
|
+
*/
|
|
145
|
+
export interface DocumentPresence {
|
|
146
|
+
user: SanityUser
|
|
147
|
+
/** Identifies one tab, so the same person in two tabs appears twice. */
|
|
18
148
|
sessionId: string
|
|
149
|
+
/**
|
|
150
|
+
* The specific document id this participant is in: a draft, published, or
|
|
151
|
+
* version id, depending on which perspective they are editing.
|
|
152
|
+
*/
|
|
153
|
+
documentId: string
|
|
154
|
+
/** The focused field path. Empty when they are in the document but no field. */
|
|
155
|
+
path: Path
|
|
156
|
+
/**
|
|
157
|
+
* When they said they were last active, on *their* clock. Fine for display,
|
|
158
|
+
* unreliable for liveness. See {@link PresenceLocation.lastActiveAt}.
|
|
159
|
+
*/
|
|
160
|
+
lastActiveAt: string
|
|
161
|
+
/** The Portable Text caret, when they are focused in a Portable Text field. */
|
|
162
|
+
selection?: PresenceSelection
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** @beta */
|
|
166
|
+
export interface DocumentPresenceOptions extends PresencePerspectiveOptions {
|
|
167
|
+
/**
|
|
168
|
+
* The document to look at. Resolved through the perspective exactly as
|
|
169
|
+
* {@link ReportPresenceOptions.documentId} is, so reads match writes.
|
|
170
|
+
*/
|
|
171
|
+
documentId: string
|
|
172
|
+
/**
|
|
173
|
+
* Narrows to participants at or below this field path. Omit it for everyone in
|
|
174
|
+
* the document.
|
|
175
|
+
*/
|
|
176
|
+
path?: Path
|
|
177
|
+
/**
|
|
178
|
+
* By default a draft, its published version, and any release versions are
|
|
179
|
+
* treated as the same document, which is what document lists want. Set this to
|
|
180
|
+
* compare ids exactly, which is what field-level indicators want so that a
|
|
181
|
+
* draft and a release version do not bleed into each other.
|
|
182
|
+
*/
|
|
183
|
+
excludeVersions?: boolean
|
|
19
184
|
}
|
|
20
185
|
|
|
21
186
|
/** @public */
|
|
22
187
|
export type PresenceTransport = [
|
|
23
188
|
incomingEvents$: Observable<TransportEvent>,
|
|
24
189
|
dispatchMessage: (message: TransportMessage) => Observable<void>,
|
|
190
|
+
/**
|
|
191
|
+
* Emits an incrementing generation number each time the connection becomes
|
|
192
|
+
* live. Reconnects with backoff, and subscribing keeps the connection alive.
|
|
193
|
+
*/
|
|
194
|
+
connections$: Observable<number>,
|
|
195
|
+
/** Emits when the page is going away, so a disconnect can be announced. */
|
|
196
|
+
unload$: Observable<void>,
|
|
25
197
|
]
|
|
26
198
|
|
|
27
199
|
/** @public */
|
|
@@ -54,7 +226,7 @@ export interface DisconnectEvent {
|
|
|
54
226
|
/** @public */
|
|
55
227
|
export type TransportMessage =
|
|
56
228
|
| {type: 'rollCall'}
|
|
57
|
-
| {type: 'state'; locations:
|
|
229
|
+
| {type: 'state'; locations: WirePresenceLocation[]}
|
|
58
230
|
| {type: 'disconnect'}
|
|
59
231
|
|
|
60
232
|
/** @public */
|
|
@@ -58,7 +58,10 @@ describe('normalizeMedia', () => {
|
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
describe('transformProjectionToPreview', () => {
|
|
61
|
-
const instance = createSanityInstance(
|
|
61
|
+
const instance = createSanityInstance({
|
|
62
|
+
projectId: 'test-project',
|
|
63
|
+
dataset: 'test-dataset',
|
|
64
|
+
})
|
|
62
65
|
|
|
63
66
|
beforeEach(async () => {
|
|
64
67
|
vi.clearAllMocks()
|
|
@@ -78,11 +81,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
78
81
|
media: null,
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
const result = transformProjectionToPreview(
|
|
82
|
-
instance,
|
|
83
|
-
{projectId: 'p', dataset: 'd'},
|
|
84
|
-
projectionResult,
|
|
85
|
-
)
|
|
84
|
+
const result = transformProjectionToPreview(instance, projectionResult)
|
|
86
85
|
|
|
87
86
|
expect(result).toEqual({
|
|
88
87
|
title: 'My Title',
|
|
@@ -101,11 +100,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
101
100
|
media: null,
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
const result = transformProjectionToPreview(
|
|
105
|
-
instance,
|
|
106
|
-
{projectId: 'p', dataset: 'd'},
|
|
107
|
-
projectionResult,
|
|
108
|
-
)
|
|
103
|
+
const result = transformProjectionToPreview(instance, projectionResult)
|
|
109
104
|
|
|
110
105
|
expect(result.title).toBe('article: doc1')
|
|
111
106
|
expect(result.subtitle).toBeUndefined()
|
|
@@ -121,11 +116,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
121
116
|
media: {type: 'image-asset', _ref: 'image-abc123-200x200-png', url: ''},
|
|
122
117
|
}
|
|
123
118
|
|
|
124
|
-
const result = transformProjectionToPreview(
|
|
125
|
-
instance,
|
|
126
|
-
{projectId: 'p', dataset: 'd'},
|
|
127
|
-
projectionResult,
|
|
128
|
-
)
|
|
119
|
+
const result = transformProjectionToPreview(instance, projectionResult)
|
|
129
120
|
|
|
130
121
|
expect(result).toEqual({
|
|
131
122
|
title: 'My Title',
|
|
@@ -152,11 +143,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
152
143
|
},
|
|
153
144
|
}
|
|
154
145
|
|
|
155
|
-
const result = transformProjectionToPreview(
|
|
156
|
-
instance,
|
|
157
|
-
{projectId: 'p', dataset: 'd'},
|
|
158
|
-
projectionResult,
|
|
159
|
-
)
|
|
146
|
+
const result = transformProjectionToPreview(instance, projectionResult)
|
|
160
147
|
|
|
161
148
|
expect(result).toEqual({
|
|
162
149
|
title: 'My Title',
|
|
@@ -181,7 +168,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
181
168
|
|
|
182
169
|
const resource = {mediaLibraryId: 'test-library'}
|
|
183
170
|
|
|
184
|
-
transformProjectionToPreview(instance,
|
|
171
|
+
transformProjectionToPreview(instance, projectionResult, resource)
|
|
185
172
|
|
|
186
173
|
const {getClient} = await import('../client/clientStore')
|
|
187
174
|
expect(getClient).toHaveBeenCalledWith(instance, {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {type SanityClient} from '@sanity/client'
|
|
2
2
|
import {createImageUrlBuilder} from '@sanity/image-url'
|
|
3
|
-
import {isObject} from 'lodash-es'
|
|
4
3
|
|
|
5
4
|
import {getClient} from '../client/clientStore'
|
|
6
5
|
import {type DocumentResource} from '../config/sanityConfig'
|
|
7
6
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {isObject} from '../utils/object'
|
|
8
8
|
import {SUBTITLE_CANDIDATES, TITLE_CANDIDATES} from './previewConstants'
|
|
9
9
|
import {type PreviewQueryResult, type PreviewValue} from './types'
|
|
10
10
|
|
|
@@ -71,13 +71,17 @@ function findFirstDefined(
|
|
|
71
71
|
*/
|
|
72
72
|
export function transformProjectionToPreview(
|
|
73
73
|
instance: SanityInstance,
|
|
74
|
-
resource: DocumentResource,
|
|
75
74
|
projectionResult: PreviewQueryResult,
|
|
75
|
+
resource?: DocumentResource,
|
|
76
76
|
): PreviewValue {
|
|
77
77
|
const title = findFirstDefined(TITLE_CANDIDATES, projectionResult.titleCandidates)
|
|
78
78
|
const subtitle = findFirstDefined(SUBTITLE_CANDIDATES, projectionResult.subtitleCandidates, title)
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// Get a client for the resource (if provided) or use the instance config
|
|
81
|
+
const client = getClient(instance, {
|
|
82
|
+
apiVersion: API_VERSION,
|
|
83
|
+
resource,
|
|
84
|
+
})
|
|
81
85
|
|
|
82
86
|
return {
|
|
83
87
|
title: String(title || `${projectionResult._type}: ${projectionResult._id}`),
|
package/src/preview/types.ts
CHANGED
|
@@ -59,18 +59,3 @@ export interface PreviewValue {
|
|
|
59
59
|
lastEditedDraftAt?: string
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Represents the current state of a preview value along with a flag indicating whether
|
|
65
|
-
* the preview data is still being fetched or is fully resolved.
|
|
66
|
-
*
|
|
67
|
-
* The tuple contains a preview value or null, and a boolean indicating if the data is
|
|
68
|
-
* pending. A `true` value means a fetch is ongoing; `false` indicates that the
|
|
69
|
-
* currently provided preview value is up-to-date.
|
|
70
|
-
*
|
|
71
|
-
* @public
|
|
72
|
-
*/
|
|
73
|
-
export type ValuePending<T> = {
|
|
74
|
-
data: T | null
|
|
75
|
-
isPending: boolean
|
|
76
|
-
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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 {type Project, project, type ProjectMember} from './project'
|
|
7
|
+
|
|
8
|
+
const instance = {} as SanityInstance
|
|
9
|
+
|
|
10
|
+
test('project.resolveState — resolves the wide-boolean detail shape', () => {
|
|
11
|
+
expectTypeOf(project.resolveState(instance, {projectId: 'p'})).resolves.toEqualTypeOf<
|
|
12
|
+
Project<boolean, boolean>
|
|
13
|
+
>()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('Project — literal flags narrow members and features', () => {
|
|
17
|
+
expectTypeOf<Project<true, true>['members']>().toEqualTypeOf<ProjectMember[]>()
|
|
18
|
+
expectTypeOf<Project<true, true>['features']>().toEqualTypeOf<string[]>()
|
|
19
|
+
expectTypeOf<keyof Project<false, false> & ('members' | 'features')>().toEqualTypeOf<never>()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('Project<boolean, boolean> — members and features are optional', () => {
|
|
23
|
+
type Item = Project<boolean, boolean>
|
|
24
|
+
expectTypeOf<Item['members']>().toEqualTypeOf<ProjectMember[] | undefined>()
|
|
25
|
+
expectTypeOf<Item['features']>().toEqualTypeOf<string[] | undefined>()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('project.getState — exposes the snapshot envelope', () => {
|
|
29
|
+
expectTypeOf(project.getState(instance, {projectId: 'p'})).toEqualTypeOf<
|
|
30
|
+
StateSource<FetcherSnapshot<Project<boolean, boolean>>>
|
|
31
|
+
>()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('project — rejects non-boolean flag values', () => {
|
|
35
|
+
// @ts-expect-error includeMembers must be a boolean
|
|
36
|
+
void project.resolveState(instance, {includeMembers: 'yes'})
|
|
37
|
+
})
|
|
@@ -1,33 +1,178 @@
|
|
|
1
1
|
import {type SanityClient} from '@sanity/client'
|
|
2
2
|
import {of} from 'rxjs'
|
|
3
|
-
import {describe, it} from 'vitest'
|
|
3
|
+
import {afterEach, beforeEach, describe, it} from 'vitest'
|
|
4
4
|
|
|
5
5
|
import {getClientState} from '../client/clientStore'
|
|
6
|
-
import {
|
|
6
|
+
import {projects} from '../projects/projects'
|
|
7
|
+
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
7
8
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
8
|
-
import {
|
|
9
|
+
import {getProjectCacheKey, project} from './project'
|
|
9
10
|
|
|
10
11
|
vi.mock('../client/clientStore')
|
|
11
12
|
|
|
12
13
|
describe('project', () => {
|
|
13
|
-
|
|
14
|
-
const instance = createSanityInstance()
|
|
14
|
+
let instance: SanityInstance
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
instance.dispose()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('calls `client.observable.request` against `/projects/<id>` and returns the result', async () => {
|
|
25
|
+
const detail = {id: 'a'}
|
|
26
|
+
const request = vi.fn().mockReturnValue(of(detail))
|
|
18
27
|
|
|
19
28
|
const mockClient = {
|
|
20
|
-
observable: {
|
|
21
|
-
|
|
29
|
+
observable: {request} as unknown as SanityClient['observable'],
|
|
30
|
+
} as SanityClient
|
|
31
|
+
|
|
32
|
+
vi.mocked(getClientState).mockReturnValue({
|
|
33
|
+
observable: of(mockClient),
|
|
34
|
+
} as StateSource<SanityClient>)
|
|
35
|
+
|
|
36
|
+
const result = await project.resolveState(instance, {projectId: 'a'})
|
|
37
|
+
expect(result).toEqual(detail)
|
|
38
|
+
expect(request).toHaveBeenCalledWith({
|
|
39
|
+
uri: '/projects/a',
|
|
40
|
+
query: {includeMembers: 'true', includeFeatures: 'true'},
|
|
41
|
+
tag: 'projects.get',
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('serializes query params (booleans → strings) and respects flags', async () => {
|
|
46
|
+
const request = vi.fn().mockReturnValue(of({id: 'a'}))
|
|
47
|
+
const mockClient = {
|
|
48
|
+
observable: {request} as unknown as SanityClient['observable'],
|
|
49
|
+
} as SanityClient
|
|
50
|
+
|
|
51
|
+
vi.mocked(getClientState).mockReturnValue({
|
|
52
|
+
observable: of(mockClient),
|
|
53
|
+
} as StateSource<SanityClient>)
|
|
54
|
+
|
|
55
|
+
await project.resolveState(instance, {
|
|
56
|
+
projectId: 'a',
|
|
57
|
+
includeMembers: false,
|
|
58
|
+
includeFeatures: false,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(request).toHaveBeenCalledWith({
|
|
62
|
+
uri: '/projects/a',
|
|
63
|
+
query: {
|
|
64
|
+
includeMembers: 'false',
|
|
65
|
+
includeFeatures: 'false',
|
|
22
66
|
},
|
|
67
|
+
tag: 'projects.get',
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('falls back to the instance projectId when none is provided in options', async () => {
|
|
72
|
+
const request = vi.fn().mockReturnValue(of({id: 'p'}))
|
|
73
|
+
const mockClient = {
|
|
74
|
+
observable: {request} as unknown as SanityClient['observable'],
|
|
75
|
+
} as SanityClient
|
|
76
|
+
|
|
77
|
+
vi.mocked(getClientState).mockReturnValue({
|
|
78
|
+
observable: of(mockClient),
|
|
79
|
+
} as StateSource<SanityClient>)
|
|
80
|
+
|
|
81
|
+
await project.resolveState(instance)
|
|
82
|
+
expect(request).toHaveBeenCalledWith({
|
|
83
|
+
uri: '/projects/p',
|
|
84
|
+
query: {includeMembers: 'true', includeFeatures: 'true'},
|
|
85
|
+
tag: 'projects.get',
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('pre-seeds a memberless detail read from the cached default list without fetching', async () => {
|
|
90
|
+
const request = vi.fn().mockReturnValue(
|
|
91
|
+
of([
|
|
92
|
+
{id: 'p1', displayName: 'One'},
|
|
93
|
+
{id: 'p2', displayName: 'Two'},
|
|
94
|
+
]),
|
|
95
|
+
)
|
|
96
|
+
const mockClient = {
|
|
97
|
+
observable: {request} as unknown as SanityClient['observable'],
|
|
23
98
|
} as SanityClient
|
|
24
99
|
|
|
25
100
|
vi.mocked(getClientState).mockReturnValue({
|
|
26
101
|
observable: of(mockClient),
|
|
27
102
|
} as StateSource<SanityClient>)
|
|
28
103
|
|
|
29
|
-
|
|
30
|
-
expect(
|
|
31
|
-
|
|
104
|
+
await projects.resolveState(instance)
|
|
105
|
+
expect(request).toHaveBeenCalledTimes(1)
|
|
106
|
+
|
|
107
|
+
const source = project.getState(instance, {projectId: 'p1', includeMembers: false})
|
|
108
|
+
const unsubscribe = source.subscribe()
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
110
|
+
|
|
111
|
+
expect(source.getCurrent()).toMatchObject({status: 'success', data: {id: 'p1'}})
|
|
112
|
+
expect(request).toHaveBeenCalledTimes(1)
|
|
113
|
+
unsubscribe()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('does not pre-seed a detail read that includes members', async () => {
|
|
117
|
+
const list = [{id: 'p1', displayName: 'One'}]
|
|
118
|
+
const detail = {id: 'p1', displayName: 'One', members: []}
|
|
119
|
+
const request = vi.fn().mockReturnValueOnce(of(list)).mockReturnValueOnce(of(detail))
|
|
120
|
+
const mockClient = {
|
|
121
|
+
observable: {request} as unknown as SanityClient['observable'],
|
|
122
|
+
} as SanityClient
|
|
123
|
+
|
|
124
|
+
vi.mocked(getClientState).mockReturnValue({
|
|
125
|
+
observable: of(mockClient),
|
|
126
|
+
} as StateSource<SanityClient>)
|
|
127
|
+
|
|
128
|
+
await projects.resolveState(instance)
|
|
129
|
+
|
|
130
|
+
const result = await project.resolveState(instance, {projectId: 'p1'})
|
|
131
|
+
expect(result).toEqual(detail)
|
|
132
|
+
expect(request).toHaveBeenCalledTimes(2)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('project cache key generation', () => {
|
|
137
|
+
const mockInstance = {config: {projectId: 'p'}} as SanityInstance
|
|
138
|
+
|
|
139
|
+
it('default call includes :members and :features (both default-true)', () => {
|
|
140
|
+
expect(getProjectCacheKey(mockInstance)).toBe('project:p:members:features')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('treats undefined and the matching default as the same key', () => {
|
|
144
|
+
expect(getProjectCacheKey(mockInstance)).toBe(
|
|
145
|
+
getProjectCacheKey(mockInstance, {includeMembers: true, includeFeatures: true}),
|
|
146
|
+
)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('explicit includeFeatures: false drops the :features segment', () => {
|
|
150
|
+
expect(getProjectCacheKey(mockInstance, {includeFeatures: false})).toBe('project:p:members')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('explicit includeMembers: false drops the :members segment', () => {
|
|
154
|
+
expect(getProjectCacheKey(mockInstance, {includeMembers: false})).toBe('project:p:features')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('combines all segments in order', () => {
|
|
158
|
+
expect(
|
|
159
|
+
getProjectCacheKey(mockInstance, {
|
|
160
|
+
projectId: 'a',
|
|
161
|
+
includeMembers: true,
|
|
162
|
+
includeFeatures: true,
|
|
163
|
+
}),
|
|
164
|
+
).toBe('project:a:members:features')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('produces distinct keys for each meaningful option permutation', () => {
|
|
168
|
+
const keys = new Set([
|
|
169
|
+
getProjectCacheKey(mockInstance),
|
|
170
|
+
getProjectCacheKey(mockInstance, {includeMembers: false}),
|
|
171
|
+
getProjectCacheKey(mockInstance, {includeFeatures: false}),
|
|
172
|
+
getProjectCacheKey(mockInstance, {includeMembers: false, includeFeatures: false}),
|
|
173
|
+
getProjectCacheKey(mockInstance, {projectId: 'a'}),
|
|
174
|
+
getProjectCacheKey(mockInstance, {projectId: 'b'}),
|
|
175
|
+
])
|
|
176
|
+
expect(keys.size).toBe(6)
|
|
32
177
|
})
|
|
33
178
|
})
|