@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
@@ -1,52 +1,52 @@
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 {getProjectionState} from './getProjectionState'
11
- import {projectionStore, type ProjectionStoreState} from './projectionStore'
12
- import {STABLE_EMPTY_PROJECTION} from './util'
8
+ import {type ProjectionStoreState} from './projectionStore'
9
+ import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
10
+ import {PROJECTION_STATE_CLEAR_DELAY, STABLE_EMPTY_PROJECTION} from './util'
13
11
 
14
12
  vi.mock('../utils/ids', async (importOriginal) => {
15
13
  const util = await importOriginal<typeof import('../utils/ids')>()
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('getProjectionState', () => {
25
- const instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
26
- const document = {_id: 'exampleId', _type: 'exampleType'}
20
+ let instance: SanityInstance
21
+ const docHandle = {documentId: 'exampleId', documentType: 'exampleType'}
27
22
  const projection = '{exampleProjection}'
28
- const initialState: ProjectionStoreState = {
29
- values: {},
30
- documentProjections: {},
31
- subscriptions: {},
32
- syncTags: {},
33
- lastLiveEventId: null,
34
- }
35
- let state: ResourceState<ProjectionStoreState>
23
+ let state: StoreState<ProjectionStoreState & {extra?: unknown}>
36
24
 
37
25
  beforeEach(() => {
38
- state = createResourceState(initialState)
26
+ // capture state
27
+ vi.mocked(subscribeToStateAndFetchBatches).mockImplementation((context) => {
28
+ state = context.state
29
+ return NEVER.subscribe()
30
+ })
31
+
32
+ instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
33
+ vi.useFakeTimers() // Enable fake timers for each test
34
+ })
35
+
36
+ afterEach(() => {
37
+ instance.dispose()
38
+ vi.useRealTimers() // Restore real timers after each test
39
39
  })
40
40
 
41
41
  it('returns a state source that emits when the projection value changes', () => {
42
- const projectionState = getProjectionState({state, instance}, {document, projection})
42
+ const projectionState = getProjectionState(instance, {projection, ...docHandle})
43
43
  expect(projectionState.getCurrent()).toBe(STABLE_EMPTY_PROJECTION)
44
44
 
45
45
  const subscriber = vi.fn()
46
46
  projectionState.subscribe(subscriber)
47
47
 
48
48
  // emit unrelated state changes
49
- state.set('updateLastLiveEventId', {lastLiveEventId: 'newLastLiveEventId'})
49
+ state.set('updateLastLiveEventId', {extra: 'unrelated change'})
50
50
  expect(subscriber).toHaveBeenCalledTimes(0)
51
51
 
52
52
  state.set('relatedChange', (prev) => ({
@@ -69,55 +69,75 @@ describe('getProjectionState', () => {
69
69
  })
70
70
 
71
71
  it('adds a subscription ID and projection to the state on subscription', () => {
72
- const projectionState = getProjectionState({state, instance}, {document, projection})
72
+ const projectionState = getProjectionState(instance, {projection, ...docHandle})
73
73
 
74
74
  expect(state.get().subscriptions).toEqual({})
75
- vi.mocked(insecureRandomId)
76
- .mockImplementationOnce(() => 'pseudoRandomId1')
77
- .mockImplementationOnce(() => 'pseudoRandomId2')
75
+ vi.mocked(insecureRandomId).mockImplementationOnce(() => 'pseudoRandomId1')
78
76
 
79
77
  const unsubscribe1 = projectionState.subscribe(vi.fn())
80
- const unsubscribe2 = projectionState.subscribe(vi.fn())
81
78
 
82
79
  expect(state.get().subscriptions).toEqual({
83
- exampleId: {pseudoRandomId1: true, pseudoRandomId2: true},
80
+ exampleId: {pseudoRandomId1: true},
84
81
  })
85
82
  expect(state.get().documentProjections).toEqual({
86
83
  exampleId: projection,
87
84
  })
88
85
 
89
- unsubscribe2()
86
+ // Unsubscribe the last one - state should NOT clear immediately
87
+ unsubscribe1()
90
88
  expect(state.get().subscriptions).toEqual({
91
89
  exampleId: {pseudoRandomId1: true},
92
90
  })
91
+ // Projection data might also remain initially
92
+ expect(state.get().documentProjections).toEqual({
93
+ exampleId: projection,
94
+ })
93
95
 
94
- unsubscribe1()
96
+ // Advance timers past the clear delay
97
+ vi.advanceTimersByTime(PROJECTION_STATE_CLEAR_DELAY)
98
+
99
+ // NOW the state related to this document should be cleared
95
100
  expect(state.get().subscriptions).toEqual({})
101
+ expect(state.get().documentProjections).toEqual({exampleId: projection})
96
102
  })
97
103
 
98
104
  it('resets to pending false on unsubscribe if the subscription is the last one', () => {
105
+ const projectionState = getProjectionState(instance, {projection, ...docHandle})
106
+
99
107
  state.set('presetValueToPending', (prev) => ({
100
- values: {...prev.values, [document._id]: {data: {field: 'Foo'}, isPending: true}},
108
+ values: {...prev.values, [docHandle.documentId]: {data: {field: 'Foo'}, isPending: true}},
101
109
  }))
102
110
 
103
- const projectionState = getProjectionState({state, instance}, {document, projection})
104
-
105
111
  const unsubscribe1 = projectionState.subscribe(vi.fn())
106
- const unsubscribe2 = projectionState.subscribe(vi.fn())
107
112
 
108
- expect(state.get().values[document._id]).toEqual({data: {field: 'Foo'}, isPending: true})
113
+ expect(state.get().values[docHandle.documentId]).toEqual({
114
+ data: {field: 'Foo'},
115
+ isPending: true,
116
+ })
109
117
 
118
+ // Unsubscribe one - pending state remains
110
119
  unsubscribe1()
111
- expect(state.get().values[document._id]).toEqual({data: {field: 'Foo'}, isPending: true})
120
+ expect(state.get().values[docHandle.documentId]).toEqual({
121
+ data: {field: 'Foo'},
122
+ isPending: true,
123
+ })
112
124
 
113
- unsubscribe2()
114
- expect(state.get().subscriptions).toEqual({})
115
- expect(state.get().values[document._id]).toEqual({data: {field: 'Foo'}, isPending: false})
116
- })
125
+ // Unsubscribe the last one - pending state should NOT reset immediately
126
+ expect(Object.keys(state.get().subscriptions['exampleId'] || {}).length).toBeGreaterThan(0)
127
+ expect(state.get().values[docHandle.documentId]).toEqual({
128
+ data: {field: 'Foo'},
129
+ isPending: true, // Still pending
130
+ })
131
+
132
+ // Advance timers past the clear delay
133
+ vi.advanceTimersByTime(PROJECTION_STATE_CLEAR_DELAY)
117
134
 
118
- it('calls getOrCreateResource if no state is provided', () => {
119
- ;(getOrCreateResource as Mock).mockReturnValue({state})
120
- getProjectionState(instance, {document, projection})
121
- expect(getOrCreateResource).toHaveBeenCalledWith(instance, projectionStore)
135
+ // NOW the pending state should be reset
136
+ expect(state.get().values[docHandle.documentId]).toEqual({
137
+ data: {field: 'Foo'},
138
+ isPending: false, // Reset to false
139
+ })
140
+ // And subscriptions should be cleared now
141
+ expect(state.get().subscriptions).toEqual({})
122
142
  })
123
143
  })
@@ -1,9 +1,13 @@
1
1
  import {omit} from 'lodash-es'
2
2
 
3
- import {type DocumentHandle} from '../document/patchOperations'
4
- import {type SanityInstance} from '../instance/types'
5
- import {type ActionContext, createAction} from '../resources/createAction'
6
- import {createStateSourceAction, type StateSource} from '../resources/createStateSourceAction'
3
+ import {type DocumentHandle} from '../config/sanityConfig'
4
+ import {bindActionByDataset} from '../store/createActionBinder'
5
+ import {type SanityInstance} from '../store/createSanityInstance'
6
+ import {
7
+ createStateSourceAction,
8
+ type SelectorContext,
9
+ type StateSource,
10
+ } from '../store/createStateSourceAction'
7
11
  import {getPublishedId, insecureRandomId} from '../utils/ids'
8
12
  import {
9
13
  projectionStore,
@@ -11,31 +15,24 @@ import {
11
15
  type ProjectionValuePending,
12
16
  type ValidProjection,
13
17
  } from './projectionStore'
14
- import {STABLE_EMPTY_PROJECTION, validateProjection} from './util'
18
+ import {PROJECTION_STATE_CLEAR_DELAY, STABLE_EMPTY_PROJECTION, validateProjection} from './util'
15
19
 
16
- interface GetProjectionStateOptions {
17
- document: DocumentHandle
20
+ interface GetProjectionStateOptions extends DocumentHandle {
18
21
  projection: ValidProjection
19
22
  }
20
23
 
21
- const getProjectStateSourceAction = createStateSourceAction(
22
- projectionStore,
23
- (state, {document}: GetProjectionStateOptions): ProjectionValuePending<object> =>
24
- state.values[document._id] ?? STABLE_EMPTY_PROJECTION,
25
- )
26
-
27
24
  /**
28
25
  * @beta
29
26
  */
30
27
  export function getProjectionState<TResult extends object>(
31
- instance: SanityInstance | ActionContext<ProjectionStoreState>,
28
+ instance: SanityInstance,
32
29
  options: GetProjectionStateOptions,
33
30
  ): StateSource<ProjectionValuePending<TResult>>
34
31
  /**
35
32
  * @beta
36
33
  */
37
34
  export function getProjectionState(
38
- instance: SanityInstance | ActionContext<ProjectionStoreState>,
35
+ instance: SanityInstance,
39
36
  options: GetProjectionStateOptions,
40
37
  ): StateSource<ProjectionValuePending<Record<string, unknown>>>
41
38
  /**
@@ -50,39 +47,34 @@ export function getProjectionState(
50
47
  /**
51
48
  * @beta
52
49
  */
53
- export const _getProjectionState = createAction(projectionStore, ({state}) => {
54
- return function ({
55
- document,
56
- projection,
57
- }: GetProjectionStateOptions): StateSource<ProjectionValuePending<object>> {
58
- const {_id} = document
59
- const documentId = getPublishedId(_id)
60
- const projectionState = getProjectStateSourceAction(this, {document, projection})
61
-
62
- return {
63
- ...projectionState,
64
- subscribe: (subscriber) => {
65
- const subscriptionId = insecureRandomId()
50
+ export const _getProjectionState = bindActionByDataset(
51
+ projectionStore,
52
+ createStateSourceAction({
53
+ selector: (
54
+ {state}: SelectorContext<ProjectionStoreState>,
55
+ options: GetProjectionStateOptions,
56
+ ): ProjectionValuePending<object> =>
57
+ state.values[options.documentId] ?? STABLE_EMPTY_PROJECTION,
58
+ onSubscribe: ({state}, {projection, ...docHandle}: GetProjectionStateOptions) => {
59
+ const subscriptionId = insecureRandomId()
60
+ const documentId = getPublishedId(docHandle.documentId)
66
61
 
67
- state.set('addSubscription', (prev) => ({
68
- documentProjections: {
69
- ...prev.documentProjections,
70
- [documentId]: validateProjection(projection),
71
- },
72
- subscriptions: {
73
- ...prev.subscriptions,
74
- [documentId]: {
75
- ...prev.subscriptions[documentId],
76
- [subscriptionId]: true,
77
- },
62
+ state.set('addSubscription', (prev) => ({
63
+ documentProjections: {
64
+ ...prev.documentProjections,
65
+ [documentId]: validateProjection(projection),
66
+ },
67
+ subscriptions: {
68
+ ...prev.subscriptions,
69
+ [documentId]: {
70
+ ...prev.subscriptions[documentId],
71
+ [subscriptionId]: true,
78
72
  },
79
- }))
80
-
81
- const unsubscribe = projectionState.subscribe(subscriber)
82
-
83
- return () => {
84
- unsubscribe()
73
+ },
74
+ }))
85
75
 
76
+ return () => {
77
+ setTimeout(() => {
86
78
  state.set('removeSubscription', (prev): Partial<ProjectionStoreState> => {
87
79
  const documentSubscriptions = omit(prev.subscriptions[documentId], subscriptionId)
88
80
  const hasSubscribers = !!Object.keys(documentSubscriptions).length
@@ -98,8 +90,8 @@ export const _getProjectionState = createAction(projectionStore, ({state}) => {
98
90
  : {...prev.values, [documentId]: {data: projectionValue, isPending: false}},
99
91
  }
100
92
  })
101
- }
102
- },
103
- }
104
- }
105
- })
93
+ }, PROJECTION_STATE_CLEAR_DELAY)
94
+ }
95
+ },
96
+ }),
97
+ )
@@ -1,4 +1,4 @@
1
- import {hashString} from '../common/util'
1
+ import {hashString} from '../utils/hashString'
2
2
  import {getDraftId, getPublishedId} from '../utils/ids'
3
3
  import {type ProjectionValuePending, type ValidProjection} from './projectionStore'
4
4
  import {validateProjection} from './util'
@@ -1,11 +1,10 @@
1
- import {type Subscription} from 'rxjs'
2
- import {describe, expect, it, type Mock, vi} from 'vitest'
1
+ import {Observable} from 'rxjs'
2
+ import {describe, it, vi} from 'vitest'
3
3
 
4
- import {createSanityInstance} from '../instance/sanityInstance'
5
- import {createResourceState} from '../resources/createResource'
4
+ import {createSanityInstance} from '../store/createSanityInstance'
5
+ import {createStoreInstance} from '../store/createStoreInstance'
6
6
  import {projectionStore} from './projectionStore'
7
7
  import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
8
- import {PROJECTION_TAG} from './util'
9
8
 
10
9
  // Mock the module with a factory function
11
10
  vi.mock('../common/createLiveEventSubscriber', () => {
@@ -15,61 +14,24 @@ vi.mock('../common/createLiveEventSubscriber', () => {
15
14
  }
16
15
  })
17
16
 
18
- vi.mock('./subscribeToStateAndFetchBatches', () => ({
19
- subscribeToStateAndFetchBatches: vi.fn(),
20
- }))
17
+ vi.mock('./subscribeToStateAndFetchBatches')
21
18
 
22
19
  describe('projectionStore', () => {
23
20
  it('is a resource that initializes with state and subscriptions', async () => {
24
- const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
25
- const initialState = projectionStore.getInitialState(instance)
26
-
27
- expect(initialState).toEqual({
28
- values: {},
29
- documentProjections: {},
30
- subscriptions: {},
31
- syncTags: {},
32
- lastLiveEventId: null,
33
- })
34
-
35
- const liveUnsubscribe = vi.fn()
36
- const stateUnsubscribe = vi.fn()
37
-
38
- const {createLiveEventSubscriber} = vi.mocked(
39
- await import('../common/createLiveEventSubscriber'),
21
+ const teardown = vi.fn()
22
+ const subscriber = vi.fn().mockReturnValue(teardown)
23
+ vi.mocked(subscribeToStateAndFetchBatches).mockReturnValue(
24
+ new Observable(subscriber).subscribe(),
40
25
  )
41
- const mockLiveSubscriber = vi
42
- .mocked(createLiveEventSubscriber(PROJECTION_TAG))
43
- .mockReturnValue({
44
- unsubscribe: liveUnsubscribe,
45
- } as unknown as Subscription)
46
-
47
- ;(subscribeToStateAndFetchBatches as Mock).mockImplementation(() => ({
48
- unsubscribe: stateUnsubscribe,
49
- }))
50
26
 
51
- const dispose = projectionStore.initialize!.call(
52
- {
53
- instance,
54
- state: createResourceState(initialState),
55
- },
56
- instance,
57
- )
58
-
59
- // Verify the factory was called with the correct tag
60
- expect(createLiveEventSubscriber).toHaveBeenCalledWith(PROJECTION_TAG)
27
+ const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
61
28
 
62
- // Verify the subscriber was called with the correct context
63
- expect(mockLiveSubscriber).toHaveBeenCalledWith({
64
- instance,
65
- state: expect.any(Object),
66
- })
29
+ const {state, dispose} = createStoreInstance(instance, projectionStore)
67
30
 
68
31
  expect(subscribeToStateAndFetchBatches).toHaveBeenCalledOnce()
32
+ expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({instance, state})
69
33
 
70
34
  dispose()
71
-
72
- expect(liveUnsubscribe).toHaveBeenCalledOnce()
73
- expect(stateUnsubscribe).toHaveBeenCalledOnce()
35
+ instance.dispose()
74
36
  })
75
37
  })
@@ -1,8 +1,5 @@
1
- import {createLiveEventSubscriber} from '../common/createLiveEventSubscriber'
2
- import {type LiveEventAwareState} from '../common/types'
3
- import {createResource} from '../resources/createResource'
1
+ import {defineStore} from '../store/defineStore'
4
2
  import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
5
- import {PROJECTION_TAG} from './util'
6
3
 
7
4
  /**
8
5
  * @beta
@@ -24,32 +21,23 @@ export interface ProjectionValuePending<TValue extends object> {
24
21
  isPending: boolean
25
22
  }
26
23
 
27
- export interface ProjectionStoreState<TValue extends object = object> extends LiveEventAwareState {
24
+ export interface ProjectionStoreState<TValue extends object = object> {
28
25
  values: {[TDocumentId in string]?: ProjectionValuePending<TValue>}
29
26
  documentProjections: {[TDocumentId in string]?: ValidProjection}
30
27
  subscriptions: {[TDocumentId in string]?: {[TSubscriptionId in string]?: true}}
31
28
  }
32
29
 
33
- export const projectionStore = createResource<ProjectionStoreState>({
30
+ export const projectionStore = defineStore<ProjectionStoreState>({
34
31
  name: 'Projection',
35
32
  getInitialState() {
36
33
  return {
37
34
  values: {},
38
35
  documentProjections: {},
39
36
  subscriptions: {},
40
- syncTags: {},
41
- lastLiveEventId: null,
42
37
  }
43
38
  },
44
- initialize() {
45
- const subscribeToLiveAndSetLastLiveEventId =
46
- createLiveEventSubscriber<ProjectionStoreState>(PROJECTION_TAG)
47
- const liveSubscription = subscribeToLiveAndSetLastLiveEventId(this)
48
- const batchSubscription = subscribeToStateAndFetchBatches(this)
49
-
50
- return () => {
51
- liveSubscription.unsubscribe()
52
- batchSubscription.unsubscribe()
53
- }
39
+ initialize(context) {
40
+ const batchSubscription = subscribeToStateAndFetchBatches(context)
41
+ return () => batchSubscription.unsubscribe()
54
42
  },
55
43
  })
@@ -1,144 +1,49 @@
1
- import {describe, it, type Mock} from 'vitest'
2
-
3
- import {createSanityInstance} from '../instance/sanityInstance'
4
- import {
5
- createResourceState,
6
- getOrCreateResource,
7
- type InitializedResource,
8
- type ResourceState,
9
- } from '../resources/createResource'
10
- import {insecureRandomId} from '../utils/ids'
11
- import {
12
- projectionStore,
13
- type ProjectionStoreState,
14
- type ProjectionValuePending,
15
- } from './projectionStore'
1
+ import {type SanityDocumentLike} from '@sanity/types'
2
+ import {of} from 'rxjs'
3
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
4
+
5
+ import {type DocumentHandle} from '../config/sanityConfig'
6
+ import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
7
+ import {type StateSource} from '../store/createStateSourceAction'
8
+ import {getProjectionState} from './getProjectionState'
9
+ import {type ProjectionValuePending, type ValidProjection} from './projectionStore'
16
10
  import {resolveProjection} from './resolveProjection'
17
11
 
18
- vi.mock('../utils/ids', async (importOriginal) => {
19
- const util = await importOriginal<typeof import('../utils/ids')>()
20
- return {...util, insecureRandomId: vi.fn(util.insecureRandomId)}
21
- })
22
-
23
- vi.mock('../resources/createResource', async (importOriginal) => {
24
- const original = await importOriginal<typeof import('../resources/createResource')>()
25
- return {...original, getOrCreateResource: vi.fn()}
26
- })
12
+ vi.mock('./getProjectionState')
27
13
 
28
14
  describe('resolveProjection', () => {
29
- const instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
30
- const document = {_id: 'exampleId', _type: 'exampleType'}
31
- const projectionString = '{title, description}'
32
- const initialState: ProjectionStoreState = {
33
- documentProjections: {},
34
- lastLiveEventId: null,
35
- subscriptions: {},
36
- syncTags: {},
37
- values: {},
38
- }
39
- let state: ResourceState<ProjectionStoreState>
15
+ let instance: SanityInstance
40
16
 
41
17
  beforeEach(() => {
42
- state = createResourceState(initialState)
18
+ vi.resetAllMocks()
19
+ // Create a mock that returns the correct ProjectionValuePending type
20
+ vi.mocked(getProjectionState).mockReturnValue({
21
+ observable: of({
22
+ data: {title: 'test'},
23
+ isPending: false,
24
+ } as ProjectionValuePending<Record<string, unknown>>),
25
+ } as StateSource<ProjectionValuePending<Record<string, unknown>>>)
26
+
27
+ instance = createSanityInstance({projectId: 'p', dataset: 'd'})
43
28
  })
44
29
 
45
- it('subscribes and resolves when the projection value is non-null', async () => {
46
- expect(state.get().subscriptions).toEqual({})
47
- ;(insecureRandomId as Mock).mockImplementationOnce(() => 'pseudoRandomId')
48
-
49
- const projectionPromise = resolveProjection(
50
- {state, instance},
51
- {document, projection: projectionString},
52
- )
53
- expect(state.get().subscriptions).toEqual({exampleId: {pseudoRandomId: true}})
54
- expect(state.get().documentProjections).toEqual({exampleId: projectionString})
55
-
56
- state.set('updateDifferentDocument', (prev) => ({
57
- values: {
58
- ...prev.values,
59
- differentId: {data: {title: 'Different Document'}, isPending: false},
60
- },
61
- }))
62
-
63
- expect(state.get().subscriptions).toEqual({exampleId: {pseudoRandomId: true}})
64
-
65
- state.set('updateCorrectDocumentButNull', (prev) => ({
66
- values: {...prev.values, exampleId: {data: null, isPending: true}},
67
- }))
68
-
69
- expect(state.get().subscriptions).toEqual({exampleId: {pseudoRandomId: true}})
70
-
71
- state.set('updateCorrectDocument', (prev) => ({
72
- values: {
73
- ...prev.values,
74
- exampleId: {
75
- data: {title: 'Correct Document', description: 'Test'},
76
- isPending: false,
77
- },
78
- },
79
- }))
80
-
81
- const projectionResult = await projectionPromise
82
- expect(projectionResult).toEqual({
83
- data: {title: 'Correct Document', description: 'Test'},
84
- isPending: false,
85
- })
86
-
87
- // subscription is removed after
88
- expect(state.get().subscriptions).toEqual({})
30
+ afterEach(() => {
31
+ instance.dispose()
89
32
  })
90
33
 
91
- it('resolves with the next emitted state (not current state)', async () => {
92
- const currentValue: ProjectionValuePending<Record<string, unknown>> = {
93
- data: {title: 'Current Document', description: 'Test'},
94
- isPending: false,
34
+ it('resolves a projection and returns the first emitted value with results', async () => {
35
+ const docHandle: DocumentHandle<SanityDocumentLike> = {
36
+ documentId: 'doc123',
37
+ documentType: 'movie',
95
38
  }
96
- state.set('setInitialDocument', (prev) => ({
97
- values: {...prev.values, exampleId: currentValue},
98
- }))
99
- vi.mocked(insecureRandomId).mockImplementationOnce(() => 'pseudoRandomId')
100
- expect(state.get().subscriptions).toEqual({})
39
+ const projection = '{title}' as ValidProjection
101
40
 
102
- const projectionPromise = resolveProjection(
103
- {state, instance},
104
- {document, projection: projectionString},
105
- )
106
- expect(state.get().subscriptions).toEqual({exampleId: {pseudoRandomId: true}})
41
+ const result = await resolveProjection(instance, {...docHandle, projection})
107
42
 
108
- state.set('updateDifferentDocument', (prev) => ({
109
- values: {
110
- ...prev.values,
111
- differentId: {data: {title: 'Different Document'}, isPending: false},
112
- },
113
- }))
114
- expect(state.get().subscriptions).toEqual({exampleId: {pseudoRandomId: true}})
115
-
116
- state.set('updateWithCurrentValue', (prev) => ({
117
- values: {...prev.values, exampleId: currentValue},
118
- }))
119
- expect(state.get().subscriptions).toEqual({exampleId: {pseudoRandomId: true}})
120
-
121
- state.set('updateWithNewValue', (prev) => ({
122
- values: {
123
- ...prev.values,
124
- exampleId: {
125
- data: {title: 'New Value', description: 'Updated'},
126
- isPending: false,
127
- },
128
- },
129
- }))
130
- expect(state.get().subscriptions).toEqual({})
131
-
132
- const projectionResult = await projectionPromise
133
- expect(projectionResult).toEqual({
134
- data: {title: 'New Value', description: 'Updated'},
43
+ expect(getProjectionState).toHaveBeenCalledWith(instance, {...docHandle, projection})
44
+ expect(result).toEqual({
45
+ data: {title: 'test'},
135
46
  isPending: false,
136
47
  })
137
48
  })
138
-
139
- it('calls getOrCreateResource if no state is provided', () => {
140
- vi.mocked(getOrCreateResource).mockReturnValue({state} as InitializedResource<unknown>)
141
- resolveProjection(instance, {document, projection: projectionString})
142
- expect(getOrCreateResource).toHaveBeenCalledWith(instance, projectionStore)
143
- })
144
49
  })