@sanity/sdk 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/_chunks-dts/utils.d.ts +2450 -0
  2. package/dist/_chunks-es/_internal.js +129 -0
  3. package/dist/_chunks-es/_internal.js.map +1 -0
  4. package/dist/_chunks-es/createGroqSearchFilter.js +1537 -0
  5. package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
  6. package/dist/_chunks-es/telemetryManager.js +87 -0
  7. package/dist/_chunks-es/telemetryManager.js.map +1 -0
  8. package/dist/_chunks-es/version.js +7 -0
  9. package/dist/_chunks-es/version.js.map +1 -0
  10. package/dist/_exports/_internal.d.ts +64 -0
  11. package/dist/_exports/_internal.js +20 -0
  12. package/dist/_exports/_internal.js.map +1 -0
  13. package/dist/index.d.ts +2 -2343
  14. package/dist/index.js +465 -1813
  15. package/dist/index.js.map +1 -1
  16. package/package.json +17 -12
  17. package/src/_exports/_internal.ts +14 -0
  18. package/src/_exports/index.ts +18 -1
  19. package/src/auth/authStore.test.ts +150 -1
  20. package/src/auth/authStore.ts +11 -11
  21. package/src/auth/dashboardAuth.ts +2 -2
  22. package/src/auth/handleAuthCallback.ts +9 -3
  23. package/src/auth/logout.test.ts +1 -1
  24. package/src/auth/logout.ts +1 -1
  25. package/src/auth/refreshStampedToken.test.ts +118 -1
  26. package/src/auth/refreshStampedToken.ts +3 -2
  27. package/src/auth/standaloneAuth.ts +9 -3
  28. package/src/auth/studioAuth.ts +34 -7
  29. package/src/auth/studioModeAuth.ts +2 -1
  30. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +10 -2
  31. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +5 -1
  32. package/src/auth/subscribeToStorageEventsAndSetToken.ts +2 -2
  33. package/src/auth/utils.ts +33 -0
  34. package/src/client/clientStore.test.ts +44 -30
  35. package/src/client/clientStore.ts +49 -48
  36. package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
  37. package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
  38. package/src/comlink/node/getNodeState.ts +2 -1
  39. package/src/config/sanityConfig.ts +78 -12
  40. package/src/document/actions.ts +18 -11
  41. package/src/document/applyDocumentActions.test.ts +7 -6
  42. package/src/document/applyDocumentActions.ts +10 -4
  43. package/src/document/documentStore.test.ts +542 -188
  44. package/src/document/documentStore.ts +142 -76
  45. package/src/document/events.ts +7 -2
  46. package/src/document/permissions.test.ts +18 -16
  47. package/src/document/permissions.ts +35 -11
  48. package/src/document/processActions.test.ts +359 -32
  49. package/src/document/processActions.ts +106 -78
  50. package/src/document/reducers.test.ts +117 -29
  51. package/src/document/reducers.ts +47 -40
  52. package/src/document/sharedListener.ts +16 -6
  53. package/src/document/util.ts +14 -0
  54. package/src/favorites/favorites.test.ts +9 -2
  55. package/src/presence/bifurTransport.test.ts +46 -6
  56. package/src/presence/bifurTransport.ts +19 -2
  57. package/src/presence/presenceStore.test.ts +96 -0
  58. package/src/presence/presenceStore.ts +96 -24
  59. package/src/preview/getPreviewState.test.ts +115 -98
  60. package/src/preview/getPreviewState.ts +38 -60
  61. package/src/preview/previewProjectionUtils.test.ts +179 -0
  62. package/src/preview/previewProjectionUtils.ts +93 -0
  63. package/src/preview/resolvePreview.test.ts +42 -25
  64. package/src/preview/resolvePreview.ts +33 -10
  65. package/src/preview/{previewStore.ts → types.ts} +8 -17
  66. package/src/projection/getProjectionState.test.ts +16 -16
  67. package/src/projection/getProjectionState.ts +6 -5
  68. package/src/projection/projectionQuery.ts +2 -3
  69. package/src/projection/projectionStore.test.ts +2 -2
  70. package/src/projection/resolveProjection.ts +2 -2
  71. package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
  72. package/src/projection/subscribeToStateAndFetchBatches.ts +12 -11
  73. package/src/projection/types.ts +1 -1
  74. package/src/query/queryStore.test.ts +12 -12
  75. package/src/query/queryStore.ts +12 -11
  76. package/src/query/reducers.ts +3 -3
  77. package/src/releases/getPerspectiveState.ts +7 -6
  78. package/src/releases/releasesStore.test.ts +20 -5
  79. package/src/releases/releasesStore.ts +20 -8
  80. package/src/store/createActionBinder.test.ts +31 -31
  81. package/src/store/createActionBinder.ts +43 -38
  82. package/src/store/createSanityInstance.ts +2 -3
  83. package/src/store/createStateSourceAction.test.ts +62 -0
  84. package/src/store/createStateSourceAction.ts +34 -39
  85. package/src/telemetry/__telemetry__/sdk.telemetry.ts +42 -0
  86. package/src/telemetry/devMode.test.ts +52 -0
  87. package/src/telemetry/devMode.ts +40 -0
  88. package/src/telemetry/initTelemetry.test.ts +225 -0
  89. package/src/telemetry/initTelemetry.ts +205 -0
  90. package/src/telemetry/telemetryManager.test.ts +263 -0
  91. package/src/telemetry/telemetryManager.ts +187 -0
  92. package/src/users/reducers.ts +3 -4
  93. package/src/users/usersStore.test.ts +1 -0
  94. package/src/users/usersStore.ts +5 -1
  95. package/src/utils/createFetcherStore.test.ts +6 -4
  96. package/src/utils/createFetcherStore.ts +8 -5
  97. package/src/utils/getStagingApiHost.test.ts +21 -0
  98. package/src/utils/getStagingApiHost.ts +14 -0
  99. package/src/utils/ids.test.ts +1 -29
  100. package/src/utils/ids.ts +0 -10
  101. package/src/utils/isImportError.test.ts +72 -0
  102. package/src/utils/isImportError.ts +34 -0
  103. package/src/utils/object.test.ts +95 -0
  104. package/src/utils/object.ts +142 -0
  105. package/src/utils/setCleanupTimeout.ts +24 -0
  106. package/src/preview/previewQuery.test.ts +0 -236
  107. package/src/preview/previewQuery.ts +0 -153
  108. package/src/preview/previewStore.test.ts +0 -36
  109. package/src/preview/subscribeToStateAndFetchBatches.test.ts +0 -221
  110. package/src/preview/subscribeToStateAndFetchBatches.ts +0 -112
  111. package/src/preview/util.ts +0 -13
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Returns true when the input is a non-null object.
3
+ *
4
+ * @internal
5
+ */
6
+ export function isObject(value: unknown): value is object {
7
+ return typeof value === 'object' && value !== null
8
+ }
9
+
10
+ const hasOwn = (value: object, key: PropertyKey) => Object.prototype.hasOwnProperty.call(value, key)
11
+
12
+ const isPlainObject = (value: unknown): value is Record<string, unknown> => {
13
+ if (!isObject(value)) return false
14
+
15
+ const prototype = Object.getPrototypeOf(value)
16
+ return prototype === Object.prototype || prototype === null
17
+ }
18
+
19
+ /**
20
+ * Creates a shallow copy without the provided property.
21
+ *
22
+ * @internal
23
+ */
24
+ export function omitProperty<T extends object, K extends keyof T>(
25
+ value: T | null | undefined,
26
+ key: K,
27
+ ): Omit<T, K> {
28
+ if (!value) return {} as Omit<T, K>
29
+
30
+ const {[key]: _omitted, ...rest} = value
31
+ return rest
32
+ }
33
+
34
+ /**
35
+ * Creates a shallow copy containing only the provided properties.
36
+ *
37
+ * @internal
38
+ */
39
+ export function pickProperties<T extends object, K extends keyof T>(
40
+ value: T,
41
+ keys: readonly K[],
42
+ ): Pick<T, K> {
43
+ const result = {} as Pick<T, K>
44
+
45
+ for (const key of keys) {
46
+ if (hasOwn(value, key)) {
47
+ result[key] = value[key]
48
+ }
49
+ }
50
+
51
+ return result
52
+ }
53
+
54
+ const areSetsEqual = (left: Set<unknown>, right: Set<unknown>): boolean => {
55
+ if (left.size !== right.size) return false
56
+
57
+ const unmatched = [...right]
58
+
59
+ outer: for (const leftValue of left) {
60
+ for (let index = 0; index < unmatched.length; index++) {
61
+ if (isDeepEqual(leftValue, unmatched[index])) {
62
+ unmatched.splice(index, 1)
63
+ continue outer
64
+ }
65
+ }
66
+
67
+ return false
68
+ }
69
+
70
+ return unmatched.length === 0
71
+ }
72
+
73
+ const areMapsEqual = (left: Map<unknown, unknown>, right: Map<unknown, unknown>): boolean => {
74
+ if (left.size !== right.size) return false
75
+
76
+ const unmatched = [...right.entries()]
77
+
78
+ outer: for (const [leftKey, leftValue] of left) {
79
+ for (let index = 0; index < unmatched.length; index++) {
80
+ const [rightKey, rightValue] = unmatched[index]
81
+
82
+ if (isDeepEqual(leftKey, rightKey) && isDeepEqual(leftValue, rightValue)) {
83
+ unmatched.splice(index, 1)
84
+ continue outer
85
+ }
86
+ }
87
+
88
+ return false
89
+ }
90
+
91
+ return unmatched.length === 0
92
+ }
93
+
94
+ /**
95
+ * Compares values deeply across the plain object, array, map, and set shapes used by the SDK.
96
+ * This helper is intended for acyclic SDK data structures and does not guard against circular
97
+ * references.
98
+ *
99
+ * @internal
100
+ */
101
+ export function isDeepEqual<T>(left: T, right: T): boolean {
102
+ if (Object.is(left, right)) return true
103
+
104
+ if (!isObject(left) || !isObject(right)) return false
105
+
106
+ if (left instanceof Date && right instanceof Date) {
107
+ return left.getTime() === right.getTime()
108
+ }
109
+
110
+ if (left instanceof RegExp && right instanceof RegExp) {
111
+ return left.source === right.source && left.flags === right.flags
112
+ }
113
+
114
+ if (left instanceof Set && right instanceof Set) {
115
+ return areSetsEqual(left, right)
116
+ }
117
+
118
+ if (left instanceof Map && right instanceof Map) {
119
+ return areMapsEqual(left, right)
120
+ }
121
+
122
+ if (Array.isArray(left) || Array.isArray(right)) {
123
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false
124
+
125
+ return left.every((value, index) => isDeepEqual(value, right[index]))
126
+ }
127
+
128
+ if (!isPlainObject(left) || !isPlainObject(right)) return false
129
+
130
+ const leftKeys = Object.keys(left)
131
+ const rightKeys = Object.keys(right)
132
+
133
+ if (leftKeys.length !== rightKeys.length) return false
134
+
135
+ for (const key of leftKeys) {
136
+ if (!hasOwn(right, key) || !isDeepEqual(left[key], right[key])) {
137
+ return false
138
+ }
139
+ }
140
+
141
+ return true
142
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Like `setTimeout`, but calls `.unref()` on the timer when running in Node.js.
3
+ *
4
+ * In Node.js, active timers prevent the process from exiting. Cleanup timers
5
+ * (e.g. deferred subscription removal) should not keep the process alive -
6
+ * `.unref()` lets the process exit naturally while still firing the timer if
7
+ * the process happens to still be running.
8
+ *
9
+ * In browsers, `setTimeout` returns a number and has no `.unref()` method,
10
+ * so this is a no-op there.
11
+ */
12
+ export function setCleanupTimeout(fn: () => void, delay: number): ReturnType<typeof setTimeout> {
13
+ const timer = setTimeout(fn, delay)
14
+
15
+ // In Node.js, setTimeout returns an object with an `unref()` method.
16
+ // In browsers, it returns a number. We assign to `unknown` and narrow
17
+ // at runtime so this works in both environments without type assertions.
18
+ const t: unknown = timer
19
+ if (typeof t === 'object' && t !== null && 'unref' in t && typeof t.unref === 'function') {
20
+ t.unref()
21
+ }
22
+
23
+ return timer
24
+ }
@@ -1,236 +0,0 @@
1
- import {describe, expect, it} from 'vitest'
2
-
3
- import {SUBTITLE_CANDIDATES, TITLE_CANDIDATES} from './previewConstants'
4
- import {createPreviewQuery, normalizeMedia, processPreviewQuery} from './previewQuery'
5
- import {STABLE_EMPTY_PREVIEW} from './util'
6
-
7
- describe('createPreviewQuery', () => {
8
- it('creates a query and params for given ids and schema', () => {
9
- const ids = new Set(['book1', 'book2'])
10
- const {query, params} = createPreviewQuery(ids)
11
- expect(query).toMatch(/.*_id in \$__ids_.*/)
12
- expect(Object.keys(params)).toHaveLength(1)
13
- })
14
- })
15
-
16
- describe('processPreviewQuery', () => {
17
- it('returns STABLE_EMPTY_PREVIEW if documentType is missing', () => {
18
- const ids = new Set(['doc1'])
19
- const result = processPreviewQuery({
20
- projectId: 'p',
21
- dataset: 'd',
22
- results: [],
23
- ids,
24
- })
25
-
26
- expect(result['doc1']).toEqual(STABLE_EMPTY_PREVIEW)
27
- })
28
-
29
- it('returns STABLE_EMPTY_PREVIEW if no candidates found', () => {
30
- const ids = new Set(['doc1'])
31
- const result = processPreviewQuery({
32
- projectId: 'p',
33
- dataset: 'd',
34
- results: [], // no results, so no selectResult
35
- ids,
36
- })
37
-
38
- expect(result['doc1']).toEqual(STABLE_EMPTY_PREVIEW)
39
- })
40
-
41
- // it.only('returns STABLE_ERROR_PREVIEW if an error occurs', () => {
42
- // const ids = new Set(['doc1'])
43
- // const result = processPreviewQuery({
44
- // projectId: 'p',
45
- // dataset: 'd',
46
- // results: [
47
- // {
48
- // _id: 'doc1',
49
- // _type: 'someType',
50
- // _updatedAt: new Date().toISOString(),
51
- // titleCandidates: {title: null},
52
- // subtitleCandidates: {subtitle: null},
53
- // },
54
- // ], // no results, so no selectResult
55
- // ids,
56
- // })
57
-
58
- // expect(result['doc1']).toEqual(STABLE_ERROR_PREVIEW)
59
- // })
60
-
61
- it('processes query results into preview values', () => {
62
- const results = [
63
- {
64
- _id: 'person1',
65
- _type: 'person',
66
- _updatedAt: '2021-01-01',
67
- titleCandidates: {title: 'John'},
68
- subtitleCandidates: {subtitle: null},
69
- },
70
- ]
71
-
72
- const processed = processPreviewQuery({
73
- projectId: 'p',
74
- dataset: 'd',
75
- ids: new Set(['person1']),
76
- results,
77
- })
78
-
79
- const val = processed['person1']
80
- expect(val?.data).toEqual({
81
- title: 'John',
82
- media: null,
83
- _status: {lastEditedPublishedAt: '2021-01-01'},
84
- })
85
- expect(val?.isPending).toBe(false)
86
- })
87
-
88
- it('resolves status preferring draft over published when available', () => {
89
- const results = [
90
- {
91
- _id: 'drafts.article1',
92
- _type: 'article',
93
- _updatedAt: '2023-12-16T12:00:00Z',
94
- titleCandidates: {
95
- title: 'Draft Title',
96
- name: null,
97
- },
98
- subtitleCandidates: {
99
- title: null,
100
- name: null,
101
- },
102
- },
103
- {
104
- _id: 'article1',
105
- _type: 'article',
106
- _updatedAt: '2023-12-15T12:00:00Z',
107
- titleCandidates: {
108
- title: 'Published Title',
109
- name: null,
110
- },
111
- subtitleCandidates: {
112
- title: null,
113
- name: null,
114
- },
115
- },
116
- ]
117
-
118
- const processed = processPreviewQuery({
119
- projectId: 'p',
120
- dataset: 'd',
121
- ids: new Set(['article1']),
122
- results,
123
- })
124
-
125
- const val = processed['article1']
126
- expect(val?.data).toEqual({
127
- title: 'Draft Title',
128
- media: null,
129
- _status: {
130
- lastEditedDraftAt: '2023-12-16T12:00:00Z',
131
- lastEditedPublishedAt: '2023-12-15T12:00:00Z',
132
- },
133
- })
134
- expect(val?.isPending).toBe(false)
135
- })
136
-
137
- it('uses the first defined title or subtitle from the candidates', () => {
138
- const titleCandidates = {
139
- title: 'Draft Title',
140
- name: 'Draft Name',
141
- label: 'Draft Label',
142
- heading: 'Draft Heading',
143
- header: 'Draft Header',
144
- caption: 'Draft Caption',
145
- }
146
-
147
- const subtitleCandidates = {
148
- subtitle: 'Draft Subtitle',
149
- description: 'Draft Description',
150
- name: 'Draft Name',
151
- label: 'Draft Label',
152
- heading: 'Draft Heading',
153
- header: 'Draft Header',
154
- caption: 'Draft Caption',
155
- }
156
-
157
- const results = [
158
- {
159
- _id: 'article1',
160
- _type: 'article',
161
- _updatedAt: '2023-12-15T12:00:00Z',
162
- titleCandidates,
163
- subtitleCandidates,
164
- },
165
- ]
166
-
167
- const processed = processPreviewQuery({
168
- projectId: 'p',
169
- dataset: 'd',
170
- ids: new Set(['article1']),
171
- results,
172
- })
173
-
174
- const val = processed['article1']
175
- expect(val?.data).toEqual({
176
- title: titleCandidates[TITLE_CANDIDATES[0] as keyof typeof titleCandidates],
177
- subtitle: subtitleCandidates[SUBTITLE_CANDIDATES[0] as keyof typeof subtitleCandidates],
178
- media: null,
179
- _status: {lastEditedPublishedAt: '2023-12-15T12:00:00Z'},
180
- })
181
- expect(val?.isPending).toBe(false)
182
- })
183
- })
184
-
185
- describe('normalizeMedia', () => {
186
- it('returns null if media is null or undefined', () => {
187
- expect(normalizeMedia(null, 'projectId', 'dataset')).toBeNull()
188
- expect(normalizeMedia(undefined, 'projectId', 'dataset')).toBeNull()
189
- })
190
-
191
- it('returns null if media does not have a valid asset', () => {
192
- const invalidMedia1 = {media: {_ref: 'image-abc123-200x200-png'}} // Missing `asset` property
193
- const invalidMedia2 = {asset: {ref: 'image-abc123-200x200-png'}} // Incorrect property name `ref`
194
- expect(normalizeMedia(invalidMedia1, 'projectId', 'dataset')).toBeNull()
195
- expect(normalizeMedia(invalidMedia2, 'projectId', 'dataset')).toBeNull()
196
- })
197
-
198
- it('returns null if media is not an object', () => {
199
- expect(normalizeMedia(123, 'projectId', 'dataset')).toBeNull()
200
- expect(normalizeMedia('invalid', 'projectId', 'dataset')).toBeNull()
201
- })
202
-
203
- it('returns a normalized URL for valid image asset objects', () => {
204
- const validMedia = {type: 'image-asset', _ref: 'image-abc123-200x200-png'}
205
- const result = normalizeMedia(validMedia, 'projectId', 'dataset')
206
- expect(result).toEqual({
207
- type: 'image-asset',
208
- _ref: 'image-abc123-200x200-png',
209
- url: 'https://cdn.sanity.io/images/projectId/dataset/abc123-200x200.png',
210
- })
211
- })
212
-
213
- it('throws an error for invalid asset IDs in the media', () => {
214
- const invalidMedia = {type: 'image-asset', _ref: 'invalid-asset-id'}
215
- expect(() => normalizeMedia(invalidMedia, 'projectId', 'dataset')).toThrow(
216
- 'Invalid asset ID `invalid-asset-id`. Expected: image-{assetName}-{width}x{height}-{format}',
217
- )
218
- })
219
-
220
- it('handles image assets with expected URL format', () => {
221
- const media = {type: 'image-asset', _ref: 'image-xyz456-400x400-jpg'}
222
- const result = normalizeMedia(media, 'projectId', 'dataset')
223
- expect(result).toEqual({
224
- type: 'image-asset',
225
- _ref: 'image-xyz456-400x400-jpg',
226
- url: 'https://cdn.sanity.io/images/projectId/dataset/xyz456-400x400.jpg',
227
- })
228
- })
229
-
230
- it('ensures assetIdToUrl throws for asset IDs with missing groups', () => {
231
- const invalidMedia = {type: 'image-asset', _ref: 'image-missinggroups'}
232
- expect(() => normalizeMedia(invalidMedia, 'projectId', 'dataset')).toThrow(
233
- 'Invalid asset ID `image-missinggroups`. Expected: image-{assetName}-{width}x{height}-{format}',
234
- )
235
- })
236
- })
@@ -1,153 +0,0 @@
1
- import {isObject} from 'lodash-es'
2
-
3
- import {hashString} from '../utils/hashString'
4
- import {getDraftId, getPublishedId} from '../utils/ids'
5
- import {PREVIEW_PROJECTION, SUBTITLE_CANDIDATES, TITLE_CANDIDATES} from './previewConstants'
6
- import {
7
- type PreviewQueryResult,
8
- type PreviewStoreState,
9
- type PreviewValue,
10
- type ValuePending,
11
- } from './previewStore'
12
- import {STABLE_EMPTY_PREVIEW, STABLE_ERROR_PREVIEW} from './util'
13
-
14
- interface ProcessPreviewQueryOptions {
15
- projectId: string
16
- dataset: string
17
- ids: Set<string>
18
- results: PreviewQueryResult[]
19
- }
20
-
21
- /**
22
- * Converts an asset ID to a URL.
23
- *
24
- * @internal
25
- */
26
- function assetIdToUrl(assetId: string, projectId: string, dataset: string) {
27
- const pattern = /^image-(?<assetName>[A-Za-z0-9]+)-(?<dimensions>\d+x\d+)-(?<format>[a-z]+)$/
28
- const match = assetId.match(pattern)
29
- if (!match?.groups) {
30
- throw new Error(
31
- `Invalid asset ID \`${assetId}\`. Expected: image-{assetName}-{width}x{height}-{format}`,
32
- )
33
- }
34
-
35
- const {assetName, dimensions, format} = match.groups
36
- return `https://cdn.sanity.io/images/${projectId}/${dataset}/${assetName}-${dimensions}.${format}`
37
- }
38
-
39
- /**
40
- * Checks if the provided value has `_ref` property that is a string and starts with `image-`
41
- */
42
- function hasImageRef<T>(value: unknown): value is T & {_ref: string} {
43
- return isObject(value) && '_ref' in value && typeof (value as {_ref: unknown})._ref === 'string'
44
- }
45
-
46
- /**
47
- * Normalizes a media asset to a preview value.
48
- * Adds a url to a media asset reference.
49
- *
50
- * @internal
51
- */
52
- export function normalizeMedia(
53
- media: unknown,
54
- projectId: string,
55
- dataset: string,
56
- ): PreviewValue['media'] {
57
- if (!media) return null
58
- if (!hasImageRef(media)) return null
59
- return {
60
- type: 'image-asset',
61
- _ref: media._ref,
62
- url: assetIdToUrl(media._ref, projectId, dataset),
63
- }
64
- }
65
-
66
- /**
67
- * Finds a single field value from a set of candidates based on a priority list of field names.
68
- * Returns the first non-empty string value found from the candidates matching the priority list order.
69
- *
70
- * @internal
71
- */
72
- function findFirstDefined(
73
- fieldsToSearch: string[],
74
- candidates: Record<string, unknown>,
75
- exclude?: unknown,
76
- ): string | undefined {
77
- if (!candidates) return undefined
78
-
79
- for (const field of fieldsToSearch) {
80
- const value = candidates[field]
81
- if (typeof value === 'string' && value.trim() !== '' && value !== exclude) {
82
- return value
83
- }
84
- }
85
-
86
- return undefined
87
- }
88
-
89
- export function processPreviewQuery({
90
- projectId,
91
- dataset,
92
- ids,
93
- results,
94
- }: ProcessPreviewQueryOptions): PreviewStoreState['values'] {
95
- const resultMap = results.reduce<{[TDocumentId in string]?: PreviewQueryResult}>((acc, next) => {
96
- acc[next._id] = next
97
- return acc
98
- }, {})
99
-
100
- return Object.fromEntries(
101
- Array.from(ids).map((id): [string, ValuePending<PreviewValue>] => {
102
- const publishedId = getPublishedId(id)
103
- const draftId = getDraftId(id)
104
-
105
- const draftResult = resultMap[draftId]
106
- const publishedResult = resultMap[publishedId]
107
-
108
- if (!draftResult && !publishedResult) return [id, STABLE_EMPTY_PREVIEW]
109
-
110
- try {
111
- const result = draftResult || publishedResult
112
- if (!result) return [id, STABLE_EMPTY_PREVIEW]
113
- const title = findFirstDefined(TITLE_CANDIDATES, result.titleCandidates)
114
- const subtitle = findFirstDefined(SUBTITLE_CANDIDATES, result.subtitleCandidates, title)
115
- const preview: Omit<PreviewValue, 'status'> = {
116
- title: String(title || `${result._type}: ${result._id}`),
117
- subtitle: subtitle || undefined,
118
- media: normalizeMedia(result.media, projectId, dataset),
119
- }
120
-
121
- const _status: PreviewValue['_status'] = {
122
- ...(draftResult?._updatedAt && {lastEditedDraftAt: draftResult._updatedAt}),
123
- ...(publishedResult?._updatedAt && {lastEditedPublishedAt: publishedResult._updatedAt}),
124
- }
125
-
126
- return [id, {data: {...preview, _status}, isPending: false}]
127
- } catch (e) {
128
- // TODO: replace this with bubbling the error
129
- // eslint-disable-next-line no-console
130
- console.warn(e)
131
- return [id, STABLE_ERROR_PREVIEW]
132
- }
133
- }),
134
- )
135
- }
136
-
137
- interface CreatePreviewQueryResult {
138
- query: string
139
- params: Record<string, string[]>
140
- }
141
-
142
- export function createPreviewQuery(documentIds: Set<string>): CreatePreviewQueryResult {
143
- // Create arrays of draft and published IDs
144
- const allIds = Array.from(documentIds).flatMap((id) => [getPublishedId(id), getDraftId(id)])
145
- const queryHash = hashString(PREVIEW_PROJECTION)
146
-
147
- return {
148
- query: `*[_id in $__ids_${queryHash}]${PREVIEW_PROJECTION}`,
149
- params: {
150
- [`__ids_${queryHash}`]: allIds,
151
- },
152
- }
153
- }
@@ -1,36 +0,0 @@
1
- import {Observable} from 'rxjs'
2
- import {describe, it, vi} from 'vitest'
3
-
4
- import {createSanityInstance} from '../store/createSanityInstance'
5
- import {createStoreInstance} from '../store/createStoreInstance'
6
- import {previewStore} from './previewStore'
7
- import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
8
-
9
- vi.mock('./subscribeToStateAndFetchBatches')
10
-
11
- describe('previewStore', () => {
12
- it('is a resource that initializes with state and subscriptions', async () => {
13
- const teardown = vi.fn()
14
- const subscriber = vi.fn().mockReturnValue(teardown)
15
- vi.mocked(subscribeToStateAndFetchBatches).mockReturnValue(
16
- new Observable(subscriber).subscribe(),
17
- )
18
-
19
- const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
20
-
21
- const {state, dispose} = createStoreInstance(
22
- instance,
23
- {name: 'p.d', projectId: 'p', dataset: 'd'},
24
- previewStore,
25
- )
26
-
27
- expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({
28
- instance,
29
- state,
30
- key: {name: 'p.d', projectId: 'p', dataset: 'd'},
31
- })
32
-
33
- dispose()
34
- instance.dispose()
35
- })
36
- })