@sanity/sdk 2.8.0 → 3.0.0-rc.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 (99) hide show
  1. package/dist/index.d.ts +228 -239
  2. package/dist/index.js +287 -454
  3. package/dist/index.js.map +1 -1
  4. package/package.json +4 -4
  5. package/src/_exports/index.ts +16 -17
  6. package/src/agent/agentActions.test.ts +60 -16
  7. package/src/agent/agentActions.ts +29 -20
  8. package/src/auth/authMode.test.ts +0 -25
  9. package/src/auth/authMode.ts +3 -6
  10. package/src/auth/authStore.test.ts +129 -66
  11. package/src/auth/authStore.ts +9 -11
  12. package/src/auth/dashboardAuth.ts +2 -2
  13. package/src/auth/getOrganizationVerificationState.test.ts +10 -11
  14. package/src/auth/handleAuthCallback.test.ts +0 -12
  15. package/src/auth/handleAuthCallback.ts +9 -3
  16. package/src/auth/logout.test.ts +0 -6
  17. package/src/auth/refreshStampedToken.test.ts +121 -17
  18. package/src/auth/standaloneAuth.ts +9 -3
  19. package/src/auth/studioAuth.ts +35 -8
  20. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +9 -3
  21. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +1 -1
  22. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +0 -2
  23. package/src/auth/subscribeToStorageEventsAndSetToken.ts +2 -2
  24. package/src/auth/utils.ts +33 -0
  25. package/src/client/clientStore.test.ts +14 -61
  26. package/src/client/clientStore.ts +52 -28
  27. package/src/comlink/controller/actions/destroyController.test.ts +1 -4
  28. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +1 -4
  29. package/src/comlink/controller/actions/getOrCreateController.test.ts +1 -4
  30. package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
  31. package/src/comlink/controller/comlinkControllerStore.test.ts +1 -4
  32. package/src/comlink/node/actions/getOrCreateNode.test.ts +1 -4
  33. package/src/comlink/node/actions/releaseNode.test.ts +1 -4
  34. package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
  35. package/src/comlink/node/getNodeState.test.ts +1 -1
  36. package/src/config/__tests__/handles.test.ts +12 -18
  37. package/src/config/handles.ts +7 -25
  38. package/src/config/sanityConfig.ts +99 -52
  39. package/src/datasets/datasets.test.ts +2 -2
  40. package/src/datasets/datasets.ts +4 -10
  41. package/src/document/actions.test.ts +33 -4
  42. package/src/document/actions.ts +3 -10
  43. package/src/document/applyDocumentActions.test.ts +17 -18
  44. package/src/document/applyDocumentActions.ts +9 -12
  45. package/src/document/documentStore.test.ts +303 -133
  46. package/src/document/documentStore.ts +70 -61
  47. package/src/document/permissions.test.ts +44 -8
  48. package/src/document/processActions.test.ts +77 -7
  49. package/src/document/reducers.test.ts +35 -3
  50. package/src/document/sharedListener.test.ts +13 -13
  51. package/src/document/sharedListener.ts +8 -3
  52. package/src/favorites/favorites.test.ts +10 -2
  53. package/src/presence/presenceStore.test.ts +34 -9
  54. package/src/presence/presenceStore.ts +29 -13
  55. package/src/preview/previewProjectionUtils.test.ts +192 -0
  56. package/src/preview/previewProjectionUtils.ts +88 -0
  57. package/src/preview/{previewStore.ts → types.ts} +6 -25
  58. package/src/project/project.test.ts +1 -1
  59. package/src/project/project.ts +14 -20
  60. package/src/projection/getProjectionState.test.ts +4 -2
  61. package/src/projection/getProjectionState.ts +2 -21
  62. package/src/projection/projectionQuery.ts +2 -3
  63. package/src/projection/projectionStore.test.ts +3 -3
  64. package/src/projection/resolveProjection.test.ts +2 -1
  65. package/src/projection/resolveProjection.ts +2 -18
  66. package/src/projection/subscribeToStateAndFetchBatches.test.ts +2 -2
  67. package/src/projection/subscribeToStateAndFetchBatches.ts +23 -36
  68. package/src/projection/types.ts +1 -9
  69. package/src/projects/projects.test.ts +1 -1
  70. package/src/query/queryStore.test.ts +86 -28
  71. package/src/query/queryStore.ts +23 -38
  72. package/src/releases/getPerspectiveState.test.ts +14 -13
  73. package/src/releases/getPerspectiveState.ts +6 -6
  74. package/src/releases/releasesStore.test.ts +21 -6
  75. package/src/releases/releasesStore.ts +18 -8
  76. package/src/store/createActionBinder.test.ts +114 -111
  77. package/src/store/createActionBinder.ts +52 -101
  78. package/src/store/createSanityInstance.test.ts +13 -83
  79. package/src/store/createSanityInstance.ts +2 -78
  80. package/src/store/createStateSourceAction.test.ts +2 -2
  81. package/src/store/createStateSourceAction.ts +5 -5
  82. package/src/store/createStoreInstance.test.ts +2 -4
  83. package/src/users/reducers.test.ts +1 -6
  84. package/src/users/reducers.ts +2 -2
  85. package/src/users/types.ts +4 -4
  86. package/src/users/usersStore.test.ts +12 -15
  87. package/src/utils/createFetcherStore.test.ts +1 -1
  88. package/src/utils/logger.test.ts +0 -12
  89. package/src/utils/logger.ts +3 -8
  90. package/src/preview/getPreviewState.test.ts +0 -120
  91. package/src/preview/getPreviewState.ts +0 -91
  92. package/src/preview/previewQuery.test.ts +0 -236
  93. package/src/preview/previewQuery.ts +0 -153
  94. package/src/preview/previewStore.test.ts +0 -36
  95. package/src/preview/resolvePreview.test.ts +0 -47
  96. package/src/preview/resolvePreview.ts +0 -20
  97. package/src/preview/subscribeToStateAndFetchBatches.test.ts +0 -221
  98. package/src/preview/subscribeToStateAndFetchBatches.ts +0 -112
  99. package/src/preview/util.ts +0 -13
