@sanity/sdk 0.0.0-alpha.21 → 0.0.0-alpha.23

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 (127) hide show
  1. package/dist/index.d.ts +428 -325
  2. package/dist/index.js +1618 -1553
  3. package/dist/index.js.map +1 -1
  4. package/package.json +6 -7
  5. package/src/_exports/index.ts +31 -30
  6. package/src/auth/authStore.test.ts +149 -104
  7. package/src/auth/authStore.ts +51 -100
  8. package/src/auth/handleAuthCallback.test.ts +67 -34
  9. package/src/auth/handleAuthCallback.ts +8 -7
  10. package/src/auth/logout.test.ts +61 -29
  11. package/src/auth/logout.ts +26 -28
  12. package/src/auth/refreshStampedToken.test.ts +9 -9
  13. package/src/auth/refreshStampedToken.ts +62 -56
  14. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
  15. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
  16. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
  17. package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
  18. package/src/client/clientStore.test.ts +131 -67
  19. package/src/client/clientStore.ts +117 -116
  20. package/src/comlink/controller/actions/destroyController.test.ts +38 -13
  21. package/src/comlink/controller/actions/destroyController.ts +11 -15
  22. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
  23. package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
  24. package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
  25. package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
  26. package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
  27. package/src/comlink/controller/actions/releaseChannel.ts +22 -21
  28. package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
  29. package/src/comlink/controller/comlinkControllerStore.ts +44 -5
  30. package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
  31. package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
  32. package/src/comlink/node/actions/releaseNode.test.ts +75 -55
  33. package/src/comlink/node/actions/releaseNode.ts +19 -21
  34. package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
  35. package/src/comlink/node/comlinkNodeStore.ts +22 -5
  36. package/src/config/authConfig.ts +79 -0
  37. package/src/config/sanityConfig.ts +48 -0
  38. package/src/datasets/datasets.test.ts +2 -2
  39. package/src/datasets/datasets.ts +18 -5
  40. package/src/document/actions.test.ts +22 -10
  41. package/src/document/actions.ts +44 -56
  42. package/src/document/applyDocumentActions.test.ts +96 -36
  43. package/src/document/applyDocumentActions.ts +140 -99
  44. package/src/document/documentStore.test.ts +103 -155
  45. package/src/document/documentStore.ts +247 -237
  46. package/src/document/listen.ts +56 -55
  47. package/src/document/patchOperations.ts +0 -43
  48. package/src/document/permissions.test.ts +25 -12
  49. package/src/document/permissions.ts +11 -4
  50. package/src/document/processActions.test.ts +41 -8
  51. package/src/document/reducers.test.ts +87 -16
  52. package/src/document/reducers.ts +2 -2
  53. package/src/document/sharedListener.test.ts +34 -16
  54. package/src/document/sharedListener.ts +33 -11
  55. package/src/preview/getPreviewState.test.ts +40 -39
  56. package/src/preview/getPreviewState.ts +68 -56
  57. package/src/preview/previewConstants.ts +43 -0
  58. package/src/preview/previewQuery.test.ts +1 -1
  59. package/src/preview/previewQuery.ts +4 -5
  60. package/src/preview/previewStore.test.ts +13 -58
  61. package/src/preview/previewStore.ts +7 -21
  62. package/src/preview/resolvePreview.test.ts +33 -104
  63. package/src/preview/resolvePreview.ts +11 -21
  64. package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
  65. package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
  66. package/src/preview/util.ts +1 -0
  67. package/src/project/project.test.ts +3 -3
  68. package/src/project/project.ts +28 -5
  69. package/src/projection/getProjectionState.test.ts +69 -49
  70. package/src/projection/getProjectionState.ts +42 -50
  71. package/src/projection/projectionQuery.ts +1 -1
  72. package/src/projection/projectionStore.test.ts +13 -51
  73. package/src/projection/projectionStore.ts +6 -18
  74. package/src/projection/resolveProjection.test.ts +32 -127
  75. package/src/projection/resolveProjection.ts +15 -28
  76. package/src/projection/subscribeToStateAndFetchBatches.test.ts +105 -90
  77. package/src/projection/subscribeToStateAndFetchBatches.ts +94 -81
  78. package/src/projection/util.ts +2 -0
  79. package/src/projects/projects.test.ts +13 -4
  80. package/src/projects/projects.ts +6 -1
  81. package/src/query/queryStore.test.ts +10 -47
  82. package/src/query/queryStore.ts +151 -133
  83. package/src/query/queryStoreConstants.ts +2 -0
  84. package/src/store/createActionBinder.test.ts +153 -0
  85. package/src/store/createActionBinder.ts +176 -0
  86. package/src/store/createSanityInstance.test.ts +84 -0
  87. package/src/store/createSanityInstance.ts +124 -0
  88. package/src/store/createStateSourceAction.test.ts +196 -0
  89. package/src/store/createStateSourceAction.ts +260 -0
  90. package/src/store/createStoreInstance.test.ts +81 -0
  91. package/src/store/createStoreInstance.ts +80 -0
  92. package/src/store/createStoreState.test.ts +85 -0
  93. package/src/store/createStoreState.ts +92 -0
  94. package/src/store/defineStore.test.ts +18 -0
  95. package/src/store/defineStore.ts +81 -0
  96. package/src/users/reducers.test.ts +318 -0
  97. package/src/users/reducers.ts +88 -0
  98. package/src/users/types.ts +46 -4
  99. package/src/users/usersConstants.ts +4 -0
  100. package/src/users/usersStore.test.ts +350 -223
  101. package/src/users/usersStore.ts +285 -149
  102. package/src/utils/createFetcherStore.test.ts +6 -7
  103. package/src/utils/createFetcherStore.ts +150 -153
  104. package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
  105. package/src/auth/fetchLoginUrls.test.ts +0 -163
  106. package/src/auth/fetchLoginUrls.ts +0 -74
  107. package/src/common/createLiveEventSubscriber.test.ts +0 -121
  108. package/src/common/createLiveEventSubscriber.ts +0 -55
  109. package/src/common/types.ts +0 -4
  110. package/src/instance/identity.test.ts +0 -46
  111. package/src/instance/identity.ts +0 -29
  112. package/src/instance/sanityInstance.test.ts +0 -77
  113. package/src/instance/sanityInstance.ts +0 -57
  114. package/src/instance/types.ts +0 -37
  115. package/src/preview/getPreviewProjection.ts +0 -45
  116. package/src/resources/README.md +0 -370
  117. package/src/resources/createAction.test.ts +0 -101
  118. package/src/resources/createAction.ts +0 -44
  119. package/src/resources/createResource.test.ts +0 -112
  120. package/src/resources/createResource.ts +0 -102
  121. package/src/resources/createStateSourceAction.test.ts +0 -114
  122. package/src/resources/createStateSourceAction.ts +0 -83
  123. package/src/resources/createStore.test.ts +0 -67
  124. package/src/resources/createStore.ts +0 -46
  125. package/src/store/createStore.test.ts +0 -108
  126. package/src/store/createStore.ts +0 -106
  127. /package/src/{common/util.ts → utils/hashString.ts} +0 -0
