@sanity/sdk 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_exports/_internal.d.ts +72 -19
- package/dist/_exports/_internal.d.ts.map +1 -1
- package/dist/_exports/_internal.js +130 -11
- package/dist/_exports/_internal.js.map +1 -1
- package/dist/_exports/agent.d.ts +7 -7
- package/dist/_exports/agent.d.ts.map +1 -1
- package/dist/_exports/agent.js +1 -1
- package/dist/_exports/comlink.d.ts +9 -9
- package/dist/_exports/comlink.d.ts.map +1 -1
- package/dist/_exports/comlink.js +2 -2
- package/dist/_exports/dashboard.d.ts +2 -0
- package/dist/_exports/dashboard.js +2 -0
- package/dist/{authStore-C0o1NOxW.d.ts → authStore-BO2vch3I.d.ts} +516 -63
- package/dist/authStore-BO2vch3I.d.ts.map +1 -0
- package/dist/{authStore-CtE9bLHW.js → clientStore-BI4D8HhJ.js} +390 -15
- package/dist/clientStore-BI4D8HhJ.js.map +1 -0
- package/dist/{createSanityInstance-DUI1bGIg.d.ts → createSanityInstance-BQIqyGLX.d.ts} +17 -2
- package/dist/createSanityInstance-BQIqyGLX.d.ts.map +1 -0
- package/dist/{createStateSourceAction-BOKtqFNm.js → createStateSourceAction-DdAhEfnZ.js} +7 -3
- package/dist/createStateSourceAction-DdAhEfnZ.js.map +1 -0
- package/dist/defineProperty-Bho9Ngsi.js +31 -0
- package/dist/{getNodeState-CR3__B2D.js → getNodeState-B4nG0sjm.js} +2 -2
- package/dist/{getNodeState-CR3__B2D.js.map → getNodeState-B4nG0sjm.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +370 -90
- package/dist/index.js.map +1 -1
- package/dist/topicStore-Ct7IFNtY.js +565 -0
- package/dist/topicStore-Ct7IFNtY.js.map +1 -0
- package/dist/topicStore-D0WqhQGF.d.ts +435 -0
- package/dist/topicStore-D0WqhQGF.d.ts.map +1 -0
- package/dist/{queryStore-C7yMHPUI.js → version-BcRmCiYi.js} +48 -16
- package/dist/version-BcRmCiYi.js.map +1 -0
- package/package.json +19 -18
- package/src/_exports/_internal.ts +13 -0
- package/src/_exports/dashboard.ts +36 -0
- package/src/_exports/index.ts +17 -1
- package/src/access/checkPermissions.test.ts +1 -1
- package/src/access/checkPermissions.ts +1 -1
- package/src/applications/applications.test-d.ts +28 -4
- package/src/applications/applications.test.ts +4 -4
- package/src/applications/applications.ts +43 -18
- package/src/auth/authMode.test.ts +24 -0
- package/src/auth/authMode.ts +8 -3
- package/src/auth/authStore.ts +12 -0
- package/src/auth/authStrategy.ts +2 -0
- package/src/auth/handleAuthCallback.test.ts +2 -2
- package/src/auth/handleAuthCallback.ts +1 -1
- package/src/auth/logout.test.ts +46 -1
- package/src/auth/logout.ts +2 -1
- package/src/auth/oauth/oauthActions.test.ts +457 -0
- package/src/auth/oauth/oauthActions.ts +389 -0
- package/src/auth/oauth/oauthAuth.test.ts +206 -0
- package/src/auth/oauth/oauthAuth.ts +168 -0
- package/src/auth/oauth/pkce.test.ts +44 -0
- package/src/auth/oauth/pkce.ts +60 -0
- package/src/auth/oauth/types.ts +12 -0
- package/src/auth/refreshStampedToken.ts +1 -1
- package/src/auth/studioModeAuth.test.ts +1 -1
- package/src/auth/studioModeAuth.ts +1 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +2 -2
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +1 -1
- package/src/comments/addonDatasetStore.test.ts +2 -2
- package/src/comments/addonDatasetStore.ts +2 -2
- package/src/comments/commentActions.ts +6 -5
- package/src/comments/commentsStore.ts +4 -4
- package/src/config/authConfig.ts +18 -0
- package/src/dashboard/messageBus/__fixtures__/test-topics.ts +18 -0
- package/src/dashboard/messageBus/bus.test.ts +1033 -0
- package/src/dashboard/messageBus/bus.ts +1246 -0
- package/src/dashboard/messageBus/store.test.ts +142 -0
- package/src/dashboard/messageBus/store.ts +74 -0
- package/src/dashboard/messageBus/topicStore.test.ts +172 -0
- package/src/dashboard/messageBus/topicStore.ts +184 -0
- package/src/dashboard/messageBus/topics.test.ts +71 -0
- package/src/dashboard/messageBus/topics.ts +260 -0
- package/src/document/actions.ts +3 -3
- package/src/document/applyDocumentActions.ts +5 -9
- package/src/document/documentStore.concurrency.test.ts +2 -1
- package/src/document/documentStore.test.ts +19 -18
- package/src/document/documentStore.ts +17 -17
- package/src/document/permissions.ts +2 -1
- package/src/document/processActions.test.ts +2 -1
- package/src/document/processMutations.ts +61 -24
- package/src/document/reducers.ts +2 -2
- package/src/installations/installations.test-d.ts +3 -3
- package/src/installations/installations.test.ts +2 -2
- package/src/installations/installations.ts +4 -21
- package/src/organization/organization.test.ts +2 -2
- package/src/organization/organization.ts +1 -1
- package/src/organizations/organizations.test.ts +2 -2
- package/src/organizations/organizations.ts +1 -1
- package/src/presence/presenceStore.test.ts +15 -22
- package/src/presence/presenceStore.ts +3 -2
- package/src/project/project.test.ts +3 -3
- package/src/project/project.ts +1 -1
- package/src/projection/getProjectionState.test.ts +6 -4
- package/src/projection/getProjectionState.ts +4 -4
- package/src/projection/resolveProjection.ts +2 -2
- package/src/projects/projects.test.ts +2 -2
- package/src/projects/projects.ts +1 -1
- package/src/query/queryStore.ts +6 -6
- package/src/store/createSanityInstance.test.ts +1 -1
- package/src/store/createSanityInstance.ts +4 -4
- package/src/store/createStoreInstance.test.ts +0 -5
- package/src/telemetry/telemetryManager.test.ts +1 -1
- package/src/telemetry/telemetryManager.ts +2 -2
- package/src/typegen/defineProjection.test.ts +17 -0
- package/src/typegen/defineProjection.ts +19 -0
- package/src/typegen/groqCompat.ts +122 -0
- package/src/typegen/resolve.ts +37 -0
- package/src/users/usersStore.test.ts +1 -1
- package/src/users/usersStore.ts +5 -5
- package/src/utils/createGroqSearchFilter.test.ts +8 -0
- package/src/utils/createGroqSearchFilter.ts +6 -3
- package/src/utils/ids.test.ts +40 -15
- package/src/utils/ids.ts +47 -2
- package/dist/authStore-C0o1NOxW.d.ts.map +0 -1
- package/dist/authStore-CtE9bLHW.js.map +0 -1
- package/dist/clientStore-BtE2-q2i.js +0 -90
- package/dist/clientStore-BtE2-q2i.js.map +0 -1
- package/dist/createSanityInstance-DUI1bGIg.d.ts.map +0 -1
- package/dist/createStateSourceAction-BOKtqFNm.js.map +0 -1
- package/dist/getEnv-D5RGkQLu.js +0 -8
- package/dist/getEnv-D5RGkQLu.js.map +0 -1
- package/dist/logger-DAtJb5RQ.js +0 -173
- package/dist/logger-DAtJb5RQ.js.map +0 -1
- package/dist/queryStore-C7yMHPUI.js.map +0 -1
- package/dist/rolldown-runtime-vyAXikos.js +0 -9
- package/dist/telemetryManager-UIEiMtC0.js +0 -127
- package/dist/telemetryManager-UIEiMtC0.js.map +0 -1
- package/dist/version-D8RP_Q59.js +0 -9
- package/dist/version-D8RP_Q59.js.map +0 -1
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type SanityDocument,
|
|
6
6
|
} from '@sanity/types'
|
|
7
7
|
|
|
8
|
+
import {randomUuid} from '../utils/ids'
|
|
8
9
|
import {
|
|
9
10
|
dec,
|
|
10
11
|
diffMatchPatch,
|
|
@@ -17,10 +18,9 @@ import {
|
|
|
17
18
|
} from './patchOperations'
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* Documents that don't exist have a `null` value.
|
|
21
|
+
* Maps document IDs to documents, using `null` for documents that do not exist.
|
|
22
|
+
*
|
|
23
|
+
* @beta
|
|
24
24
|
*/
|
|
25
25
|
export type DocumentSet<TDocument extends SanityDocument = SanityDocument> = {
|
|
26
26
|
[TDocumentId in string]?: TDocument | null
|
|
@@ -28,10 +28,29 @@ export type DocumentSet<TDocument extends SanityDocument = SanityDocument> = {
|
|
|
28
28
|
|
|
29
29
|
type SupportedPatchOperation = Exclude<keyof PatchOperations, 'merge'>
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Exposes the patch operations used by {@link processMutations}.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* Property order determines execution order in {@link processMutations}:
|
|
36
|
+
* `ifRevisionID`, `set`, `setIfMissing`, `unset`, `inc`, `dec`, `insert`,
|
|
37
|
+
* then `diffMatchPatch`. Reordering the properties changes mutation results.
|
|
38
|
+
* Freezes the collection to prevent callers from replacing the functions
|
|
39
|
+
* used by the SDK's mutation evaluator.
|
|
40
|
+
*
|
|
41
|
+
* @see https://www.sanity.io/docs/http-mutations#5b4db1396e56
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const document = {items: [{_key: 'a', title: 'Before'}]}
|
|
46
|
+
* const updated = patchOperations.set<typeof document>(document, {
|
|
47
|
+
* 'items[_key=="a"].title': 'After',
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
export const patchOperations = Object.freeze({
|
|
35
54
|
ifRevisionID,
|
|
36
55
|
set,
|
|
37
56
|
setIfMissing,
|
|
@@ -45,7 +64,7 @@ const patchOperations = {
|
|
|
45
64
|
input: unknown,
|
|
46
65
|
pathExpressions: NonNullable<PatchOperations[K]>,
|
|
47
66
|
) => unknown
|
|
48
|
-
}
|
|
67
|
+
})
|
|
49
68
|
|
|
50
69
|
/**
|
|
51
70
|
* Implements ID generation:
|
|
@@ -63,12 +82,17 @@ const patchOperations = {
|
|
|
63
82
|
* [- source](https://www.sanity.io/docs/http-mutations#c732f27330a4)
|
|
64
83
|
*/
|
|
65
84
|
export function getId(id?: string): string {
|
|
66
|
-
if (!id || typeof id !== 'string') return
|
|
67
|
-
if (id.endsWith('.')) return `${id}${
|
|
85
|
+
if (!id || typeof id !== 'string') return randomUuid()
|
|
86
|
+
if (id.endsWith('.')) return `${id}${randomUuid()}`
|
|
68
87
|
return id
|
|
69
88
|
}
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Configures {@link processMutations}.
|
|
92
|
+
*
|
|
93
|
+
* @internal
|
|
94
|
+
*/
|
|
95
|
+
export interface ProcessMutationsOptions {
|
|
72
96
|
/**
|
|
73
97
|
* The transaction ID that will become the next `_rev` for documents mutated
|
|
74
98
|
* by the given mutations.
|
|
@@ -83,8 +107,8 @@ interface ProcessMutationsOptions {
|
|
|
83
107
|
*/
|
|
84
108
|
mutations: Mutation[]
|
|
85
109
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
110
|
+
* Supplies the default `_createdAt` and `_updatedAt` timestamps.
|
|
111
|
+
* Uses the current time when omitted or empty.
|
|
88
112
|
*/
|
|
89
113
|
timestamp?: string
|
|
90
114
|
}
|
|
@@ -107,18 +131,31 @@ export function getDocumentIds(selection: MutationSelection): string[] {
|
|
|
107
131
|
}
|
|
108
132
|
|
|
109
133
|
/**
|
|
110
|
-
* Applies
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
134
|
+
* Applies mutations to an in-memory set of Sanity documents.
|
|
135
|
+
* Deleted documents remain in the returned set with a value of `null`.
|
|
136
|
+
*
|
|
137
|
+
* Include every document affected by `mutations` in `documents`, using `null`
|
|
138
|
+
* for documents that do not exist.
|
|
139
|
+
*
|
|
140
|
+
* Returns `documents` itself when `mutations` is empty. Otherwise, returns a
|
|
141
|
+
* shallow copy of that object. The result can reuse document objects and
|
|
142
|
+
* nested objects from `documents`. Mutating a reused object through the
|
|
143
|
+
* result also changes the input.
|
|
144
|
+
*
|
|
145
|
+
* Sets `_rev` to `transactionId` on created, replaced, and patched documents.
|
|
146
|
+
* Uses `timestamp` for timestamps unless the mutation preserves them.
|
|
147
|
+
* Ignores `merge` patches; `inc` and `dec` skip values that are not numbers.
|
|
148
|
+
* Generates document IDs when omitted or ending in `.`.
|
|
149
|
+
* Patch operations can generate missing array keys.
|
|
150
|
+
*
|
|
151
|
+
* Does not persist documents or run Content Lake's server validation.
|
|
116
152
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
153
|
+
* @throws If a selection uses a query, a `create` targets an existing document,
|
|
154
|
+
* or a `patch` targets a document that does not exist.
|
|
155
|
+
* @throws If a patch operation fails, including a revision check failure or
|
|
156
|
+
* `diffMatchPatch` applied to a value other than a string or `undefined`.
|
|
119
157
|
*
|
|
120
|
-
*
|
|
121
|
-
* and `_createdAt` timestamps.
|
|
158
|
+
* @internal
|
|
122
159
|
*/
|
|
123
160
|
export function processMutations({
|
|
124
161
|
documents,
|
package/src/document/reducers.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {type Mutation, type PatchOperations, type SanityDocumentLike} from '@san
|
|
|
4
4
|
import {type DocumentHandle} from '../config/sanityConfig'
|
|
5
5
|
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
6
6
|
import {type StoreContext} from '../store/defineStore'
|
|
7
|
-
import {
|
|
7
|
+
import {randomId} from '../utils/ids'
|
|
8
8
|
import {isDeepEqual, omitProperty} from '../utils/object'
|
|
9
9
|
import {setCleanupTimeout} from '../utils/setCleanupTimeout'
|
|
10
10
|
import {type Action} from './actions'
|
|
@@ -790,7 +790,7 @@ export function manageSubscriberIds(
|
|
|
790
790
|
): () => void {
|
|
791
791
|
const documentIds = getDocumentIdsFromHandleLikes(handles)
|
|
792
792
|
|
|
793
|
-
const subscriptionId =
|
|
793
|
+
const subscriptionId = randomId(16)
|
|
794
794
|
state.set('addSubscribers', (prev) =>
|
|
795
795
|
documentIds.reduce(
|
|
796
796
|
(acc, id) => addSubscriptionIdToDocument(acc, id, subscriptionId),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {expectTypeOf, test} from 'vitest'
|
|
2
2
|
|
|
3
|
+
import {type ApplicationInterface} from '../applications/applications'
|
|
3
4
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
4
5
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
5
6
|
import {type FetcherSnapshot} from '../store/fetcherStore'
|
|
@@ -10,7 +11,6 @@ import {
|
|
|
10
11
|
type InstallationActiveConfig,
|
|
11
12
|
type InstallationBase,
|
|
12
13
|
type InstallationInclude,
|
|
13
|
-
type InstallationInterface,
|
|
14
14
|
installations,
|
|
15
15
|
type InstallationsResponse,
|
|
16
16
|
} from './installations'
|
|
@@ -40,7 +40,7 @@ test('Installation — each token adds its top-level field, required, others abs
|
|
|
40
40
|
Extract<keyof Installation<'access'>, 'activeConfig' | 'interfaces'>
|
|
41
41
|
>().toEqualTypeOf<never>()
|
|
42
42
|
|
|
43
|
-
expectTypeOf<Installation<'interfaces'>['interfaces']>().toEqualTypeOf<
|
|
43
|
+
expectTypeOf<Installation<'interfaces'>['interfaces']>().toEqualTypeOf<ApplicationInterface[]>()
|
|
44
44
|
expectTypeOf<
|
|
45
45
|
Installation<'activeConfig'>['activeConfig']
|
|
46
46
|
>().toEqualTypeOf<InstallationActiveConfig | null>()
|
|
@@ -49,7 +49,7 @@ test('Installation — each token adds its top-level field, required, others abs
|
|
|
49
49
|
test('Installation — multiple tokens add each field', () => {
|
|
50
50
|
type Both = Installation<'access' | 'interfaces'>
|
|
51
51
|
expectTypeOf<Both['access']>().toEqualTypeOf<InstallationAccess[]>()
|
|
52
|
-
expectTypeOf<Both['interfaces']>().toEqualTypeOf<
|
|
52
|
+
expectTypeOf<Both['interfaces']>().toEqualTypeOf<ApplicationInterface[]>()
|
|
53
53
|
expectTypeOf<Extract<keyof Both, 'activeConfig'>>().toEqualTypeOf<never>()
|
|
54
54
|
})
|
|
55
55
|
|
|
@@ -40,7 +40,7 @@ describe('installations', () => {
|
|
|
40
40
|
scope: 'global',
|
|
41
41
|
})
|
|
42
42
|
expect(request).toHaveBeenCalledWith({
|
|
43
|
-
|
|
43
|
+
url: '/installations',
|
|
44
44
|
query: {organizationId: 'org1', include: 'interfaces'},
|
|
45
45
|
tag: 'installations.list',
|
|
46
46
|
})
|
|
@@ -54,7 +54,7 @@ describe('installations', () => {
|
|
|
54
54
|
|
|
55
55
|
expect(result).toEqual(inst)
|
|
56
56
|
expect(request).toHaveBeenCalledWith({
|
|
57
|
-
|
|
57
|
+
url: '/installations/inst1',
|
|
58
58
|
query: {},
|
|
59
59
|
tag: 'installations.get',
|
|
60
60
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {switchMap} from 'rxjs'
|
|
2
2
|
|
|
3
|
+
import {type ApplicationInterface} from '../applications/applications'
|
|
3
4
|
import {getClientState} from '../client/clientStore'
|
|
4
5
|
import {defineFetcher} from '../store/fetcherStore'
|
|
5
6
|
import {buildQuery} from '../utils/buildQuery'
|
|
@@ -29,24 +30,6 @@ export interface InstallationAccess {
|
|
|
29
30
|
resourceId: string
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
-
* An interface exposed by an installation, embedded when `interfaces` is
|
|
34
|
-
* included.
|
|
35
|
-
*
|
|
36
|
-
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
37
|
-
* @public
|
|
38
|
-
*/
|
|
39
|
-
export interface InstallationInterface {
|
|
40
|
-
id: string
|
|
41
|
-
type: 'app' | 'worker' | 'asset_source' | 'panel'
|
|
42
|
-
name: string
|
|
43
|
-
title: string
|
|
44
|
-
version: string
|
|
45
|
-
/** Module federation module ID; resolved from the host mf-manifest at runtime */
|
|
46
|
-
moduleId: string
|
|
47
|
-
metadata: {group?: string; priority?: number} | null
|
|
48
|
-
}
|
|
49
|
-
|
|
50
33
|
/**
|
|
51
34
|
* The active config of an installation, embedded when `activeConfig` is
|
|
52
35
|
* included. `null` when nothing is deployed.
|
|
@@ -104,7 +87,7 @@ export type Installation<Include extends InstallationInclude = never> = Installa
|
|
|
104
87
|
{activeConfig: InstallationActiveConfig | null}
|
|
105
88
|
> &
|
|
106
89
|
Included<InstallationInclude, 'access', Include, {access: InstallationAccess[]}> &
|
|
107
|
-
Included<InstallationInclude, 'interfaces', Include, {interfaces:
|
|
90
|
+
Included<InstallationInclude, 'interfaces', Include, {interfaces: ApplicationInterface[]}>
|
|
108
91
|
|
|
109
92
|
/**
|
|
110
93
|
* Options for listing an organization's installations.
|
|
@@ -160,7 +143,7 @@ export const installations = defineFetcher<
|
|
|
160
143
|
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
161
144
|
switchMap((client) =>
|
|
162
145
|
client.observable.request<InstallationsResponse<InstallationInclude>>({
|
|
163
|
-
|
|
146
|
+
url: '/installations',
|
|
164
147
|
query: buildQuery({
|
|
165
148
|
organizationId: options.organizationId,
|
|
166
149
|
include: serializeInclude(options.include),
|
|
@@ -196,7 +179,7 @@ export const installation = defineFetcher<
|
|
|
196
179
|
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
197
180
|
switchMap((client) =>
|
|
198
181
|
client.observable.request<Installation<InstallationInclude>>({
|
|
199
|
-
|
|
182
|
+
url: `/installations/${installationId}`,
|
|
200
183
|
query: buildQuery({include: serializeInclude(options?.include)}),
|
|
201
184
|
tag: 'installations.get',
|
|
202
185
|
}),
|
|
@@ -35,7 +35,7 @@ describe('organization', () => {
|
|
|
35
35
|
const result = await organization.resolveState(instance, {organizationId: 'org_1'})
|
|
36
36
|
expect(result).toEqual(detail)
|
|
37
37
|
expect(request).toHaveBeenCalledWith({
|
|
38
|
-
|
|
38
|
+
url: '/organizations/org_1',
|
|
39
39
|
query: {includeMembers: 'false', includeFeatures: 'false'},
|
|
40
40
|
tag: 'organizations.get',
|
|
41
41
|
})
|
|
@@ -58,7 +58,7 @@ describe('organization', () => {
|
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
expect(request).toHaveBeenCalledWith({
|
|
61
|
-
|
|
61
|
+
url: '/organizations/org_1',
|
|
62
62
|
query: {
|
|
63
63
|
includeMembers: 'true',
|
|
64
64
|
includeFeatures: 'true',
|
|
@@ -128,7 +128,7 @@ export const organization = defineFetcher<
|
|
|
128
128
|
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
129
129
|
switchMap((client) =>
|
|
130
130
|
client.observable.request<Organization<boolean, boolean>>({
|
|
131
|
-
|
|
131
|
+
url: `/organizations/${resolveOrganizationId(options)}`,
|
|
132
132
|
query: buildQuery(normalizeOrganizationOptions(options)),
|
|
133
133
|
tag: 'organizations.get',
|
|
134
134
|
}),
|
|
@@ -35,7 +35,7 @@ describe('organizations', () => {
|
|
|
35
35
|
const result = await organizations.resolveState(instance)
|
|
36
36
|
expect(result).toEqual(list)
|
|
37
37
|
expect(request).toHaveBeenCalledWith({
|
|
38
|
-
|
|
38
|
+
url: '/organizations',
|
|
39
39
|
query: {
|
|
40
40
|
includeImplicitMemberships: 'false',
|
|
41
41
|
includeMembers: 'false',
|
|
@@ -62,7 +62,7 @@ describe('organizations', () => {
|
|
|
62
62
|
})
|
|
63
63
|
|
|
64
64
|
expect(request).toHaveBeenCalledWith({
|
|
65
|
-
|
|
65
|
+
url: '/organizations',
|
|
66
66
|
query: {
|
|
67
67
|
includeImplicitMemberships: 'true',
|
|
68
68
|
includeMembers: 'true',
|
|
@@ -94,7 +94,7 @@ export const organizations = defineFetcher<
|
|
|
94
94
|
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
95
95
|
switchMap((client) =>
|
|
96
96
|
client.observable.request<Organizations<boolean, boolean>>({
|
|
97
|
-
|
|
97
|
+
url: '/organizations',
|
|
98
98
|
query: buildQuery(normalizeOrganizationsOptions(options)),
|
|
99
99
|
tag: 'organizations.list',
|
|
100
100
|
}),
|
|
@@ -24,6 +24,12 @@ vi.mock('../client/clientStore')
|
|
|
24
24
|
vi.mock('../users/usersStore')
|
|
25
25
|
vi.mock('./bifurTransport')
|
|
26
26
|
|
|
27
|
+
function lastTransportSessionId(): string {
|
|
28
|
+
const call = vi.mocked(createBifurTransport).mock.calls.at(-1)
|
|
29
|
+
if (!call) throw new Error('createBifurTransport was not called')
|
|
30
|
+
return call[0].sessionId
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
describe('presenceStore', () => {
|
|
28
34
|
let instance: SanityInstance
|
|
29
35
|
let mockClient: SanityClient
|
|
@@ -49,13 +55,6 @@ describe('presenceStore', () => {
|
|
|
49
55
|
beforeEach(() => {
|
|
50
56
|
vi.clearAllMocks()
|
|
51
57
|
|
|
52
|
-
// Mock crypto.randomUUID
|
|
53
|
-
Object.defineProperty(global, 'crypto', {
|
|
54
|
-
value: {
|
|
55
|
-
randomUUID: vi.fn(() => 'test-session-id'),
|
|
56
|
-
},
|
|
57
|
-
})
|
|
58
|
-
|
|
59
58
|
mockClient = {
|
|
60
59
|
withConfig: vi.fn().mockReturnThis(),
|
|
61
60
|
observable: {
|
|
@@ -103,7 +102,7 @@ describe('presenceStore', () => {
|
|
|
103
102
|
expect(createBifurTransport).toHaveBeenCalledWith({
|
|
104
103
|
client: mockClient,
|
|
105
104
|
token$: expect.any(Object),
|
|
106
|
-
sessionId:
|
|
105
|
+
sessionId: expect.stringMatching(/^[a-zA-Z0-9]{16}$/),
|
|
107
106
|
})
|
|
108
107
|
})
|
|
109
108
|
|
|
@@ -164,7 +163,7 @@ describe('presenceStore', () => {
|
|
|
164
163
|
mockIncomingEvents.next({
|
|
165
164
|
type: 'state',
|
|
166
165
|
userId: 'user-1',
|
|
167
|
-
sessionId:
|
|
166
|
+
sessionId: lastTransportSessionId(), // Same as our session
|
|
168
167
|
timestamp: '2023-01-01T12:00:00Z',
|
|
169
168
|
locations: [],
|
|
170
169
|
})
|
|
@@ -314,7 +313,7 @@ describe('presenceStore', () => {
|
|
|
314
313
|
getPresence(instance, {resource: canvasResource})
|
|
315
314
|
|
|
316
315
|
expect(mockClient.observable.request).toHaveBeenCalledWith({
|
|
317
|
-
|
|
316
|
+
url: '/canvases/canvas123',
|
|
318
317
|
tag: 'canvases.get',
|
|
319
318
|
})
|
|
320
319
|
})
|
|
@@ -1050,7 +1049,7 @@ describe('presenceStore', () => {
|
|
|
1050
1049
|
mockIncomingEvents.next({
|
|
1051
1050
|
type: 'rollCall',
|
|
1052
1051
|
userId: 'user-1',
|
|
1053
|
-
sessionId:
|
|
1052
|
+
sessionId: lastTransportSessionId(),
|
|
1054
1053
|
})
|
|
1055
1054
|
await flush()
|
|
1056
1055
|
|
|
@@ -1139,25 +1138,19 @@ describe('presenceStore', () => {
|
|
|
1139
1138
|
})
|
|
1140
1139
|
|
|
1141
1140
|
describe('session id', () => {
|
|
1142
|
-
afterEach(() => {
|
|
1143
|
-
vi.unstubAllGlobals()
|
|
1144
|
-
})
|
|
1145
|
-
|
|
1146
1141
|
it('gives each store its own id, so nothing can collide across tabs', () => {
|
|
1147
1142
|
// Ids are deliberately not persisted: a tab that inherits another's
|
|
1148
1143
|
// session storage would otherwise reuse the id and the two live clients
|
|
1149
1144
|
// would filter each other out as their own session.
|
|
1150
|
-
let counter = 0
|
|
1151
|
-
vi.stubGlobal('crypto', {randomUUID: vi.fn(() => `session-${++counter}`)})
|
|
1152
|
-
|
|
1153
1145
|
getPresence(instance, {resource: {projectId: 'p1', dataset: 'd1'}}).subscribe(() => {})
|
|
1154
|
-
const first =
|
|
1146
|
+
const first = lastTransportSessionId()
|
|
1155
1147
|
|
|
1156
1148
|
getPresence(instance, {resource: {projectId: 'p2', dataset: 'd2'}}).subscribe(() => {})
|
|
1157
|
-
const second =
|
|
1149
|
+
const second = lastTransportSessionId()
|
|
1158
1150
|
|
|
1159
|
-
expect(first).
|
|
1160
|
-
expect(second).
|
|
1151
|
+
expect(first).toMatch(/^[a-zA-Z0-9]{16}$/)
|
|
1152
|
+
expect(second).toMatch(/^[a-zA-Z0-9]{16}$/)
|
|
1153
|
+
expect(first).not.toBe(second)
|
|
1161
1154
|
})
|
|
1162
1155
|
})
|
|
1163
1156
|
})
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
39
39
|
import {type SanityUser} from '../users/types'
|
|
40
40
|
import {getUserState} from '../users/usersStore'
|
|
41
|
+
import {randomId} from '../utils/ids'
|
|
41
42
|
import {createLogger} from '../utils/logger'
|
|
42
43
|
import {createBifurTransport} from './bifurTransport'
|
|
43
44
|
import {startsWithPath} from './paths'
|
|
@@ -198,7 +199,7 @@ export const presenceStore = defineStore<PresenceStoreState, BoundResourceKey>({
|
|
|
198
199
|
// storage (`window.open`, or a same-origin iframe), making two live clients
|
|
199
200
|
// filter each other out as self. Stale sessions are handled by the
|
|
200
201
|
// disconnect on unload, with the expiry sweep above as the backstop.
|
|
201
|
-
const sessionId =
|
|
202
|
+
const sessionId = randomId(16)
|
|
202
203
|
|
|
203
204
|
// Dataset resources must use the project hostname so the socket URL is project-specific.
|
|
204
205
|
// Canvas resources use the global API endpoint via the resource config.
|
|
@@ -411,7 +412,7 @@ export const presenceStore = defineStore<PresenceStoreState, BoundResourceKey>({
|
|
|
411
412
|
subscription.add(
|
|
412
413
|
globalClient.observable
|
|
413
414
|
.request<{organizationId: string}>({
|
|
414
|
-
|
|
415
|
+
url: `/canvases/${resource.canvasId}`,
|
|
415
416
|
tag: 'canvases.get',
|
|
416
417
|
})
|
|
417
418
|
.subscribe({
|
|
@@ -36,7 +36,7 @@ describe('project', () => {
|
|
|
36
36
|
const result = await project.resolveState(instance, {projectId: 'a'})
|
|
37
37
|
expect(result).toEqual(detail)
|
|
38
38
|
expect(request).toHaveBeenCalledWith({
|
|
39
|
-
|
|
39
|
+
url: '/projects/a',
|
|
40
40
|
query: {includeMembers: 'true', includeFeatures: 'true'},
|
|
41
41
|
tag: 'projects.get',
|
|
42
42
|
})
|
|
@@ -59,7 +59,7 @@ describe('project', () => {
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
expect(request).toHaveBeenCalledWith({
|
|
62
|
-
|
|
62
|
+
url: '/projects/a',
|
|
63
63
|
query: {
|
|
64
64
|
includeMembers: 'false',
|
|
65
65
|
includeFeatures: 'false',
|
|
@@ -80,7 +80,7 @@ describe('project', () => {
|
|
|
80
80
|
|
|
81
81
|
await project.resolveState(instance)
|
|
82
82
|
expect(request).toHaveBeenCalledWith({
|
|
83
|
-
|
|
83
|
+
url: '/projects/p',
|
|
84
84
|
query: {includeMembers: 'true', includeFeatures: 'true'},
|
|
85
85
|
tag: 'projects.get',
|
|
86
86
|
})
|
package/src/project/project.ts
CHANGED
|
@@ -130,7 +130,7 @@ export const project = defineFetcher<
|
|
|
130
130
|
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
131
131
|
switchMap((client) =>
|
|
132
132
|
client.observable.request<Project<boolean, boolean>>({
|
|
133
|
-
|
|
133
|
+
url: `/projects/${resolveProjectId(instance, options)}`,
|
|
134
134
|
query: buildQuery(normalizeProjectOptions(options)),
|
|
135
135
|
tag: 'projects.get',
|
|
136
136
|
}),
|
|
@@ -4,7 +4,7 @@ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
|
4
4
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
5
5
|
import {type StoreState} from '../store/createStoreState'
|
|
6
6
|
import {hashString} from '../utils/hashString'
|
|
7
|
-
import {
|
|
7
|
+
import {randomId} from '../utils/ids'
|
|
8
8
|
import {getProjectionState} from './getProjectionState'
|
|
9
9
|
import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
|
|
10
10
|
import {type ProjectionStoreState} from './types'
|
|
@@ -19,7 +19,7 @@ vi.mock('../utils/ids', async (importOriginal) => {
|
|
|
19
19
|
// Mock implementation uses the external counter
|
|
20
20
|
return {
|
|
21
21
|
...util,
|
|
22
|
-
|
|
22
|
+
randomId: vi.fn(() => {
|
|
23
23
|
const id = `testSubId_${++mockIdCounter}`
|
|
24
24
|
return id
|
|
25
25
|
}),
|
|
@@ -39,8 +39,7 @@ describe('getProjectionState', () => {
|
|
|
39
39
|
let state: StoreState<ProjectionStoreState & {extra?: unknown}>
|
|
40
40
|
|
|
41
41
|
beforeEach(() => {
|
|
42
|
-
|
|
43
|
-
vi.mocked(insecureRandomId).mockClear()
|
|
42
|
+
vi.mocked(randomId).mockClear()
|
|
44
43
|
|
|
45
44
|
// Capture state
|
|
46
45
|
vi.mocked(subscribeToStateAndFetchBatches).mockImplementation((context) => {
|
|
@@ -49,6 +48,9 @@ describe('getProjectionState', () => {
|
|
|
49
48
|
})
|
|
50
49
|
|
|
51
50
|
instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
|
|
51
|
+
// Reset after instance creation: the instanceId also comes from randomId,
|
|
52
|
+
// but the test ids should only count subscription-related calls
|
|
53
|
+
mockIdCounter = 0
|
|
52
54
|
vi.useFakeTimers() // Enable fake timers for each test
|
|
53
55
|
})
|
|
54
56
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {DocumentId, getPublishedId} from '@sanity/id-utils'
|
|
2
|
-
import {type SanityProjectionResult} from 'groq'
|
|
3
2
|
|
|
4
3
|
import {type DocumentHandle} from '../config/sanityConfig'
|
|
5
4
|
import {bindActionByResourceAndPerspective} from '../store/createActionBinder'
|
|
@@ -9,8 +8,9 @@ import {
|
|
|
9
8
|
type SelectorContext,
|
|
10
9
|
type StateSource,
|
|
11
10
|
} from '../store/createStateSourceAction'
|
|
11
|
+
import {type ResolveProjectionResult} from '../typegen/resolve'
|
|
12
12
|
import {hashString} from '../utils/hashString'
|
|
13
|
-
import {
|
|
13
|
+
import {randomId} from '../utils/ids'
|
|
14
14
|
import {omitProperty} from '../utils/object'
|
|
15
15
|
import {setCleanupTimeout} from '../utils/setCleanupTimeout'
|
|
16
16
|
import {projectionStore} from './projectionStore'
|
|
@@ -39,7 +39,7 @@ export function getProjectionState<
|
|
|
39
39
|
options: ProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
|
|
40
40
|
): StateSource<
|
|
41
41
|
| ProjectionValuePending<
|
|
42
|
-
|
|
42
|
+
ResolveProjectionResult<TProjection, TDocumentType, `${TProjectId}.${TDataset}`>
|
|
43
43
|
>
|
|
44
44
|
| undefined
|
|
45
45
|
>
|
|
@@ -85,7 +85,7 @@ export const _getProjectionState = bindActionByResourceAndPerspective(
|
|
|
85
85
|
},
|
|
86
86
|
onSubscribe: ({state}, options: ProjectionOptions<string, string, string, string>) => {
|
|
87
87
|
const {projection, ...docHandle} = options
|
|
88
|
-
const subscriptionId =
|
|
88
|
+
const subscriptionId = randomId(16)
|
|
89
89
|
const documentId = getPublishedId(DocumentId(docHandle.documentId))
|
|
90
90
|
const validProjection = validateProjection(projection)
|
|
91
91
|
const projectionHash = hashString(validProjection)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {type SanityProjectionResult} from 'groq'
|
|
2
1
|
import {filter, firstValueFrom} from 'rxjs'
|
|
3
2
|
|
|
4
3
|
import {bindActionByResourceAndPerspective} from '../store/createActionBinder'
|
|
5
4
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
5
|
+
import {type ResolveProjectionResult} from '../typegen/resolve'
|
|
6
6
|
import {getProjectionState, type ProjectionOptions} from './getProjectionState'
|
|
7
7
|
import {projectionStore} from './projectionStore'
|
|
8
8
|
import {type ProjectionValuePending} from './types'
|
|
@@ -18,7 +18,7 @@ export function resolveProjection<
|
|
|
18
18
|
options: ProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
|
|
19
19
|
): Promise<
|
|
20
20
|
ProjectionValuePending<
|
|
21
|
-
|
|
21
|
+
ResolveProjectionResult<TProjection, TDocumentType, `${TProjectId}.${TDataset}`>
|
|
22
22
|
>
|
|
23
23
|
>
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ describe('projects', () => {
|
|
|
35
35
|
const result = await projects.resolveState(instance)
|
|
36
36
|
expect(result).toEqual(list)
|
|
37
37
|
expect(request).toHaveBeenCalledWith({
|
|
38
|
-
|
|
38
|
+
url: '/projects',
|
|
39
39
|
query: {includeMembers: 'false', includeFeatures: 'true', onlyExplicitMembership: 'false'},
|
|
40
40
|
tag: 'projects.list',
|
|
41
41
|
})
|
|
@@ -58,7 +58,7 @@ describe('projects', () => {
|
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
expect(request).toHaveBeenCalledWith({
|
|
61
|
-
|
|
61
|
+
url: '/projects',
|
|
62
62
|
query: {
|
|
63
63
|
organizationId: 'org123',
|
|
64
64
|
includeMembers: 'true',
|
package/src/projects/projects.ts
CHANGED
|
@@ -58,7 +58,7 @@ export const projects = defineFetcher<
|
|
|
58
58
|
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
59
59
|
switchMap((client) =>
|
|
60
60
|
client.observable.request<Project<boolean, boolean>[]>({
|
|
61
|
-
|
|
61
|
+
url: '/projects',
|
|
62
62
|
query: buildQuery(normalizeProjectsOptions(options)),
|
|
63
63
|
tag: 'projects.list',
|
|
64
64
|
}),
|
package/src/query/queryStore.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {type ResponseQueryOptions} from '@sanity/client'
|
|
2
|
-
import {type SanityQueryResult} from 'groq'
|
|
3
2
|
import {
|
|
4
3
|
catchError,
|
|
5
4
|
combineLatest,
|
|
@@ -36,7 +35,8 @@ import {
|
|
|
36
35
|
} from '../store/createStateSourceAction'
|
|
37
36
|
import {type StoreState} from '../store/createStoreState'
|
|
38
37
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
39
|
-
import {
|
|
38
|
+
import {type ResolveQueryResult} from '../typegen/resolve'
|
|
39
|
+
import {randomId} from '../utils/ids'
|
|
40
40
|
import {setCleanupTimeout} from '../utils/setCleanupTimeout'
|
|
41
41
|
import {
|
|
42
42
|
QUERY_STATE_CLEAR_DELAY,
|
|
@@ -278,7 +278,7 @@ export function getQueryState<
|
|
|
278
278
|
>(
|
|
279
279
|
instance: SanityInstance,
|
|
280
280
|
queryOptions: QueryOptions<TQuery, TDataset, TProjectId>,
|
|
281
|
-
): StateSource<
|
|
281
|
+
): StateSource<ResolveQueryResult<TQuery, `${TProjectId}.${TDataset}`> | undefined>
|
|
282
282
|
|
|
283
283
|
/** @beta */
|
|
284
284
|
export function getQueryState<TData>(
|
|
@@ -309,7 +309,7 @@ const _getQueryState = bindActionByResource(
|
|
|
309
309
|
return queryState?.result
|
|
310
310
|
},
|
|
311
311
|
onSubscribe: ({state, instance}, options: QueryOptions) => {
|
|
312
|
-
const subscriptionId =
|
|
312
|
+
const subscriptionId = randomId(16)
|
|
313
313
|
const key = getQueryKey(instance, options)
|
|
314
314
|
|
|
315
315
|
state.set('addSubscriber', addSubscriber(key, subscriptionId))
|
|
@@ -347,7 +347,7 @@ export function resolveQuery<
|
|
|
347
347
|
>(
|
|
348
348
|
instance: SanityInstance,
|
|
349
349
|
queryOptions: ResolveQueryOptions<TQuery, TDataset, TProjectId>,
|
|
350
|
-
): Promise<
|
|
350
|
+
): Promise<ResolveQueryResult<TQuery, `${TProjectId}.${TDataset}`>>
|
|
351
351
|
|
|
352
352
|
/** @beta */
|
|
353
353
|
export function resolveQuery<TData>(
|
|
@@ -370,7 +370,7 @@ const _resolveQuery = bindActionByResource(
|
|
|
370
370
|
// while a component is suspended leaves a subscriber-less key behind — the
|
|
371
371
|
// component never commits, so no other subscriber exists — and its stored
|
|
372
372
|
// error would be rethrown on every future mount without ever refetching.
|
|
373
|
-
const subscriptionId =
|
|
373
|
+
const subscriptionId = randomId(16)
|
|
374
374
|
state.set('addSubscriber', addSubscriber(key, subscriptionId))
|
|
375
375
|
|
|
376
376
|
const aborted$ = signal
|