@@ -1,10 +1,9 @@
1
1
  import {beforeEach, describe, expect, it, vi} from 'vitest'
2
2
 
3
- import {type DocumentSource} from '../config/sanityConfig'
3
+ import {type DocumentResource} from '../config/sanityConfig'
4
4
  import {
5
- bindActionByDataset,
6
- bindActionBySource,
7
- bindActionBySourceAndPerspective,
5
+ bindActionByResource,
6
+ bindActionByResourceAndPerspective,
8
7
  bindActionGlobally,
9
8
  createActionBinder,
10
9
  } from './createActionBinder'
@@ -30,7 +29,7 @@ describe('createActionBinder', () => {
30
29
  return context.state.counter
31
30
  })
32
31
  const boundAction = binder(storeDefinition, action)
33
- const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
32
+ const instance = createSanityInstance()
34
33
 
35
34
  // First call creates store instance
36
35
  const result1 = boundAction(instance, 5)
@@ -44,9 +43,9 @@ describe('createActionBinder', () => {
44
43
  })
45
44
 
46
45
  it('should create separate store instances for different composite keys', () => {
47
- const binder = createActionBinder(({config: {projectId, dataset}}, ..._rest) => ({
48
- name: `${projectId}.${dataset}`,
49
- }))
46
+ const binder = createActionBinder((instance, ..._rest) => {
47
+ return {name: instance.instanceId}
48
+ })
50
49
  const storeDefinition = {
51
50
  name: 'TestStore',
52
51
  getInitialState: () => ({counter: 0}),
@@ -56,8 +55,8 @@ describe('createActionBinder', () => {
56
55
  return context.state.counter
57
56
  })
58
57
  const boundAction = binder(storeDefinition, action)
59
- const instanceA = createSanityInstance({projectId: 'p1', dataset: 'd1'})
60
- const instanceB = createSanityInstance({projectId: 'p2', dataset: 'd2'})
58
+ const instanceA = createSanityInstance()
59
+ const instanceB = createSanityInstance()
61
60
 
62
61
  const resultA = boundAction(instanceA, 3)
63
62
  const resultB = boundAction(instanceB, 4)
@@ -75,8 +74,8 @@ describe('createActionBinder', () => {
75
74
  }
76
75
  const action = vi.fn((context) => context.state.counter)
77
76
  const boundAction = binder(storeDefinition, action)
78
- const instance1 = createSanityInstance({projectId: 'p1', dataset: 'd1'})
79
- const instance2 = createSanityInstance({projectId: 'p1', dataset: 'd1'})
77
+ const instance1 = createSanityInstance()
78
+ const instance2 = createSanityInstance()
80
79
 
81
80
  // Call action on both instances
82
81
  boundAction(instance1)
@@ -96,34 +95,6 @@ describe('createActionBinder', () => {
96
95
  })
97
96
  })
98
97
 
99
- describe('bindActionByDataset', () => {
100
- it('should work correctly when projectId and dataset are provided', () => {
101
- const storeDefinition = {
102
- name: 'DSStore',
103
- getInitialState: () => ({counter: 0}),
104
- }
105
- const action = vi.fn((_context, {value}: {value: string}) => value)
106
- const boundAction = bindActionByDataset(storeDefinition, action)
107
- const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
108
- const result = boundAction(instance, {value: 'hello'})
109
- expect(result).toBe('hello')
110
- })
111
-
112
- it('should throw an error if projectId or dataset is missing', () => {
113
- const storeDefinition = {
114
- name: 'DSStore',
115
- getInitialState: () => ({counter: 0}),
116
- }
117
- const action = vi.fn((_context, _?) => 'fail')
118
- const boundAction = bindActionByDataset(storeDefinition, action)
119
- // Instance with missing dataset
120
- const instance = createSanityInstance({projectId: 'proj1', dataset: ''})
121
- expect(() => boundAction(instance)).toThrow(
122
- 'This API requires a project ID and dataset configured.',
123
- )
124
- })
125
- })
126
-
127
98
  describe('bindActionGlobally', () => {
128
99
  it('should work correctly ignoring config in key generation', () => {
129
100
  const storeDefinition = {
@@ -134,8 +105,8 @@ describe('bindActionGlobally', () => {
134
105
  const boundAction = bindActionGlobally(storeDefinition, action)
135
106
 
136
107
  // Create instances with different configs
137
- const instance1 = createSanityInstance({projectId: 'any', dataset: 'any'})
138
- const instance2 = createSanityInstance({projectId: 'different', dataset: 'config'})
108
+ const instance1 = createSanityInstance()
109
+ const instance2 = createSanityInstance()
139
110
 
140
111
  // Both instances should use the same store
141
112
  const result1 = boundAction(instance1, 42)
@@ -161,131 +132,135 @@ describe('bindActionGlobally', () => {
161
132
  })
162
133
  })
163
134
 
164
- describe('bindActionBySource', () => {
165
- it('should throw an error when provided an invalid source', () => {
135
+ describe('bindActionByResource', () => {
136
+ it('should resolve from config.defaultResource when no resource provided', () => {
166
137
  const storeDefinition = {
167
- name: 'SourceStore',
138
+ name: 'ResourceNamedStore',
168
139
  getInitialState: () => ({counter: 0}),
169
140
  }
170
- const action = vi.fn((_context) => 'success')
171
- const boundAction = bindActionBySource(storeDefinition, action)
172
- const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
173
-
174
- expect(() =>
175
- boundAction(instance, {source: {invalid: 'source'} as unknown as DocumentSource}),
176
- ).toThrow('Received invalid source:')
141
+ const action = vi.fn((context) => context.key)
142
+ const boundAction = bindActionByResource(storeDefinition, action)
143
+ const instance = createSanityInstance({
144
+ defaultResource: {projectId: 'named-proj', dataset: 'named-ds'},
145
+ })
146
+ const result = boundAction(instance, {})
147
+ expect(result).toEqual(
148
+ expect.objectContaining({
149
+ name: 'named-proj.named-ds',
150
+ resource: {projectId: 'named-proj', dataset: 'named-ds'},
151
+ }),
152
+ )
177
153
  })
178
154
 
179
- it('should throw an error when no source provided and projectId/dataset are missing', () => {
155
+ it('should throw an error when provided an invalid resource', () => {
180
156
  const storeDefinition = {
181
- name: 'SourceStore',
157
+ name: 'ResourceStore',
182
158
  getInitialState: () => ({counter: 0}),
183
159
  }
184
160
  const action = vi.fn((_context) => 'success')
185
- const boundAction = bindActionBySource(storeDefinition, action)
186
- const instance = createSanityInstance({projectId: '', dataset: ''})
161
+ const boundAction = bindActionByResource(storeDefinition, action)
162
+ const instance = createSanityInstance()
187
163
 
188
- expect(() => boundAction(instance, {})).toThrow(
189
- 'This API requires a project ID and dataset configured.',
190
- )
164
+ expect(() =>
165
+ boundAction(instance, {resource: {invalid: 'resource'} as unknown as DocumentResource}),
166
+ ).toThrow('Received invalid resource:')
191
167
  })
192
168
 
193
- it('should work correctly with a valid dataset source', () => {
169
+ it('should work correctly with a valid dataset resource', () => {
194
170
  const storeDefinition = {
195
- name: 'SourceStore',
171
+ name: 'ResourceStore',
196
172
  getInitialState: () => ({counter: 0}),
197
173
  }
198
174
  const action = vi.fn((_context) => 'success')
199
- const boundAction = bindActionBySource(storeDefinition, action)
200
- const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
175
+ const boundAction = bindActionByResource(storeDefinition, action)
176
+ const instance = createSanityInstance()
201
177
 
202
178
  const result = boundAction(instance, {
203
- source: {projectId: 'proj2', dataset: 'ds2'},
179
+ resource: {projectId: 'proj2', dataset: 'ds2'},
204
180
  })
205
181
  expect(result).toBe('success')
206
182
  })
207
183
  })
208
184
 
209
- describe('bindActionBySourceAndPerspective', () => {
210
- it('should throw an error when provided an invalid source', () => {
185
+ describe('bindActionByResourceAndPerspective', () => {
186
+ it('should throw an error when provided an invalid resource', () => {
211
187
  const storeDefinition = {
212
188
  name: 'PerspectiveStore',
213
189
  getInitialState: () => ({counter: 0}),
214
190
  }
215
191
  const action = vi.fn((_context) => 'success')
216
- const boundAction = bindActionBySourceAndPerspective(storeDefinition, action)
217
- const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
192
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
193
+ const instance = createSanityInstance()
218
194
 
219
195
  expect(() =>
220
196
  boundAction(instance, {
221
- source: {invalid: 'source'} as unknown as DocumentSource,
197
+ resource: {invalid: 'resource'} as unknown as DocumentResource,
222
198
  perspective: 'drafts',
223
199
  }),
224
- ).toThrow('Received invalid source:')
200
+ ).toThrow('Received invalid resource:')
225
201
  })
226
202
 
227
- it('should throw an error when no source provided and projectId/dataset are missing', () => {
203
+ it('should work correctly with a valid dataset resource and explicit perspective', () => {
228
204
  const storeDefinition = {
229
205
  name: 'PerspectiveStore',
230
206
  getInitialState: () => ({counter: 0}),
231
207
  }
232
208
  const action = vi.fn((_context) => 'success')
233
- const boundAction = bindActionBySourceAndPerspective(storeDefinition, action)
234
- const instance = createSanityInstance({projectId: '', dataset: ''})
209
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
210
+ const instance = createSanityInstance()
235
211
 
236
- expect(() => boundAction(instance, {perspective: 'drafts'})).toThrow(
237
- 'This API requires a project ID and dataset configured.',
238
- )
212
+ const result = boundAction(instance, {
213
+ resource: {projectId: 'proj2', dataset: 'ds2'},
214
+ perspective: 'drafts',
215
+ })
216
+ expect(result).toBe('success')
239
217
  })
240
218
 
241
- it('should work correctly with a valid dataset source and explicit perspective', () => {
219
+ it('should throw an error when no resource provided and no default resource configured', () => {
242
220
  const storeDefinition = {
243
221
  name: 'PerspectiveStore',
244
222
  getInitialState: () => ({counter: 0}),
245
223
  }
246
224
  const action = vi.fn((_context) => 'success')
247
- const boundAction = bindActionBySourceAndPerspective(storeDefinition, action)
248
- const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
225
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
226
+ const instance = createSanityInstance({})
249
227
 
250
- const result = boundAction(instance, {
251
- source: {projectId: 'proj2', dataset: 'ds2'},
252
- perspective: 'drafts',
253
- })
254
- expect(result).toBe('success')
228
+ // @ts-expect-error - test invalid argument
229
+ expect(() => boundAction(instance, {perspective: 'drafts' as const})).toThrow(
230
+ 'No resource provided and no default resource configured.',
231
+ )
255
232
  })
256
233
 
257
- it('should work correctly with valid dataset source and no perspective (falls back to drafts)', () => {
234
+ it('should work correctly with valid dataset resource and no perspective (falls back to drafts)', () => {
258
235
  const storeDefinition = {
259
236
  name: 'PerspectiveStore',
260
237
  getInitialState: () => ({counter: 0}),
261
238
  }
262
239
  const action = vi.fn((_context) => 'success')
263
- const boundAction = bindActionBySourceAndPerspective(storeDefinition, action)
264
- const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
240
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
241
+ const instance = createSanityInstance()
265
242
 
266
243
  const result = boundAction(instance, {
267
- source: {projectId: 'proj1', dataset: 'ds1'},
244
+ resource: {projectId: 'proj1', dataset: 'ds1'},
268
245
  })
269
246
  expect(result).toBe('success')
270
247
  })
271
248
 
272
- it('should use instance.config.perspective when options.perspective is not provided', () => {
249
+ it('should fall back to "drafts" when options.perspective is not provided', () => {
273
250
  const storeDefinition = {
274
251
  name: 'PerspectiveStore',
275
252
  getInitialState: () => ({counter: 0}),
276
253
  }
277
254
  const action = vi.fn((context) => context.key)
278
- const boundAction = bindActionBySourceAndPerspective(storeDefinition, action)
255
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
279
256
  const instance = createSanityInstance({
280
- projectId: 'proj1',
281
- dataset: 'ds1',
282
257
  perspective: 'published',
283
258
  })
284
259
 
285
- const result = boundAction(instance, {})
260
+ const result = boundAction(instance, {resource: {projectId: 'proj2', dataset: 'ds2'}})
286
261
  expect(result).toEqual(
287
262
  expect.objectContaining({
288
- name: 'proj1.ds1:published',
263
+ name: 'proj2.ds2:published',
289
264
  perspective: 'published',
290
265
  }),
291
266
  )
@@ -300,15 +275,20 @@ describe('bindActionBySourceAndPerspective', () => {
300
275
  context.state.counter += increment
301
276
  return context.state.counter
302
277
  })
303
- const boundAction = bindActionBySourceAndPerspective(storeDefinition, action)
278
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
304
279
  // Use unique project/dataset so we don't reuse stores from other tests
305
- const instance = createSanityInstance({
306
- projectId: 'perspective-isolation',
307
- dataset: 'ds1',
308
- })
280
+ const instance = createSanityInstance()
309
281
 
310
- const resultDrafts = boundAction(instance, {perspective: 'drafts'}, 3)
311
- const resultPublished = boundAction(instance, {perspective: 'published'}, 4)
282
+ const resultDrafts = boundAction(
283
+ instance,
284
+ {resource: {projectId: 'proj3', dataset: 'ds3'}, perspective: 'drafts'},
285
+ 3,
286
+ )
287
+ const resultPublished = boundAction(
288
+ instance,
289
+ {resource: {projectId: 'proj3', dataset: 'ds3'}, perspective: 'published'},
290
+ 4,
291
+ )
312
292
 
313
293
  expect(resultDrafts).toBe(3)
314
294
  expect(resultPublished).toBe(4)
@@ -322,10 +302,11 @@ describe('bindActionBySourceAndPerspective', () => {
322
302
  getInitialState: () => ({counter: 0}),
323
303
  }
324
304
  const action = vi.fn((_context) => 'success')
325
- const boundAction = bindActionBySourceAndPerspective(storeDefinition, action)
326
- const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
305
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
306
+ const instance = createSanityInstance()
327
307
 
328
308
  const result = boundAction(instance, {
309
+ resource: {projectId: 'proj1', dataset: 'ds1'},
329
310
  perspective: {releaseName: 'release1'},
330
311
  })
331
312
  expect(result).toBe('success')
@@ -339,7 +320,24 @@ describe('bindActionBySourceAndPerspective', () => {
339
320
  )
340
321
  })
341
322
 
342
- it('should reuse same store when same source and perspective are used', () => {
323
+ it('should throw when provided a stackable perspective', () => {
324
+ const storeDefinition = {
325
+ name: 'PerspectiveStore',
326
+ getInitialState: () => ({counter: 0}),
327
+ }
328
+ const action = vi.fn((_context) => 'success')
329
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
330
+ const instance = createSanityInstance()
331
+
332
+ expect(() =>
333
+ boundAction(instance, {
334
+ resource: {projectId: 'proj1', dataset: 'ds1'},
335
+ perspective: ['drafts', 'release1'] as unknown as 'drafts',
336
+ }),
337
+ ).toThrow('Stackable perspectives are not supported.')
338
+ })
339
+
340
+ it('should reuse same store when same resource and perspective are used', () => {
343
341
  const storeDefinition = {
344
342
  name: 'PerspectiveStore',
345
343
  getInitialState: () => ({counter: 0}),
@@ -348,15 +346,20 @@ describe('bindActionBySourceAndPerspective', () => {
348
346
  context.state.counter += increment
349
347
  return context.state.counter
350
348
  })
351
- const boundAction = bindActionBySourceAndPerspective(storeDefinition, action)
349
+ const boundAction = bindActionByResourceAndPerspective(storeDefinition, action)
352
350
  // Use unique project/dataset so we don't reuse stores from other tests
353
- const instance = createSanityInstance({
354
- projectId: 'perspective-reuse',
355
- dataset: 'ds1',
356
- })
351
+ const instance = createSanityInstance()
357
352
 
358
- const result1 = boundAction(instance, {perspective: 'drafts'}, 2)
359
- const result2 = boundAction(instance, {perspective: 'drafts'}, 3)
353
+ const result1 = boundAction(
354
+ instance,
355
+ {resource: {projectId: 'proj4', dataset: 'ds4'}, perspective: 'drafts'},
356
+ 2,
357
+ )
358
+ const result2 = boundAction(
359
+ instance,
360
+ {resource: {projectId: 'proj4', dataset: 'ds4'}, perspective: 'drafts'},
361
+ 3,
362
+ )
360
363
 
361
364
  expect(result1).toBe(2)
362
365
  expect(result2).toBe(5)
@@ -1,12 +1,10 @@
1
- import {type ClientPerspective} from '@sanity/client'
2
-
3
1
  import {
4
- type DatasetHandle,
5
- type DocumentSource,
6
- isCanvasSource,
7
- isDatasetSource,
8
- isMediaLibrarySource,
9
- type ReleasePerspective,
2
+ type DocumentResource,
3
+ isCanvasResource,
4
+ isDatasetResource,
5
+ isMediaLibraryResource,
6
+ type PerspectiveHandle,
7
+ type ResourceHandle,
10
8
  } from '../config/sanityConfig'
11
9
  import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
12
10
  import {type SanityInstance} from './createSanityInstance'
@@ -14,19 +12,13 @@ import {createStoreInstance, type StoreInstance} from './createStoreInstance'
14
12
  import {type StoreState} from './createStoreState'
15
13
  import {type StoreContext, type StoreDefinition} from './defineStore'
16
14
 
17
- export interface BoundSourceKey {
15
+ export interface BoundResourceKey {
18
16
  name: string
19
- source: DocumentSource
20
- }
21
- export interface BoundPerspectiveKey extends BoundSourceKey {
22
- perspective: ClientPerspective | ReleasePerspective
17
+ resource: DocumentResource
23
18
  }
24
- export interface BoundDatasetKey {
25
- name: string
26
- projectId: string
27
- dataset: string
19
+ export interface BoundPerspectiveKey extends BoundResourceKey {
20
+ perspective: NonNullable<PerspectiveHandle['perspective']>
28
21
  }
29
-
30
22
  /**
31
23
  * Defines a store action that operates on a specific state type
32
24
  */
@@ -123,95 +115,53 @@ export function createActionBinder<
123
115
  }
124
116
  }
125
117
 
126
- /**
127
- * Binds an action to a store that's scoped to a specific project and dataset
128
- *
129
- * @remarks
130
- * This creates actions that operate on state isolated to a specific projectId and dataset.
131
- * Different project/dataset combinations will have separate states.
132
- *
133
- * @throws Error if projectId or dataset is missing from the Sanity instance config
134
- *
135
- * @example
136
- * ```ts
137
- * // Define a store
138
- * const documentStore = defineStore<DocumentState>({
139
- * name: 'Document',
140
- * getInitialState: () => ({ documents: {} }),
141
- * // ...
142
- * })
143
- *
144
- * // Create dataset-specific actions
145
- * export const fetchDocument = bindActionByDataset(
146
- * documentStore,
147
- * ({instance, state}, documentId) => {
148
- * // This state is isolated to the specific project/dataset
149
- * // ...fetch logic...
150
- * }
151
- * )
152
- *
153
- * // Usage
154
- * fetchDocument(sanityInstance, 'doc123')
155
- * ```
156
- */
157
- export const bindActionByDataset = createActionBinder<
158
- BoundDatasetKey,
159
- [(object & {projectId?: string; dataset?: string})?, ...unknown[]]
160
- >((instance, options) => {
161
- const projectId = options?.projectId ?? instance.config.projectId
162
- const dataset = options?.dataset ?? instance.config.dataset
163
- if (!projectId || !dataset) {
164
- throw new Error('This API requires a project ID and dataset configured.')
165
- }
166
- return {name: `${projectId}.${dataset}`, projectId, dataset}
167
- })
118
+ const resourceKeyName = (resource: DocumentResource): string => {
119
+ if (isDatasetResource(resource)) return `${resource.projectId}.${resource.dataset}`
120
+ if (isMediaLibraryResource(resource)) return `media-library:${resource.mediaLibraryId}`
121
+ if (isCanvasResource(resource)) return `canvas:${resource.canvasId}`
122
+ throw new Error(`Received invalid resource: ${JSON.stringify(resource)}`)
123
+ }
168
124
 
169
- const createSourceKey = (instance: SanityInstance, source?: DocumentSource): BoundSourceKey => {
170
- let name: string | undefined
171
- let sourceForKey: DocumentSource | undefined
172
- if (source) {
173
- sourceForKey = source
174
- if (isDatasetSource(source)) {
175
- name = `${source.projectId}.${source.dataset}`
176
- } else if (isMediaLibrarySource(source)) {
177
- name = `media-library:${source.mediaLibraryId}`
178
- } else if (isCanvasSource(source)) {
179
- name = `canvas:${source.canvasId}`
180
- } else {
181
- throw new Error(`Received invalid source: ${JSON.stringify(source)}`)
182
- }
183
- return {name, source: sourceForKey}
125
+ const createResourceKey = (
126
+ instance: SanityInstance,
127
+ resource?: DocumentResource,
128
+ ): BoundResourceKey => {
129
+ if (resource) {
130
+ return {name: resourceKeyName(resource), resource}
184
131
  }
185
132
 
186
- // TODO: remove reference to instance.config when we get to v3
187
- const {projectId, dataset} = instance.config
188
- if (!projectId || !dataset) {
189
- throw new Error('This API requires a project ID and dataset configured.')
133
+ const defaultResource = instance.config.defaultResource
134
+ if (!defaultResource) {
135
+ throw new Error(
136
+ 'No resource provided and no default resource configured. ' +
137
+ 'Provide a "default" resource in your resources map, or pass an explicit resource.',
138
+ )
190
139
  }
191
- return {name: `${projectId}.${dataset}`, source: {projectId, dataset}}
140
+ return {name: resourceKeyName(defaultResource), resource: defaultResource}
192
141
  }
193
142
 
194
143
  /**
195
- * Binds an action to a store that's scoped to a specific document source.
144
+ * Binds an action to a store that's scoped to a specific document resource.
196
145
  **/
197
- export const bindActionBySource = createActionBinder<
198
- BoundSourceKey,
199
- [{source?: DocumentSource}, ...unknown[]]
200
- >((instance, {source}) => {
201
- return createSourceKey(instance, source)
146
+ export const bindActionByResource = createActionBinder<
147
+ BoundResourceKey,
148
+ [{resource?: DocumentResource}, ...unknown[]]
149
+ >((instance, {resource}) => {
150
+ return createResourceKey(instance, resource)
202
151
  })
203
152
 
204
153
  /**
205
- * Binds an action to a store that's scoped to a specific document source and perspective.
154
+ * Binds an action to a store that's scoped to a specific document resource and perspective.
206
155
  *
207
156
  * @remarks
208
- * This creates actions that operate on state isolated to a specific document source and perspective.
209
- * Different document sources and perspectives will have separate states.
157
+ * This creates actions that operate on state isolated to a specific document resource and perspective.
158
+ * Different document resources and perspectives will have separate states.
210
159
  *
211
160
  * This is mostly useful for stores that do batch fetching operations, since the query store
212
161
  * can isolate single queries by perspective.
213
162
  *
214
- * @throws Error if source or perspective is missing from the Sanity instance config
163
+ * @throws Error if resource or perspective is missing from the Sanity instance config
164
+ * @throws Error if a stackable perspective (array) is provided, as they are not supported
215
165
  *
216
166
  * @example
217
167
  * ```ts
@@ -222,11 +172,11 @@ export const bindActionBySource = createActionBinder<
222
172
  * // ...
223
173
  * })
224
174
  *
225
- * // Create source-and-perspective-specific actions
226
- * export const fetchDocuments = bindActionBySourceAndPerspective(
175
+ * // Create resource-and-perspective-specific actions
176
+ * export const fetchDocuments = bindActionByResourceAndPerspective(
227
177
  * documentStore,
228
178
  * ({instance, state}, documentId) => {
229
- * // This state is isolated to the specific document source and perspective
179
+ * // This state is isolated to the specific document resource and perspective
230
180
  * // ...fetch logic...
231
181
  * }
232
182
  * )
@@ -235,11 +185,11 @@ export const bindActionBySource = createActionBinder<
235
185
  * fetchDocument(sanityInstance, 'doc123')
236
186
  * ```
237
187
  */
238
- export const bindActionBySourceAndPerspective = createActionBinder<
188
+ export const bindActionByResourceAndPerspective = createActionBinder<
239
189
  BoundPerspectiveKey,
240
- [DatasetHandle, ...unknown[]]
190
+ [ResourceHandle, ...unknown[]]
241
191
  >((instance, options): BoundPerspectiveKey => {
242
- const {source, perspective} = options
192
+ const {resource, perspective} = options
243
193
  // TODO: remove reference to instance.config.perspective when we get to v3
244
194
  const utilizedPerspective = perspective ?? instance.config.perspective ?? 'drafts'
245
195
  let perspectiveKey: string
@@ -248,14 +198,15 @@ export const bindActionBySourceAndPerspective = createActionBinder<
248
198
  } else if (typeof utilizedPerspective === 'string') {
249
199
  perspectiveKey = utilizedPerspective
250
200
  } else {
251
- // "StackablePerspective", shouldn't be a common case, but just in case
252
- perspectiveKey = JSON.stringify(utilizedPerspective)
201
+ throw new Error(
202
+ `Stackable perspectives are not supported. Received perspective: ${JSON.stringify(utilizedPerspective)}`,
203
+ )
253
204
  }
254
- const sourceKey = createSourceKey(instance, source)
205
+ const resourceKey = createResourceKey(instance, resource)
255
206
 
256
207
  return {
257
- name: `${sourceKey.name}:${perspectiveKey}`,
258
- source: sourceKey.source,
208
+ name: `${resourceKey.name}:${perspectiveKey}`,
209
+ resource: resourceKey.resource,
259
210
  perspective: utilizedPerspective,
260
211
  }
261
212
  })