@sanity/sdk 0.0.0-chore-react-18-compat.1 → 0.0.0-chore-react-18-compat.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +441 -322
- package/dist/index.js +1685 -1481
- package/dist/index.js.map +1 -1
- package/package.json +13 -15
- package/src/_exports/index.ts +32 -30
- package/src/auth/authStore.test.ts +149 -104
- package/src/auth/authStore.ts +51 -100
- package/src/auth/handleAuthCallback.test.ts +67 -34
- package/src/auth/handleAuthCallback.ts +8 -7
- package/src/auth/logout.test.ts +61 -29
- package/src/auth/logout.ts +26 -28
- package/src/auth/refreshStampedToken.test.ts +197 -91
- package/src/auth/refreshStampedToken.ts +170 -59
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
- package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
- package/src/client/clientStore.test.ts +131 -67
- package/src/client/clientStore.ts +117 -116
- package/src/comlink/controller/actions/destroyController.test.ts +38 -13
- package/src/comlink/controller/actions/destroyController.ts +11 -15
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
- package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
- package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
- package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
- package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
- package/src/comlink/controller/actions/releaseChannel.ts +22 -21
- package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
- package/src/comlink/controller/comlinkControllerStore.ts +44 -5
- package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
- package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
- package/src/comlink/node/actions/releaseNode.test.ts +75 -55
- package/src/comlink/node/actions/releaseNode.ts +19 -21
- package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
- package/src/comlink/node/comlinkNodeStore.ts +22 -5
- package/src/config/authConfig.ts +79 -0
- package/src/config/sanityConfig.ts +48 -0
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +18 -5
- package/src/document/actions.test.ts +22 -10
- package/src/document/actions.ts +44 -56
- package/src/document/applyDocumentActions.test.ts +96 -36
- package/src/document/applyDocumentActions.ts +140 -99
- package/src/document/documentStore.test.ts +103 -155
- package/src/document/documentStore.ts +247 -238
- package/src/document/listen.ts +56 -55
- package/src/document/patchOperations.ts +0 -43
- package/src/document/permissions.test.ts +25 -12
- package/src/document/permissions.ts +11 -4
- package/src/document/processActions.test.ts +41 -8
- package/src/document/reducers.test.ts +87 -16
- package/src/document/reducers.ts +2 -2
- package/src/document/sharedListener.test.ts +34 -16
- package/src/document/sharedListener.ts +33 -11
- package/src/preview/getPreviewState.test.ts +40 -39
- package/src/preview/getPreviewState.ts +68 -56
- package/src/preview/previewConstants.ts +43 -0
- package/src/preview/previewQuery.test.ts +1 -1
- package/src/preview/previewQuery.ts +4 -5
- package/src/preview/previewStore.test.ts +13 -58
- package/src/preview/previewStore.ts +7 -21
- package/src/preview/resolvePreview.test.ts +33 -104
- package/src/preview/resolvePreview.ts +11 -21
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
- package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
- package/src/preview/util.ts +1 -0
- package/src/project/project.test.ts +3 -3
- package/src/project/project.ts +28 -5
- package/src/projection/getProjectionState.test.ts +188 -72
- package/src/projection/getProjectionState.ts +92 -62
- package/src/projection/projectionQuery.test.ts +114 -12
- package/src/projection/projectionQuery.ts +75 -32
- package/src/projection/projectionStore.test.ts +13 -51
- package/src/projection/projectionStore.ts +6 -43
- package/src/projection/resolveProjection.test.ts +32 -127
- package/src/projection/resolveProjection.ts +16 -28
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +203 -116
- package/src/projection/subscribeToStateAndFetchBatches.ts +140 -85
- package/src/projection/types.ts +50 -0
- package/src/projection/util.ts +3 -1
- package/src/projects/projects.test.ts +13 -4
- package/src/projects/projects.ts +6 -1
- package/src/query/queryStore.test.ts +10 -47
- package/src/query/queryStore.ts +151 -133
- package/src/query/queryStoreConstants.ts +2 -0
- package/src/store/createActionBinder.test.ts +153 -0
- package/src/store/createActionBinder.ts +176 -0
- package/src/store/createSanityInstance.test.ts +84 -0
- package/src/store/createSanityInstance.ts +124 -0
- package/src/store/createStateSourceAction.test.ts +196 -0
- package/src/store/createStateSourceAction.ts +260 -0
- package/src/store/createStoreInstance.test.ts +81 -0
- package/src/store/createStoreInstance.ts +80 -0
- package/src/store/createStoreState.test.ts +85 -0
- package/src/store/createStoreState.ts +92 -0
- package/src/store/defineStore.test.ts +18 -0
- package/src/store/defineStore.ts +81 -0
- package/src/users/reducers.test.ts +318 -0
- package/src/users/reducers.ts +88 -0
- package/src/users/types.ts +46 -4
- package/src/users/usersConstants.ts +4 -0
- package/src/users/usersStore.test.ts +350 -223
- package/src/users/usersStore.ts +285 -149
- package/src/utils/createFetcherStore.test.ts +6 -7
- package/src/utils/createFetcherStore.ts +150 -153
- package/src/utils/createGroqSearchFilter.test.ts +75 -0
- package/src/utils/createGroqSearchFilter.ts +85 -0
- package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
- package/dist/index.cjs +0 -4888
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -2121
- package/src/auth/fetchLoginUrls.test.ts +0 -163
- package/src/auth/fetchLoginUrls.ts +0 -74
- package/src/common/createLiveEventSubscriber.test.ts +0 -121
- package/src/common/createLiveEventSubscriber.ts +0 -55
- package/src/common/types.ts +0 -4
- package/src/instance/identity.test.ts +0 -46
- package/src/instance/identity.ts +0 -29
- package/src/instance/sanityInstance.test.ts +0 -77
- package/src/instance/sanityInstance.ts +0 -57
- package/src/instance/types.ts +0 -37
- package/src/preview/getPreviewProjection.ts +0 -45
- package/src/resources/README.md +0 -370
- package/src/resources/createAction.test.ts +0 -101
- package/src/resources/createAction.ts +0 -44
- package/src/resources/createResource.test.ts +0 -112
- package/src/resources/createResource.ts +0 -102
- package/src/resources/createStateSourceAction.test.ts +0 -114
- package/src/resources/createStateSourceAction.ts +0 -83
- package/src/resources/createStore.test.ts +0 -67
- package/src/resources/createStore.ts +0 -46
- package/src/store/createStore.test.ts +0 -108
- package/src/store/createStore.ts +0 -106
- /package/src/{common/util.ts → utils/hashString.ts} +0 -0
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import {createSanityInstance} from '../instance/sanityInstance'
|
|
2
|
-
import {createResourceState} from '../resources/createResource'
|
|
3
|
-
import {authStore} from './authStore'
|
|
4
|
-
import {fetchLoginUrls} from './fetchLoginUrls'
|
|
5
|
-
|
|
6
|
-
describe('fetchLoginUrls', () => {
|
|
7
|
-
it('returns providers with updated URLs', async () => {
|
|
8
|
-
const mockRequest = vi.fn().mockResolvedValue({
|
|
9
|
-
providers: [
|
|
10
|
-
{title: 'Provider A', url: 'https://auth.example.com/a'},
|
|
11
|
-
{title: 'Provider B', url: 'https://auth.example.com/b'},
|
|
12
|
-
],
|
|
13
|
-
})
|
|
14
|
-
const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
|
|
15
|
-
const instance = createSanityInstance({
|
|
16
|
-
projectId: 'p',
|
|
17
|
-
dataset: 'd',
|
|
18
|
-
auth: {clientFactory},
|
|
19
|
-
})
|
|
20
|
-
const state = createResourceState(authStore.getInitialState(instance))
|
|
21
|
-
const providers = await fetchLoginUrls({instance, state})
|
|
22
|
-
|
|
23
|
-
expect(providers.length).toBe(2)
|
|
24
|
-
expect(providers[0].url).toContain('withSid=true')
|
|
25
|
-
expect(providers[1].url).toContain('withSid=true')
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('caches the providers and early returns', async () => {
|
|
29
|
-
const clientFactory = vi.fn()
|
|
30
|
-
const instance = createSanityInstance({
|
|
31
|
-
projectId: 'p',
|
|
32
|
-
dataset: 'd',
|
|
33
|
-
auth: {clientFactory},
|
|
34
|
-
})
|
|
35
|
-
const state = createResourceState(authStore.getInitialState(instance))
|
|
36
|
-
|
|
37
|
-
const provider = {
|
|
38
|
-
name: 'cached-provided',
|
|
39
|
-
title: 'cached provider',
|
|
40
|
-
url: 'https://auth.example.com#withSid=true',
|
|
41
|
-
}
|
|
42
|
-
state.set('setInitialProviders', {providers: [provider]})
|
|
43
|
-
|
|
44
|
-
const providers = await fetchLoginUrls({instance, state})
|
|
45
|
-
|
|
46
|
-
expect(providers.length).toBe(1)
|
|
47
|
-
expect(providers[0].url).toContain('https://auth.example.com#withSid=true')
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('handles providers as a static array and merges/replaces accordingly', async () => {
|
|
51
|
-
const mockRequest = vi.fn().mockResolvedValue({
|
|
52
|
-
providers: [
|
|
53
|
-
{title: 'Provider A', name: 'provider-a', url: 'https://auth.example.com/a'},
|
|
54
|
-
{title: 'Provider B', name: 'provider-b', url: 'https://auth.example.com/b'},
|
|
55
|
-
],
|
|
56
|
-
})
|
|
57
|
-
const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
|
|
58
|
-
const instance = createSanityInstance({
|
|
59
|
-
projectId: 'p',
|
|
60
|
-
dataset: 'd',
|
|
61
|
-
auth: {
|
|
62
|
-
clientFactory,
|
|
63
|
-
providers: [
|
|
64
|
-
{
|
|
65
|
-
title: 'Custom Provider B',
|
|
66
|
-
name: 'custom-provider-b',
|
|
67
|
-
url: 'https://auth.example.com/b',
|
|
68
|
-
},
|
|
69
|
-
{title: 'Provider C', name: 'provider-c', url: 'https://auth.example.com/c'},
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
})
|
|
73
|
-
const state = createResourceState(authStore.getInitialState(instance))
|
|
74
|
-
const providers = await fetchLoginUrls({instance, state})
|
|
75
|
-
|
|
76
|
-
expect(providers.find((p) => p.title === 'Provider A')).toBeTruthy()
|
|
77
|
-
expect(providers.find((p) => p.title === 'Custom Provider B')).toBeTruthy()
|
|
78
|
-
expect(providers.find((p) => p.title === 'Provider C')).toBeTruthy()
|
|
79
|
-
expect(providers.find((p) => p.title === 'Provider B')).toBeFalsy()
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('allows custom provider function modification', async () => {
|
|
83
|
-
const mockRequest = vi.fn().mockResolvedValue({
|
|
84
|
-
providers: [{title: 'Provider A', url: 'https://auth.example.com/a'}],
|
|
85
|
-
})
|
|
86
|
-
const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
|
|
87
|
-
const instance = createSanityInstance({
|
|
88
|
-
projectId: 'p',
|
|
89
|
-
dataset: 'd',
|
|
90
|
-
auth: {
|
|
91
|
-
clientFactory,
|
|
92
|
-
providers: (defaults) => defaults.map((p) => ({...p, title: 'Modified ' + p.title})),
|
|
93
|
-
},
|
|
94
|
-
})
|
|
95
|
-
const state = createResourceState(authStore.getInitialState(instance))
|
|
96
|
-
const providers = await fetchLoginUrls({instance, state})
|
|
97
|
-
|
|
98
|
-
expect(providers[0].title).toBe('Modified Provider A')
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('uses default providers if none are specified', async () => {
|
|
102
|
-
const mockRequest = vi.fn().mockResolvedValue({
|
|
103
|
-
providers: [{title: 'Provider A', url: 'https://auth.example.com/a'}],
|
|
104
|
-
})
|
|
105
|
-
const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
|
|
106
|
-
const instance = createSanityInstance({
|
|
107
|
-
projectId: 'p',
|
|
108
|
-
dataset: 'd',
|
|
109
|
-
auth: {
|
|
110
|
-
clientFactory,
|
|
111
|
-
},
|
|
112
|
-
})
|
|
113
|
-
const state = createResourceState(authStore.getInitialState(instance))
|
|
114
|
-
const providers = await fetchLoginUrls({instance, state})
|
|
115
|
-
|
|
116
|
-
expect(providers.length).toBe(1)
|
|
117
|
-
expect(providers[0].title).toBe('Provider A')
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('includes callbackUrl in provider URLs if set', async () => {
|
|
121
|
-
const mockRequest = vi.fn().mockResolvedValue({
|
|
122
|
-
providers: [{title: 'Provider A', url: 'https://auth.example.com/a'}],
|
|
123
|
-
})
|
|
124
|
-
const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
|
|
125
|
-
const instance = createSanityInstance({
|
|
126
|
-
projectId: 'p',
|
|
127
|
-
dataset: 'd',
|
|
128
|
-
auth: {
|
|
129
|
-
clientFactory,
|
|
130
|
-
callbackUrl: 'http://localhost/callback',
|
|
131
|
-
},
|
|
132
|
-
})
|
|
133
|
-
const state = createResourceState(authStore.getInitialState(instance))
|
|
134
|
-
const providers = await fetchLoginUrls({instance, state})
|
|
135
|
-
expect(providers[0].url).toContain('origin=http%3A%2F%2Flocalhost%2Fcallback')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('should allow async custom provider function', async () => {
|
|
139
|
-
const mockRequest = vi.fn().mockResolvedValue({
|
|
140
|
-
providers: [{title: 'Provider A', url: 'https://auth.example.com/a'}],
|
|
141
|
-
})
|
|
142
|
-
const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
|
|
143
|
-
const instance = createSanityInstance({
|
|
144
|
-
projectId: 'p',
|
|
145
|
-
dataset: 'd',
|
|
146
|
-
auth: {
|
|
147
|
-
clientFactory,
|
|
148
|
-
providers: async (defaults) => {
|
|
149
|
-
await new Promise((r) => setTimeout(r, 10))
|
|
150
|
-
return defaults.concat([
|
|
151
|
-
{title: 'Provider C', name: 'provider-c', url: 'https://auth.example.com/c'},
|
|
152
|
-
])
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
})
|
|
156
|
-
const state = createResourceState(authStore.getInitialState(instance))
|
|
157
|
-
|
|
158
|
-
const providers = await fetchLoginUrls({instance, state})
|
|
159
|
-
|
|
160
|
-
expect(providers.length).toBe(2)
|
|
161
|
-
expect(providers.some((p) => p.title === 'Provider C')).toBe(true)
|
|
162
|
-
})
|
|
163
|
-
})
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import {type AuthProvider} from '@sanity/client'
|
|
2
|
-
|
|
3
|
-
import {createAction} from '../resources/createAction'
|
|
4
|
-
import {DEFAULT_API_VERSION, REQUEST_TAG_PREFIX} from './authConstants'
|
|
5
|
-
import {authStore} from './authStore'
|
|
6
|
-
import {getDefaultLocation} from './utils'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @public
|
|
10
|
-
*/
|
|
11
|
-
export const fetchLoginUrls = createAction(authStore, ({state}) => {
|
|
12
|
-
const {callbackUrl, clientFactory, apiHost, customProviders} = state.get().options
|
|
13
|
-
const client = clientFactory({
|
|
14
|
-
apiVersion: DEFAULT_API_VERSION,
|
|
15
|
-
requestTagPrefix: REQUEST_TAG_PREFIX,
|
|
16
|
-
useProjectHostname: false,
|
|
17
|
-
...(apiHost && {apiHost}),
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
return async function () {
|
|
21
|
-
const cachedProviders = state.get().providers
|
|
22
|
-
if (cachedProviders) return cachedProviders
|
|
23
|
-
|
|
24
|
-
const {providers: defaultProviders} = await client.request<{providers: AuthProvider[]}>({
|
|
25
|
-
uri: '/auth/providers',
|
|
26
|
-
tag: 'fetch-providers',
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
let providers: AuthProvider[]
|
|
30
|
-
|
|
31
|
-
if (typeof customProviders === 'function') {
|
|
32
|
-
providers = await customProviders(defaultProviders)
|
|
33
|
-
} else if (!customProviders?.length) {
|
|
34
|
-
providers = defaultProviders
|
|
35
|
-
} else {
|
|
36
|
-
const customProviderUrls = new Set(customProviders.map((p) => p.url))
|
|
37
|
-
providers = defaultProviders
|
|
38
|
-
.filter((official) => !customProviderUrls.has(official.url))
|
|
39
|
-
.concat(customProviders)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const configuredProviders = providers.map((provider) => {
|
|
43
|
-
const url = new URL(provider.url)
|
|
44
|
-
const origin = new URL(
|
|
45
|
-
callbackUrl
|
|
46
|
-
? new URL(callbackUrl, new URL(getDefaultLocation()).origin).toString()
|
|
47
|
-
: getDefaultLocation(),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
// `getDefaultLocation()` may be populated with an `sid` from a previous
|
|
51
|
-
// failed login attempt and should be omitted from the next login URL
|
|
52
|
-
const hashParams = new URLSearchParams(origin.hash.slice(1))
|
|
53
|
-
hashParams.delete('sid')
|
|
54
|
-
origin.hash = hashParams.toString()
|
|
55
|
-
origin.searchParams.delete('sid')
|
|
56
|
-
origin.searchParams.delete('url')
|
|
57
|
-
|
|
58
|
-
// similarly, the origin may be populated with an `error` query param if
|
|
59
|
-
// the auth provider redirects back to the application. this should also
|
|
60
|
-
// be omitted from the origin sent
|
|
61
|
-
origin.searchParams.delete('error')
|
|
62
|
-
|
|
63
|
-
url.searchParams.set('origin', origin.toString())
|
|
64
|
-
url.searchParams.set('withSid', 'true')
|
|
65
|
-
url.searchParams.set('type', 'stampedToken')
|
|
66
|
-
|
|
67
|
-
return {...provider, url: url.toString()}
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
state.set('fetchedLoginUrls', {providers: configuredProviders})
|
|
71
|
-
|
|
72
|
-
return configuredProviders
|
|
73
|
-
}
|
|
74
|
-
})
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import {type LiveEventMessage, SanityClient} from '@sanity/client'
|
|
2
|
-
import {Observable, of, Subject} from 'rxjs'
|
|
3
|
-
import {describe, it, vi} from 'vitest'
|
|
4
|
-
|
|
5
|
-
import {getClientState} from '../client/clientStore'
|
|
6
|
-
import {createSanityInstance} from '../instance/sanityInstance'
|
|
7
|
-
import {createResourceState, type ResourceState} from '../resources/createResource'
|
|
8
|
-
import {type StateSource} from '../resources/createStateSourceAction'
|
|
9
|
-
import {createLiveEventSubscriber} from './createLiveEventSubscriber'
|
|
10
|
-
import {type LiveEventAwareState} from './types'
|
|
11
|
-
|
|
12
|
-
vi.mock('../client/clientStore.ts', () => ({getClientState: vi.fn()}))
|
|
13
|
-
|
|
14
|
-
vi.mock('../resources/createResource', async (importOriginal) => {
|
|
15
|
-
const original = await importOriginal<typeof import('../resources/createResource')>()
|
|
16
|
-
return {...original, getOrCreateResource: vi.fn()}
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('createLiveEventSubscriber', () => {
|
|
20
|
-
const TEST_TAG = 'test-tag'
|
|
21
|
-
const instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
|
|
22
|
-
const initialState: LiveEventAwareState = {
|
|
23
|
-
lastLiveEventId: null,
|
|
24
|
-
syncTags: {},
|
|
25
|
-
}
|
|
26
|
-
let state: ResourceState<LiveEventAwareState>
|
|
27
|
-
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
state = createResourceState(initialState)
|
|
30
|
-
vi.clearAllMocks()
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('listens for matching sync tags and updates the `lastLiveEventId`', async () => {
|
|
34
|
-
const mockLiveEvents = new Observable<LiveEventMessage>((observer) => {
|
|
35
|
-
observer.next({
|
|
36
|
-
type: 'message',
|
|
37
|
-
id: 'event123',
|
|
38
|
-
tags: ['s1:tag1', 's1:tag2'],
|
|
39
|
-
})
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const mockClient = {
|
|
43
|
-
live: {
|
|
44
|
-
events: vi.fn().mockReturnValue(mockLiveEvents),
|
|
45
|
-
},
|
|
46
|
-
config: vi.fn().mockReturnValue({}),
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Mock the getSubscribableClient to emit our mock client
|
|
50
|
-
vi.mocked(getClientState).mockReturnValue({
|
|
51
|
-
observable: of(mockClient as unknown as SanityClient),
|
|
52
|
-
} as StateSource<SanityClient>)
|
|
53
|
-
|
|
54
|
-
// Set up initial state with a matching sync tag
|
|
55
|
-
state.set('setSyncTags', {
|
|
56
|
-
syncTags: {
|
|
57
|
-
's1:tag1': true,
|
|
58
|
-
},
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// Create and subscribe to live events
|
|
62
|
-
const subscribeToLiveAndSetLastLiveEventId =
|
|
63
|
-
createLiveEventSubscriber<LiveEventAwareState>(TEST_TAG)
|
|
64
|
-
const subscription = subscribeToLiveAndSetLastLiveEventId({instance, state})
|
|
65
|
-
|
|
66
|
-
// Verify the client was configured correctly
|
|
67
|
-
expect(mockClient.live.events).toHaveBeenCalledWith({
|
|
68
|
-
includeDrafts: false,
|
|
69
|
-
tag: TEST_TAG,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
// Verify the state was updated with the new event ID
|
|
73
|
-
expect(state.get().lastLiveEventId).toBe('event123')
|
|
74
|
-
|
|
75
|
-
// Clean up subscription
|
|
76
|
-
subscription.unsubscribe()
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('unsubscribes from the previous live content connection if any', () => {
|
|
80
|
-
const unsubscribe = vi.fn()
|
|
81
|
-
|
|
82
|
-
const mockLiveEvents = new Observable<LiveEventMessage>((observer) => {
|
|
83
|
-
observer.next({
|
|
84
|
-
type: 'message',
|
|
85
|
-
id: 'event123',
|
|
86
|
-
tags: ['s1:tag1'],
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
return unsubscribe
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
const mockClient = {
|
|
93
|
-
live: {
|
|
94
|
-
events: vi.fn().mockReturnValue(mockLiveEvents),
|
|
95
|
-
},
|
|
96
|
-
config: vi.fn().mockReturnValue({}),
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const clientSubject = new Subject()
|
|
100
|
-
|
|
101
|
-
vi.mocked(getClientState).mockReturnValue({
|
|
102
|
-
observable: clientSubject as Observable<SanityClient>,
|
|
103
|
-
} as StateSource<SanityClient>)
|
|
104
|
-
|
|
105
|
-
// Create and subscribe to live events
|
|
106
|
-
const subscribeToLiveAndSetLastLiveEventId =
|
|
107
|
-
createLiveEventSubscriber<LiveEventAwareState>(TEST_TAG)
|
|
108
|
-
const liveSubscription = subscribeToLiveAndSetLastLiveEventId({instance, state})
|
|
109
|
-
clientSubject.next(mockClient)
|
|
110
|
-
|
|
111
|
-
expect(mockClient.live.events).toHaveBeenCalledTimes(1)
|
|
112
|
-
|
|
113
|
-
const newClient = {...mockClient}
|
|
114
|
-
clientSubject.next(newClient)
|
|
115
|
-
expect(unsubscribe).toHaveBeenCalledTimes(1)
|
|
116
|
-
|
|
117
|
-
// Clean up subscriptions
|
|
118
|
-
liveSubscription.unsubscribe()
|
|
119
|
-
expect(unsubscribe).toHaveBeenCalledTimes(2)
|
|
120
|
-
})
|
|
121
|
-
})
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import {combineLatest, distinctUntilChanged, filter, map, type Subscription, switchMap} from 'rxjs'
|
|
2
|
-
|
|
3
|
-
import {getClientState} from '../client/clientStore'
|
|
4
|
-
import {type ActionContext, createInternalAction} from '../resources/createAction'
|
|
5
|
-
import {type LiveEventAwareState} from './types'
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
* Factory function for creating a subscribeToLiveAndSetLastLiveEventId action.
|
|
9
|
-
* Typically invoked in the initialization of stores that depend on the live events API.
|
|
10
|
-
* It listens to events associated with sync tags (also kept internally in the store)
|
|
11
|
-
|
|
12
|
-
* Usage like:
|
|
13
|
-
*
|
|
14
|
-
* const subscribeToLiveAndSetLastLiveEventId = createLiveEventSubscriber<ProjectionStoreState<TValue>>(TAG)
|
|
15
|
-
* const liveSubscription = subscribeToLiveAndSetLastLiveEventId(this)
|
|
16
|
-
*
|
|
17
|
-
* return () => {
|
|
18
|
-
* stateSubscriptionForBatches.unsubscribe()
|
|
19
|
-
* liveSubscription.unsubscribe()
|
|
20
|
-
*/
|
|
21
|
-
export function createLiveEventSubscriber<TState extends LiveEventAwareState>(
|
|
22
|
-
tag: string,
|
|
23
|
-
): (actionContext: ActionContext<TState>) => Subscription {
|
|
24
|
-
return createInternalAction(({instance, state}: ActionContext<TState>) => {
|
|
25
|
-
const client$ = getClientState(instance, {apiVersion: 'vX'}).observable
|
|
26
|
-
const syncTags$ = state.observable.pipe(
|
|
27
|
-
map((i) => i.syncTags),
|
|
28
|
-
distinctUntilChanged(),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
return function () {
|
|
32
|
-
const messageEvents$ = client$.pipe(
|
|
33
|
-
switchMap((client) =>
|
|
34
|
-
client.live
|
|
35
|
-
.events({includeDrafts: !!client.config().token, tag})
|
|
36
|
-
.pipe(filter((e): e is Extract<typeof e, {type: 'message'}> => e.type === 'message')),
|
|
37
|
-
),
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
return combineLatest([messageEvents$, syncTags$]).subscribe({
|
|
41
|
-
next: ([event, currentSyncTags]) => {
|
|
42
|
-
for (const eventTag of event.tags) {
|
|
43
|
-
if (currentSyncTags[eventTag]) {
|
|
44
|
-
state.set('setLastLiveEventId', (prevState: TState) => ({
|
|
45
|
-
...prevState,
|
|
46
|
-
lastLiveEventId: event.id,
|
|
47
|
-
}))
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
}
|
package/src/common/types.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {getSdkIdentity} from './identity'
|
|
4
|
-
|
|
5
|
-
describe('identity', () => {
|
|
6
|
-
describe('getSdkIdentity', () => {
|
|
7
|
-
it('creates a frozen object with expected properties', () => {
|
|
8
|
-
const identity = getSdkIdentity({
|
|
9
|
-
projectId: 'test-project',
|
|
10
|
-
dataset: 'test-dataset',
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
// Check if object is frozen
|
|
14
|
-
expect(Object.isFrozen(identity)).toBe(true)
|
|
15
|
-
|
|
16
|
-
// Check if all expected properties exist
|
|
17
|
-
expect(identity).toHaveProperty('id')
|
|
18
|
-
expect(identity).toHaveProperty('projectId', 'test-project')
|
|
19
|
-
expect(identity).toHaveProperty('dataset', 'test-dataset')
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('generates unique ids for different instances', () => {
|
|
23
|
-
const identity1 = getSdkIdentity({
|
|
24
|
-
projectId: 'test-project',
|
|
25
|
-
dataset: 'test-dataset',
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
const identity2 = getSdkIdentity({
|
|
29
|
-
projectId: 'test-project',
|
|
30
|
-
dataset: 'test-dataset',
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
expect(identity1.id).not.toBe(identity2.id)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('generates id with correct format', () => {
|
|
37
|
-
const identity = getSdkIdentity({
|
|
38
|
-
projectId: 'test-project',
|
|
39
|
-
dataset: 'test-dataset',
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
// ID should be 16 characters long (8 pairs of hex digits)
|
|
43
|
-
expect(identity.id).toMatch(/^[0-9a-f]{16}$/)
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
})
|
package/src/instance/identity.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import {type SdkIdentity} from './types'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* thoughtLevel 2 - Primarily what we want is to have an object that we can bind stores/memoizations to, but let the things that depend on it (eg projectId, dataset) be internals that can't be overwritten by accident/intentionally
|
|
5
|
-
* @public
|
|
6
|
-
*/
|
|
7
|
-
export function getSdkIdentity({
|
|
8
|
-
projectId,
|
|
9
|
-
dataset,
|
|
10
|
-
}: {
|
|
11
|
-
projectId: string
|
|
12
|
-
dataset: string
|
|
13
|
-
}): SdkIdentity {
|
|
14
|
-
const id = generateId()
|
|
15
|
-
return Object.freeze({
|
|
16
|
-
id,
|
|
17
|
-
projectId,
|
|
18
|
-
dataset,
|
|
19
|
-
resourceId: `${projectId}.${dataset}`,
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function generateId() {
|
|
24
|
-
return Array.from({length: 8}, () =>
|
|
25
|
-
Math.floor(Math.random() * 16)
|
|
26
|
-
.toString(16)
|
|
27
|
-
.padStart(2, '0'),
|
|
28
|
-
).join('')
|
|
29
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import {beforeEach, describe, expect, test} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {disposeResources} from '../resources/createResource'
|
|
4
|
-
import {createSanityInstance, getOrCreateResource} from './sanityInstance'
|
|
5
|
-
import {type SanityConfig} from './types'
|
|
6
|
-
|
|
7
|
-
vi.mock('../resources/createResource')
|
|
8
|
-
|
|
9
|
-
describe('sanityInstance', () => {
|
|
10
|
-
let config: SanityConfig
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
config = {
|
|
14
|
-
projectId: 'test-project',
|
|
15
|
-
dataset: 'test-dataset',
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('createSanityInstance', () => {
|
|
20
|
-
test('creates instance with correct configuration', () => {
|
|
21
|
-
const instance = createSanityInstance(config)
|
|
22
|
-
|
|
23
|
-
expect(instance.identity).toEqual(
|
|
24
|
-
expect.objectContaining({
|
|
25
|
-
projectId: 'test-project',
|
|
26
|
-
dataset: 'test-dataset',
|
|
27
|
-
}),
|
|
28
|
-
)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test('instance.dispose', () => {
|
|
32
|
-
const instance = createSanityInstance(config)
|
|
33
|
-
instance.dispose()
|
|
34
|
-
|
|
35
|
-
expect(disposeResources).toHaveBeenCalled()
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe('getOrCreateResource', () => {
|
|
40
|
-
test('creates and caches resource', () => {
|
|
41
|
-
const instance = createSanityInstance(config)
|
|
42
|
-
let createCount = 0
|
|
43
|
-
|
|
44
|
-
const creator = () => {
|
|
45
|
-
createCount++
|
|
46
|
-
return {value: 'test-resource'}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// First call should create new resource
|
|
50
|
-
const resource1 = getOrCreateResource(instance, 'test-key', creator)
|
|
51
|
-
expect(resource1).toEqual({value: 'test-resource'})
|
|
52
|
-
expect(createCount).toBe(1)
|
|
53
|
-
|
|
54
|
-
// Second call should return cached resource
|
|
55
|
-
const resource2 = getOrCreateResource(instance, 'test-key', creator)
|
|
56
|
-
expect(resource2).toBe(resource1)
|
|
57
|
-
expect(createCount).toBe(1)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('different instances have separate resource caches', () => {
|
|
61
|
-
const instance1 = createSanityInstance({...config, projectId: 'project1'})
|
|
62
|
-
const instance2 = createSanityInstance({...config, projectId: 'project2'})
|
|
63
|
-
let createCount = 0
|
|
64
|
-
|
|
65
|
-
const creator = () => {
|
|
66
|
-
createCount++
|
|
67
|
-
return {value: 'test-resource'}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const resource1 = getOrCreateResource(instance1, 'test-key', creator)
|
|
71
|
-
const resource2 = getOrCreateResource(instance2, 'test-key', creator)
|
|
72
|
-
|
|
73
|
-
expect(resource1).not.toBe(resource2)
|
|
74
|
-
expect(createCount).toBe(2)
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
})
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import {disposeResources} from '../resources/createResource'
|
|
2
|
-
import {getSdkIdentity} from './identity'
|
|
3
|
-
import {type SanityConfig, type SanityInstance, type SdkIdentity} from './types'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Returns a new instance of dependencies required for SanitySDK.
|
|
7
|
-
*
|
|
8
|
-
* @public
|
|
9
|
-
*
|
|
10
|
-
* @param config - The configuration for this instance
|
|
11
|
-
*
|
|
12
|
-
* @returns A new "instance" of a Sanity SDK, used to bind resources/configuration to it
|
|
13
|
-
*/
|
|
14
|
-
export function createSanityInstance({
|
|
15
|
-
projectId = '',
|
|
16
|
-
dataset = '',
|
|
17
|
-
...config
|
|
18
|
-
}: SanityConfig): SanityInstance {
|
|
19
|
-
const identity = getSdkIdentity({projectId, dataset})
|
|
20
|
-
return {
|
|
21
|
-
identity,
|
|
22
|
-
config,
|
|
23
|
-
dispose: () => disposeResources(identity),
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const resourceStorage = new WeakMap<SdkIdentity, Map<string, unknown>>()
|
|
28
|
-
|
|
29
|
-
function getResource(instance: SanityInstance, key: string) {
|
|
30
|
-
const instanceMap = resourceStorage.get(instance.identity)
|
|
31
|
-
return instanceMap?.get(key)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function setResource(instance: SanityInstance, key: string, value: unknown) {
|
|
35
|
-
let instanceMap = resourceStorage.get(instance.identity)
|
|
36
|
-
if (!instanceMap) {
|
|
37
|
-
instanceMap = new Map()
|
|
38
|
-
resourceStorage.set(instance.identity, instanceMap)
|
|
39
|
-
}
|
|
40
|
-
instanceMap.set(key, value)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* This is an internal function that retrieves or creates a Zustand store resource.
|
|
45
|
-
* @internal
|
|
46
|
-
*/
|
|
47
|
-
export function getOrCreateResource<T>(instance: SanityInstance, key: string, creator: () => T): T {
|
|
48
|
-
const cached = getResource(instance, key)
|
|
49
|
-
|
|
50
|
-
if (cached) {
|
|
51
|
-
return cached as T
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const resource = creator()
|
|
55
|
-
setResource(instance, key, resource)
|
|
56
|
-
return resource
|
|
57
|
-
}
|
package/src/instance/types.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/* eslint-disable import/consistent-type-specifier-style */
|
|
2
|
-
// NOTE: These have to be type import because we do not want the side-effect
|
|
3
|
-
// of importing these modules, we just want the types for their configs
|
|
4
|
-
import type {AuthConfig} from '../auth/authStore'
|
|
5
|
-
import type {ResourceId} from '../document/patchOperations'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @public
|
|
9
|
-
*/
|
|
10
|
-
export interface SanityConfig {
|
|
11
|
-
projectId: string
|
|
12
|
-
dataset: string
|
|
13
|
-
auth?: AuthConfig
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** @public */
|
|
17
|
-
export interface SanityInstance {
|
|
18
|
-
/**
|
|
19
|
-
* The following is used to look up resources associated with this instance,
|
|
20
|
-
* and can be used to retrieve an "id" for the instance - useful in debugging.
|
|
21
|
-
*
|
|
22
|
-
* @public
|
|
23
|
-
*/
|
|
24
|
-
readonly identity: SdkIdentity
|
|
25
|
-
|
|
26
|
-
config: Omit<SanityConfig, 'projectId' | 'dataset'>
|
|
27
|
-
|
|
28
|
-
dispose: () => void
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** @public */
|
|
32
|
-
export interface SdkIdentity {
|
|
33
|
-
readonly id: string
|
|
34
|
-
readonly projectId: string
|
|
35
|
-
readonly dataset: string
|
|
36
|
-
readonly resourceId: ResourceId
|
|
37
|
-
}
|