@@ -50,7 +50,14 @@ describe('queueTransaction', () => {
50
50
 
51
51
  const transaction: QueuedTransaction = {
52
52
  transactionId: 'txn1',
53
- actions: [{type: 'document.edit', documentId: 'doc1', patches: [{set: {foo: 'bar'}}]}],
53
+ actions: [
54
+ {
55
+ type: 'document.edit',
56
+ documentId: 'doc1',
57
+ documentType: 'book',
58
+ patches: [{set: {foo: 'bar'}}],
59
+ },
60
+ ],
54
61
  }
55
62
 
56
63
  const newState = queueTransaction(initialState, transaction)
@@ -77,7 +84,14 @@ describe('removeQueuedTransaction', () => {
77
84
  queued: [
78
85
  {
79
86
  transactionId: 'txn1',
80
- actions: [{type: 'document.edit', documentId: 'doc1', patches: [{set: {foo: 'bar'}}]}],
87
+ actions: [
88
+ {
89
+ type: 'document.edit',
90
+ documentId: 'doc1',
91
+ documentType: 'book',
92
+ patches: [{set: {foo: 'bar'}}],
93
+ },
94
+ ],
81
95
  },
82
96
  ],
83
97
  applied: [],
@@ -123,13 +137,13 @@ describe('applyFirstQueuedTransaction', () => {
123
137
  })
124
138
 
125
139
  it('returns unchanged state if any required document is not yet loaded', () => {
126
- // If a documents local value is undefined, the reducer should do nothing.
140
+ // If a document's local value is undefined, the reducer should do nothing.
127
141
  const draftId = getDraftId('doc1')
128
142
  const state: SyncTransactionState = {
129
143
  queued: [
130
144
  {
131
145
  transactionId: 'txn2',
132
- actions: [{type: 'document.discard', documentId: 'doc1'}],
146
+ actions: [{type: 'document.discard', documentId: 'doc1', documentType: 'book'}],
133
147
  },
134
148
  ],
135
149
  applied: [],
@@ -148,7 +162,7 @@ describe('applyFirstQueuedTransaction', () => {
148
162
  queued: [
149
163
  {
150
164
  transactionId: 'txn-missing-grants',
151
- actions: [{type: 'document.discard', documentId: 'doc1'}],
165
+ actions: [{type: 'document.discard', documentId: 'doc1', documentType: 'book'}],
152
166
  },
153
167
  ],
154
168
  applied: [],
@@ -171,7 +185,7 @@ describe('applyFirstQueuedTransaction', () => {
171
185
  queued: [
172
186
  {
173
187
  transactionId: 'txn3',
174
- actions: [{type: 'document.discard', documentId: 'doc1'}],
188
+ actions: [{type: 'document.discard', documentId: 'doc1', documentType: 'book'}],
175
189
  },
176
190
  ],
177
191
  applied: [],
@@ -188,7 +202,7 @@ describe('applyFirstQueuedTransaction', () => {
188
202
  expect(newState.queued).toHaveLength(0)
189
203
  // One applied transaction should have been added.
190
204
  expect(newState.applied).toHaveLength(1)
191
- // For a discard action, the drafts local value becomes null.
205
+ // For a discard action, the draft's local value becomes null.
192
206
  expect(newState.documentStates[draftId]?.local).toBeNull()
193
207
  // The published version remains unchanged.
194
208
  expect(newState.documentStates[pubId]?.local).toEqual(initialPub)
@@ -205,8 +219,18 @@ describe('batchAppliedTransactions', () => {
205
219
  const appliedTx: AppliedTransaction = {
206
220
  transactionId: 'txn4',
207
221
  actions: [
208
- {type: 'document.edit', documentId: 'doc1', patches: [{set: {foo: 'a'}}]},
209
- {type: 'document.edit', documentId: 'doc1', patches: [{set: {bar: 'b'}}]},
222
+ {
223
+ type: 'document.edit',
224
+ documentId: 'doc1',
225
+ documentType: 'book',
226
+ patches: [{set: {foo: 'a'}}],
227
+ },
228
+ {
229
+ type: 'document.edit',
230
+ documentId: 'doc1',
231
+ documentType: 'book',
232
+ patches: [{set: {bar: 'b'}}],
233
+ },
210
234
  ],
211
235
  disableBatching: false,
212
236
  outgoingActions: [],
@@ -235,7 +259,14 @@ describe('batchAppliedTransactions', () => {
235
259
  const draftId = getDraftId('doc1')
236
260
  const appliedTx1: AppliedTransaction = {
237
261
  transactionId: 'txn5',
238
- actions: [{type: 'document.edit', documentId: 'doc1', patches: [{set: {foo: 'a'}}]}],
262
+ actions: [
263
+ {
264
+ type: 'document.edit',
265
+ documentId: 'doc1',
266
+ documentType: 'book',
267
+ patches: [{set: {foo: 'a'}}],
268
+ },
269
+ ],
239
270
  disableBatching: false,
240
271
  outgoingActions: [
241
272
  {
@@ -254,7 +285,14 @@ describe('batchAppliedTransactions', () => {
254
285
  }
255
286
  const appliedTx2: AppliedTransaction = {
256
287
  transactionId: 'txn6',
257
- actions: [{type: 'document.edit', documentId: 'doc1', patches: [{set: {bar: 'b'}}]}],
288
+ actions: [
289
+ {
290
+ type: 'document.edit',
291
+ documentId: 'doc1',
292
+ documentType: 'book',
293
+ patches: [{set: {bar: 'b'}}],
294
+ },
295
+ ],
258
296
  disableBatching: false,
259
297
  outgoingActions: [
260
298
  {
@@ -287,7 +325,14 @@ describe('batchAppliedTransactions', () => {
287
325
  const draftId = getDraftId('docA')
288
326
  const appliedTx: AppliedTransaction = {
289
327
  transactionId: 'txn-disable',
290
- actions: [{type: 'document.edit', documentId: 'docA', patches: [{set: {foo: 'a'}}]}],
328
+ actions: [
329
+ {
330
+ type: 'document.edit',
331
+ documentId: 'docA',
332
+ documentType: 'book',
333
+ patches: [{set: {foo: 'a'}}],
334
+ },
335
+ ],
291
336
  disableBatching: true, // already set to true
292
337
  outgoingActions: [],
293
338
  outgoingMutations: [],
@@ -328,7 +373,14 @@ describe('transitionAppliedTransactionsToOutgoing', () => {
328
373
  applied: [
329
374
  {
330
375
  transactionId: 'txn7',
331
- actions: [{type: 'document.edit', documentId: 'doc1', patches: [{set: {foo: 'new'}}]}],
376
+ actions: [
377
+ {
378
+ type: 'document.edit',
379
+ documentId: 'doc1',
380
+ documentType: 'book',
381
+ patches: [{set: {foo: 'new'}}],
382
+ },
383
+ ],
332
384
  disableBatching: false,
333
385
  outgoingActions: [],
334
386
  outgoingMutations: [],
@@ -381,7 +433,14 @@ describe('cleanupOutgoingTransaction', () => {
381
433
  applied: [],
382
434
  outgoing: {
383
435
  transactionId: 'txn8',
384
- actions: [{type: 'document.edit', documentId: 'doc1', patches: [{set: {foo: 'x'}}]}],
436
+ actions: [
437
+ {
438
+ type: 'document.edit',
439
+ documentId: 'doc1',
440
+ documentType: 'book',
441
+ patches: [{set: {foo: 'x'}}],
442
+ },
443
+ ],
385
444
  disableBatching: false,
386
445
  batchedTransactionIds: ['txn8'],
387
446
  outgoingActions: [],
@@ -417,7 +476,12 @@ describe('revertOutgoingTransaction', () => {
417
476
  {
418
477
  transactionId: 'txn9',
419
478
  actions: [
420
- {type: 'document.edit', documentId: 'doc1', patches: [{set: {foo: 'reverted'}}]},
479
+ {
480
+ type: 'document.edit',
481
+ documentId: 'doc1',
482
+ documentType: 'book',
483
+ patches: [{set: {foo: 'reverted'}}],
484
+ },
421
485
  ],
422
486
  disableBatching: false,
423
487
  outgoingActions: [],
@@ -431,7 +495,14 @@ describe('revertOutgoingTransaction', () => {
431
495
  ],
432
496
  outgoing: {
433
497
  transactionId: 'txnOut',
434
- actions: [{type: 'document.edit', documentId: 'doc1', patches: [{set: {foo: 'changed'}}]}],
498
+ actions: [
499
+ {
500
+ type: 'document.edit',
501
+ documentId: 'doc1',
502
+ documentType: 'book',
503
+ patches: [{set: {foo: 'changed'}}],
504
+ },
505
+ ],
435
506
  disableBatching: false,
436
507
  batchedTransactionIds: ['txnOut'],
437
508
  outgoingActions: [],
@@ -2,7 +2,7 @@ import {getPublishedId} from '@sanity/client/csm'
2
2
  import {type Mutation, type PatchOperations, type SanityDocumentLike} from '@sanity/types'
3
3
  import {omit} from 'lodash-es'
4
4
 
5
- import {type ResourceState} from '../resources/createResource'
5
+ import {type StoreContext} from '../store/defineStore'
6
6
  import {getDraftId, insecureRandomId} from '../utils/ids'
7
7
  import {type DocumentAction} from './actions'
8
8
  import {DOCUMENT_STATE_CLEAR_DELAY} from './documentConstants'
@@ -548,7 +548,7 @@ export function removeSubscriptionIdFromDocument(
548
548
  }
549
549
 
550
550
  export function manageSubscriberIds(
551
- state: ResourceState<SyncTransactionState>,
551
+ {state}: StoreContext<SyncTransactionState>,
552
552
  documentId: string | string[],
553
553
  ): () => void {
554
554
  const documentIds = Array.from(
@@ -12,8 +12,8 @@ import {bufferTime, catchError, toArray} from 'rxjs/operators'
12
12
  import {afterEach, beforeEach, describe, expect, it, type Mock, vi} from 'vitest'
13
13
 
14
14
  import {getClientState} from '../client/clientStore'
15
- import {createSanityInstance} from '../instance/sanityInstance'
16
- import {type StateSource} from '../resources/createStateSourceAction'
15
+ import {createSanityInstance} from '../store/createSanityInstance'
16
+ import {type StateSource} from '../store/createStateSourceAction'
17
17
  import {createFetchDocument, createSharedListener} from './sharedListener'
18
18
 
19
19
  const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
@@ -28,7 +28,7 @@ describe('createSharedListener', () => {
28
28
  beforeEach(() => {
29
29
  // Create a subject to simulate the events coming from client.listen.
30
30
  fakeListenSubject = new Subject()
31
- // Create a fake client whose listen() method returns our subjects observable.
31
+ // Create a fake client whose listen() method returns our subject's observable.
32
32
  fakeClient = {
33
33
  listen: vi.fn(() => fakeListenSubject.asObservable()),
34
34
  } as unknown as SanityClient
@@ -43,7 +43,7 @@ describe('createSharedListener', () => {
43
43
  })
44
44
 
45
45
  it('should call client.listen with the expected parameters', () => {
46
- createSharedListener(instance).subscribe()
46
+ createSharedListener(instance).events.subscribe()
47
47
  expect(fakeClient.listen).toHaveBeenCalledTimes(1)
48
48
  expect(fakeClient.listen).toHaveBeenCalledWith(
49
49
  '*',
@@ -62,9 +62,9 @@ describe('createSharedListener', () => {
62
62
  const mutationEvent: MutationEvent = {type: 'mutation'} as MutationEvent
63
63
  const reconnectEvent: ReconnectEvent = {type: 'reconnect'} as ReconnectEvent
64
64
 
65
- const shared$ = createSharedListener(instance)
65
+ const sharedListener = createSharedListener(instance)
66
66
  // Start collecting the emitted events.
67
- const eventsPromise = lastValueFrom(shared$.pipe(toArray()))
67
+ const eventsPromise = lastValueFrom(sharedListener.events.pipe(toArray()))
68
68
 
69
69
  // Emit events in order.
70
70
  fakeListenSubject.next(welcomeEvent)
@@ -80,16 +80,16 @@ describe('createSharedListener', () => {
80
80
  it('should replay the welcome event for new subscribers', async () => {
81
81
  const welcomeEvent: WelcomeEvent = {type: 'welcome', listenerName: 'listener'}
82
82
 
83
- const shared$ = createSharedListener(instance)
83
+ const sharedListener = createSharedListener(instance)
84
84
  // First subscription: emit welcome and complete.
85
- const firstPromise = lastValueFrom(shared$.pipe(toArray()))
85
+ const firstPromise = lastValueFrom(sharedListener.events.pipe(toArray()))
86
86
  fakeListenSubject.next(welcomeEvent)
87
87
  fakeListenSubject.complete()
88
88
  const firstEvents = await firstPromise
89
89
  expect(firstEvents).toEqual([welcomeEvent])
90
90
 
91
91
  // New subscriber should immediately receive the replayed welcome event.
92
- const secondEvents = await lastValueFrom(shared$.pipe(toArray()))
92
+ const secondEvents = await lastValueFrom(sharedListener.events.pipe(toArray()))
93
93
  expect(secondEvents).toEqual([welcomeEvent])
94
94
  })
95
95
 
@@ -97,8 +97,8 @@ describe('createSharedListener', () => {
97
97
  const mutationEvent = {type: 'mutation'} as MutationEvent
98
98
  const reconnectEvent = {type: 'reconnect'} as ReconnectEvent
99
99
 
100
- const shared$ = createSharedListener(instance)
101
- const eventsPromise = lastValueFrom(shared$.pipe(toArray()))
100
+ const sharedListener = createSharedListener(instance)
101
+ const eventsPromise = lastValueFrom(sharedListener.events.pipe(toArray()))
102
102
  fakeListenSubject.next(mutationEvent)
103
103
  fakeListenSubject.next(reconnectEvent)
104
104
  fakeListenSubject.complete()
@@ -110,11 +110,11 @@ describe('createSharedListener', () => {
110
110
  const welcomeEvent = {type: 'welcome'} as WelcomeEvent
111
111
  const mutationEvent = {type: 'mutation'} as MutationEvent
112
112
 
113
- const shared$ = createSharedListener(instance)
113
+ const sharedListener = createSharedListener(instance)
114
114
 
115
115
  // Subscribe two observers concurrently.
116
- const subscriber1 = shared$.pipe(bufferTime(0))
117
- const subscriber2 = shared$.pipe(bufferTime(0))
116
+ const subscriber1 = sharedListener.events.pipe(bufferTime(0))
117
+ const subscriber2 = sharedListener.events.pipe(bufferTime(0))
118
118
  const combined = forkJoin([subscriber1, subscriber2])
119
119
 
120
120
  const result = lastValueFrom(combined)
@@ -133,9 +133,9 @@ describe('createSharedListener', () => {
133
133
 
134
134
  it('should propagate errors from the underlying client.listen observable', async () => {
135
135
  const errorMessage = 'Test error'
136
- const shared$ = createSharedListener(instance)
136
+ const sharedListener = createSharedListener(instance)
137
137
 
138
- const error$ = shared$.pipe(
138
+ const error$ = sharedListener.events.pipe(
139
139
  toArray(),
140
140
  catchError((err) => of(err)),
141
141
  )
@@ -145,6 +145,24 @@ describe('createSharedListener', () => {
145
145
  expect(result).toBeInstanceOf(Error)
146
146
  expect(result.message).toBe(errorMessage)
147
147
  })
148
+
149
+ it('should stop emitting events after calling dispose', async () => {
150
+ const welcomeEvent: WelcomeEvent = {type: 'welcome', listenerName: 'listener'}
151
+ const mutationEvent: MutationEvent = {type: 'mutation'} as MutationEvent
152
+ const sharedListener = createSharedListener(instance)
153
+
154
+ const events: ListenEvent<SanityDocument>[] = []
155
+ const subscription = sharedListener.events.subscribe((event) => {
156
+ events.push(event)
157
+ })
158
+
159
+ fakeListenSubject.next(welcomeEvent)
160
+ sharedListener.dispose()
161
+ fakeListenSubject.next(mutationEvent)
162
+ await new Promise((resolve) => setTimeout(resolve, 0))
163
+ expect(events).toEqual([welcomeEvent])
164
+ subscription.unsubscribe()
165
+ })
148
166
  })
149
167
 
150
168
  describe('createFetchDocument', () => {
@@ -1,16 +1,33 @@
1
1
  import {type ListenEvent, type SanityDocument} from '@sanity/client'
2
2
  import {createDocumentLoaderFromClient} from '@sanity/mutate/_unstable_store'
3
- import {first, map, merge, Observable, partition, share, shareReplay, switchMap} from 'rxjs'
3
+ import {
4
+ first,
5
+ map,
6
+ merge,
7
+ Observable,
8
+ partition,
9
+ share,
10
+ shareReplay,
11
+ Subject,
12
+ switchMap,
13
+ takeUntil,
14
+ } from 'rxjs'
4
15
 
5
16
  import {getClientState} from '../client/clientStore'
6
- import {type SanityInstance} from '../instance/types'
17
+ import {type SanityInstance} from '../store/createSanityInstance'
7
18
 
8
19
  const API_VERSION = 'vX'
9
20
 
10
- export function createSharedListener(
11
- instance: SanityInstance,
12
- ): Observable<ListenEvent<SanityDocument>> {
13
- const events$ = getClientState(instance, {apiVersion: API_VERSION}).observable.pipe(
21
+ export interface SharedListener {
22
+ events: Observable<ListenEvent<SanityDocument>>
23
+ dispose: () => void
24
+ }
25
+
26
+ export function createSharedListener(instance: SanityInstance): SharedListener {
27
+ const dispose$ = new Subject<void>()
28
+ const events$ = getClientState(instance, {
29
+ apiVersion: API_VERSION,
30
+ }).observable.pipe(
14
31
  switchMap((client) =>
15
32
  // TODO: it seems like the client.listen method is not emitting disconnected
16
33
  // events. this is important to ensure we have an up to date version of the
@@ -29,15 +46,20 @@ export function createSharedListener(
29
46
  },
30
47
  ),
31
48
  ),
49
+ takeUntil(dispose$),
32
50
  share(),
33
51
  )
34
52
 
35
53
  const [welcome$, mutation$] = partition(events$, (e) => e.type === 'welcome')
36
- return merge(
37
- // we replay the welcome event because that event kicks off fetching the document
38
- welcome$.pipe(shareReplay(1)),
39
- mutation$,
40
- )
54
+
55
+ return {
56
+ events: merge(
57
+ // we replay the welcome event because that event kicks off fetching the document
58
+ welcome$.pipe(shareReplay(1)),
59
+ mutation$,
60
+ ),
61
+ dispose: () => dispose$.next(),
62
+ }
41
63
  }
42
64
 
43
65
  export function createFetchDocument(instance: SanityInstance) {
@@ -1,14 +1,12 @@
1
- import {describe, it, type Mock} from 'vitest'
2
-
3
- import {createSanityInstance} from '../instance/sanityInstance'
4
- import {
5
- createResourceState,
6
- getOrCreateResource,
7
- type ResourceState,
8
- } from '../resources/createResource'
1
+ import {NEVER} from 'rxjs'
2
+ import {describe, it} from 'vitest'
3
+
4
+ import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
5
+ import {type StoreState} from '../store/createStoreState'
9
6
  import {insecureRandomId} from '../utils/ids'
10
7
  import {getPreviewState} from './getPreviewState'
11
- import {previewStore, type PreviewStoreState} from './previewStore'
8
+ import {type PreviewStoreState} from './previewStore'
9
+ import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
12
10
  import {STABLE_EMPTY_PREVIEW} from './util'
13
11
 
14
12
  vi.mock('../utils/ids', async (importOriginal) => {
@@ -16,36 +14,36 @@ vi.mock('../utils/ids', async (importOriginal) => {
16
14
  return {...util, insecureRandomId: vi.fn(util.insecureRandomId)}
17
15
  })
18
16
 
19
- vi.mock('../resources/createResource', async (importOriginal) => {
20
- const original = await importOriginal<typeof import('../resources/createResource')>()
21
- return {...original, getOrCreateResource: vi.fn()}
22
- })
17
+ vi.mock('./subscribeToStateAndFetchBatches.ts')
23
18
 
24
19
  describe('getPreviewState', () => {
25
- const instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
26
- const document = {_id: 'exampleId', _type: 'exampleType'}
27
- const initialState: PreviewStoreState = {
28
- documentTypes: {},
29
- lastLiveEventId: null,
30
- subscriptions: {},
31
- syncTags: {},
32
- values: {},
33
- }
34
- let state: ResourceState<PreviewStoreState>
20
+ let instance: SanityInstance
21
+ const docHandle = {documentId: 'exampleId', documentType: 'exampleType'}
22
+ let state: StoreState<PreviewStoreState & {extra?: unknown}>
35
23
 
36
24
  beforeEach(() => {
37
- state = createResourceState(initialState)
25
+ // capture state
26
+ vi.mocked(subscribeToStateAndFetchBatches).mockImplementation((context) => {
27
+ state = context.state
28
+ return NEVER.subscribe()
29
+ })
30
+
31
+ instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
32
+ })
33
+
34
+ afterEach(() => {
35
+ instance.dispose()
38
36
  })
39
37
 
40
38
  it('returns a state source that emits when the preview value changes', () => {
41
- const previewState = getPreviewState({state, instance}, {document})
39
+ const previewState = getPreviewState(instance, docHandle)
42
40
  expect(previewState.getCurrent()).toBe(STABLE_EMPTY_PREVIEW)
43
41
 
44
42
  const subscriber = vi.fn()
45
43
  previewState.subscribe(subscriber)
46
44
 
47
45
  // emit unrelated state changes
48
- state.set('updateLastLiveEventId', {lastLiveEventId: 'newLastLiveEventId'})
46
+ state.set('updateLastLiveEventId', {extra: 'unrelated change'})
49
47
  expect(subscriber).toHaveBeenCalledTimes(0)
50
48
 
51
49
  state.set('relatedChange', (prev) => ({
@@ -68,7 +66,7 @@ describe('getPreviewState', () => {
68
66
  })
69
67
 
70
68
  it('adds a subscription ID and document type to the state on subscription', () => {
71
- const previewState = getPreviewState({state, instance}, {document})
69
+ const previewState = getPreviewState(instance, docHandle)
72
70
 
73
71
  expect(state.get().subscriptions).toEqual({})
74
72
  vi.mocked(insecureRandomId)
@@ -92,28 +90,31 @@ describe('getPreviewState', () => {
92
90
  })
93
91
 
94
92
  it('resets to pending false on unsubscribe if the subscription is the last one', () => {
93
+ const previewState = getPreviewState(instance, docHandle)
94
+
95
95
  state.set('presetValueToPending', (prev) => ({
96
- values: {...prev.values, [document._id]: {data: {title: 'Foo'}, isPending: true}},
96
+ values: {...prev.values, [docHandle.documentId]: {data: {title: 'Foo'}, isPending: true}},
97
97
  }))
98
98
 
99
- const previewState = getPreviewState({state, instance}, {document})
100
-
101
99
  const unsubscribe1 = previewState.subscribe(vi.fn())
102
100
  const unsubscribe2 = previewState.subscribe(vi.fn())
103
101
 
104
- expect(state.get().values[document._id]).toEqual({data: {title: 'Foo'}, isPending: true})
102
+ expect(state.get().values[docHandle.documentId]).toEqual({
103
+ data: {title: 'Foo'},
104
+ isPending: true,
105
+ })
105
106
 
106
107
  unsubscribe1()
107
- expect(state.get().values[document._id]).toEqual({data: {title: 'Foo'}, isPending: true})
108
+ expect(state.get().values[docHandle.documentId]).toEqual({
109
+ data: {title: 'Foo'},
110
+ isPending: true,
111
+ })
108
112
 
109
113
  unsubscribe2()
110
114
  expect(state.get().subscriptions).toEqual({})
111
- expect(state.get().values[document._id]).toEqual({data: {title: 'Foo'}, isPending: false})
112
- })
113
-
114
- it('calls getOrCreateResource if no state is provided', () => {
115
- ;(getOrCreateResource as Mock).mockReturnValue({state})
116
- getPreviewState(instance, {document})
117
- expect(getOrCreateResource).toHaveBeenCalledWith(instance, previewStore)
115
+ expect(state.get().values[docHandle.documentId]).toEqual({
116
+ data: {title: 'Foo'},
117
+ isPending: false,
118
+ })
118
119
  })
119
120
  })