@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.2
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/README.md +12 -0
- package/dist/_exports/_internal.d.ts +163 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +332 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/_exports/agent.d.ts +65 -0
- package/dist/_exports/agent.d.ts.map +1 -0
- package/dist/_exports/agent.js +71 -0
- package/dist/_exports/agent.js.map +1 -0
- package/dist/_exports/comlink.d.ts +118 -0
- package/dist/_exports/comlink.d.ts.map +1 -0
- package/dist/_exports/comlink.js +69 -0
- package/dist/_exports/comlink.js.map +1 -0
- package/dist/authStore-CtE9bLHW.js +754 -0
- package/dist/authStore-CtE9bLHW.js.map +1 -0
- package/dist/authStore-DKFX8fn7.d.ts +3179 -0
- package/dist/authStore-DKFX8fn7.d.ts.map +1 -0
- package/dist/clientStore-BtE2-q2i.js +90 -0
- package/dist/clientStore-BtE2-q2i.js.map +1 -0
- package/dist/createSanityInstance-DUI1bGIg.d.ts +278 -0
- package/dist/createSanityInstance-DUI1bGIg.d.ts.map +1 -0
- package/dist/createStateSourceAction-BOKtqFNm.js +300 -0
- package/dist/createStateSourceAction-BOKtqFNm.js.map +1 -0
- package/dist/getEnv-D5RGkQLu.js +8 -0
- package/dist/getEnv-D5RGkQLu.js.map +1 -0
- package/dist/getNodeState-CR3__B2D.js +67 -0
- package/dist/getNodeState-CR3__B2D.js.map +1 -0
- package/dist/index.d.ts +3 -2332
- package/dist/index.js +4171 -4332
- package/dist/index.js.map +1 -1
- package/dist/logger-DAtJb5RQ.js +173 -0
- package/dist/logger-DAtJb5RQ.js.map +1 -0
- package/dist/object-CxXlSVg7.js +83 -0
- package/dist/object-CxXlSVg7.js.map +1 -0
- package/dist/queryStore-DPf9sN3V.js +1346 -0
- package/dist/queryStore-DPf9sN3V.js.map +1 -0
- package/dist/rolldown-runtime-vyAXikos.js +9 -0
- package/dist/setCleanupTimeout-BiM6q1KF.js +31 -0
- package/dist/setCleanupTimeout-BiM6q1KF.js.map +1 -0
- package/dist/telemetryManager-Dq_qCkeI.js +127 -0
- package/dist/telemetryManager-Dq_qCkeI.js.map +1 -0
- package/dist/version-BChjsi4u.js +9 -0
- package/dist/version-BChjsi4u.js.map +1 -0
- package/package.json +47 -52
- package/src/_exports/_internal.ts +27 -0
- package/src/_exports/agent.ts +19 -0
- package/src/_exports/comlink.ts +16 -0
- package/src/_exports/index.ts +125 -64
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- package/src/agent/agentActions.test.ts +16 -60
- package/src/agent/agentActions.ts +25 -38
- package/src/applications/applications.test-d.ts +88 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +367 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +58 -26
- package/src/auth/getOrganizationVerificationState.test.ts +18 -17
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/auth/handleAuthCallback.test.ts +35 -1
- package/src/auth/handleAuthCallback.ts +25 -6
- package/src/auth/logout.test.ts +75 -2
- package/src/auth/logout.ts +23 -4
- package/src/auth/refreshStampedToken.test.ts +219 -469
- package/src/auth/refreshStampedToken.ts +79 -195
- package/src/auth/studioAuth.ts +1 -1
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +146 -13
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +37 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +2 -0
- package/src/client/clientStore.test.ts +62 -37
- package/src/client/clientStore.ts +45 -75
- package/src/client/liveEvents.test.ts +151 -0
- package/src/client/liveEvents.ts +107 -0
- package/src/comlink/controller/actions/destroyController.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateController.test.ts +4 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -1
- package/src/comlink/node/actions/getOrCreateNode.test.ts +9 -3
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +7 -4
- package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
- package/src/comlink/node/getNodeState.test.ts +1 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/comments/addonDatasetStore.test.ts +215 -0
- package/src/comments/addonDatasetStore.ts +249 -0
- package/src/comments/buildCommentThreads.test.ts +83 -0
- package/src/comments/buildCommentThreads.ts +54 -0
- package/src/comments/commentActions.test.ts +540 -0
- package/src/comments/commentActions.ts +555 -0
- package/src/comments/commentFieldPath.test.ts +40 -0
- package/src/comments/commentFieldPath.ts +44 -0
- package/src/comments/commentsConstants.ts +80 -0
- package/src/comments/commentsStore.test.ts +419 -0
- package/src/comments/commentsStore.ts +483 -0
- package/src/comments/normalizeComment.test.ts +116 -0
- package/src/comments/normalizeComment.ts +45 -0
- package/src/comments/observeComments.test.ts +193 -0
- package/src/comments/observeComments.ts +113 -0
- package/src/comments/reducers.test.ts +277 -0
- package/src/comments/reducers.ts +316 -0
- package/src/comments/types.ts +249 -0
- package/src/comments/weakenReferences.test.ts +47 -0
- package/src/comments/weakenReferences.ts +29 -0
- package/src/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +40 -82
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +10 -4
- package/src/document/actions.test.ts +115 -33
- package/src/document/actions.ts +212 -15
- package/src/document/applyDocumentActions.test.ts +35 -13
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +15 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +753 -273
- package/src/document/documentStore.ts +197 -75
- package/src/document/events.test.ts +57 -2
- package/src/document/events.ts +82 -26
- package/src/document/listen.test.ts +56 -0
- package/src/document/listen.ts +74 -21
- package/src/document/listenerEventOperators.test.ts +311 -0
- package/src/document/listenerEventOperators.ts +217 -0
- package/src/document/patchOperations.test.ts +76 -2
- package/src/document/patchOperations.ts +60 -1
- package/src/document/permissions.test.ts +107 -60
- package/src/document/permissions.ts +44 -19
- package/src/document/processActions/create.ts +138 -0
- package/src/document/processActions/delete.ts +100 -0
- package/src/document/processActions/discard.ts +63 -0
- package/src/document/processActions/edit.ts +154 -0
- package/src/document/processActions/processActions.ts +218 -0
- package/src/document/processActions/publish.ts +120 -0
- package/src/document/processActions/releaseArchive.ts +77 -0
- package/src/document/processActions/releaseCreate.ts +59 -0
- package/src/document/processActions/releaseDelete.ts +65 -0
- package/src/document/processActions/releaseEdit.ts +37 -0
- package/src/document/processActions/releasePublish.ts +45 -0
- package/src/document/processActions/releaseSchedule.ts +87 -0
- package/src/document/processActions/releaseUtil.ts +31 -0
- package/src/document/processActions/shared.ts +308 -0
- package/src/document/processActions/unpublish.ts +85 -0
- package/src/document/processActions.test.ts +1129 -121
- package/src/document/reducers.test.ts +772 -64
- package/src/document/reducers.ts +205 -51
- package/src/document/resourceRules.test.ts +178 -0
- package/src/document/resourceRules.ts +117 -0
- package/src/document/sharedListener.test.ts +16 -14
- package/src/document/sharedListener.ts +24 -8
- package/src/document/util.test.ts +46 -0
- package/src/document/util.ts +50 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +73 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +206 -0
- package/src/organization/organization.test-d.ts +36 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +138 -0
- package/src/organizations/organizations.test-d.ts +56 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +107 -0
- package/src/presence/bifurTransport.test.ts +201 -9
- package/src/presence/bifurTransport.ts +98 -14
- package/src/presence/paths.test.ts +70 -0
- package/src/presence/paths.ts +41 -0
- package/src/presence/presenceStore.test.ts +915 -24
- package/src/presence/presenceStore.ts +576 -104
- package/src/presence/types.ts +175 -3
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/types.ts +0 -15
- package/src/project/project.test-d.ts +37 -0
- package/src/project/project.test.ts +157 -12
- package/src/project/project.ts +138 -25
- package/src/projection/getProjectionState.test.ts +18 -20
- package/src/projection/getProjectionState.ts +23 -3
- package/src/projection/projectionQuery.ts +5 -5
- package/src/projection/projectionStore.test.ts +1 -9
- package/src/projection/resolveProjection.test.ts +1 -2
- package/src/projection/resolveProjection.ts +16 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +32 -23
- package/src/projects/projects.test-d.ts +32 -0
- package/src/projects/projects.test.ts +108 -42
- package/src/projects/projects.ts +63 -27
- package/src/query/queryStore.test.ts +259 -123
- package/src/query/queryStore.ts +85 -69
- package/src/query/reducers.test.ts +0 -42
- package/src/query/reducers.ts +2 -18
- package/src/releases/getPerspectiveState.test.ts +17 -23
- package/src/releases/getPerspectiveState.ts +8 -20
- package/src/releases/observeReleases.test.ts +127 -0
- package/src/releases/observeReleases.ts +102 -0
- package/src/releases/releasesStore.test.ts +100 -53
- package/src/releases/releasesStore.ts +69 -44
- package/src/releases/utils/sortReleases.test.ts +38 -2
- package/src/releases/utils/sortReleases.ts +15 -10
- package/src/store/createActionBinder.test.ts +109 -90
- package/src/store/createActionBinder.ts +36 -28
- package/src/store/createSanityInstance.test.ts +15 -13
- package/src/store/createSanityInstance.ts +14 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +38 -43
- package/src/store/createStoreInstance.test.ts +4 -2
- package/src/store/fetcherStore.test-d.ts +121 -0
- package/src/store/fetcherStore.test.ts +299 -0
- package/src/store/fetcherStore.ts +801 -0
- package/src/telemetry/environment.test.ts +119 -0
- package/src/telemetry/environment.ts +92 -0
- package/src/telemetry/events.ts +42 -0
- package/src/telemetry/initTelemetry.test.ts +432 -0
- package/src/telemetry/initTelemetry.ts +218 -0
- package/src/telemetry/telemetryManager.test.ts +327 -0
- package/src/telemetry/telemetryManager.ts +199 -0
- package/src/users/reducers.test.ts +3 -1
- package/src/users/reducers.ts +5 -6
- package/src/users/types.ts +4 -4
- package/src/users/usersStore.test.ts +16 -12
- package/src/users/usersStore.ts +5 -1
- package/src/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/createFetcherStore.test.ts +7 -5
- package/src/utils/createFetcherStore.ts +8 -5
- package/src/utils/getStagingApiHost.test.ts +21 -0
- package/src/utils/getStagingApiHost.ts +14 -0
- package/src/utils/ids.test.ts +1 -29
- package/src/utils/ids.ts +0 -10
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/logger.test.ts +12 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
- package/src/utils/setCleanupTimeout.ts +37 -0
- package/src/document/processActions.ts +0 -707
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {type SanityClient} from '@sanity/client'
|
|
2
|
+
import {
|
|
3
|
+
type ConsentStatus,
|
|
4
|
+
createBatchedStore,
|
|
5
|
+
type SessionId,
|
|
6
|
+
type TelemetryEvent,
|
|
7
|
+
type TelemetryLogger,
|
|
8
|
+
type TelemetryStore,
|
|
9
|
+
} from '@sanity/telemetry'
|
|
10
|
+
|
|
11
|
+
import {createLogger} from '../utils/logger'
|
|
12
|
+
import {CORE_SDK_VERSION} from '../version'
|
|
13
|
+
import {type TelemetryEnvironment} from './environment'
|
|
14
|
+
import {SDKError, SDKHookMounted, SDKSessionEnded, SDKSessionStarted} from './events'
|
|
15
|
+
|
|
16
|
+
const FLUSH_INTERVAL_MS = 30_000
|
|
17
|
+
const CONSENT_TAG = 'telemetry-consent.sdk'
|
|
18
|
+
const BATCH_TAG = 'telemetry.batch'
|
|
19
|
+
|
|
20
|
+
const log = createLogger('telemetry')
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Manages telemetry for a single SDK instance.
|
|
24
|
+
*
|
|
25
|
+
* Wraps `@sanity/telemetry`'s batched store with SDK-specific concerns:
|
|
26
|
+
* consent caching, session lifecycle events, and hook usage tracking.
|
|
27
|
+
* The `environment` is captured at construction time and recorded in
|
|
28
|
+
* the event context so downstream pipelines can distinguish dev and
|
|
29
|
+
* production sessions.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export interface TelemetryManager {
|
|
34
|
+
/**
|
|
35
|
+
* Eagerly resolve and cache the user's consent status.
|
|
36
|
+
* Returns true only when the user has explicitly opted in (`granted`).
|
|
37
|
+
* Call this before logging any events to avoid buffering events that
|
|
38
|
+
* will be dropped on the first flush.
|
|
39
|
+
*/
|
|
40
|
+
checkConsent(): Promise<boolean>
|
|
41
|
+
|
|
42
|
+
/** Log a "SDK Session Started" event */
|
|
43
|
+
logSessionStarted(data: {projectId: string; perspective: string; authMethod: string}): void
|
|
44
|
+
|
|
45
|
+
/** Log a "SDK Hook Mounted" event (deduplicated per hook name) */
|
|
46
|
+
logHookFirstUsed(hookName: string): void
|
|
47
|
+
|
|
48
|
+
/** Log a "SDK Error" event */
|
|
49
|
+
logError(errorType: string, hookName: string): void
|
|
50
|
+
|
|
51
|
+
/** Log a "SDK Session Ended" event and tear down the store */
|
|
52
|
+
endSession(): void
|
|
53
|
+
|
|
54
|
+
/** Tear down the store without logging a session-end event */
|
|
55
|
+
dispose(): void
|
|
56
|
+
|
|
57
|
+
/** The set of hook names used during this session */
|
|
58
|
+
readonly hooksUsed: ReadonlySet<string>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface TelemetryManagerOptions {
|
|
62
|
+
sessionId: string
|
|
63
|
+
getClient: () => SanityClient
|
|
64
|
+
projectId: string
|
|
65
|
+
environment: TelemetryEnvironment
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Creates a telemetry manager for a single SDK instance session.
|
|
70
|
+
*
|
|
71
|
+
* The manager initializes a `createBatchedStore` from `@sanity/telemetry`,
|
|
72
|
+
* caches the consent check for the lifetime of the session, and provides
|
|
73
|
+
* typed methods for each SDK telemetry event.
|
|
74
|
+
*
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
export function createTelemetryManager(options: TelemetryManagerOptions): TelemetryManager {
|
|
78
|
+
const {sessionId, getClient, projectId, environment} = options
|
|
79
|
+
const startedAt = Date.now()
|
|
80
|
+
const emittedHooks = new Set<string>()
|
|
81
|
+
|
|
82
|
+
let cachedConsent: {status: ConsentStatus} | null = null
|
|
83
|
+
|
|
84
|
+
const resolveConsent = async (): Promise<{status: ConsentStatus}> => {
|
|
85
|
+
if (cachedConsent) return cachedConsent
|
|
86
|
+
try {
|
|
87
|
+
const client = getClient()
|
|
88
|
+
const result = await client.request<{status: ConsentStatus}>({
|
|
89
|
+
uri: '/intake/telemetry-status',
|
|
90
|
+
tag: CONSENT_TAG,
|
|
91
|
+
})
|
|
92
|
+
cachedConsent = result
|
|
93
|
+
} catch {
|
|
94
|
+
cachedConsent = {status: 'undetermined'}
|
|
95
|
+
}
|
|
96
|
+
return cachedConsent
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const enrichBatch = (batch: TelemetryEvent[]) =>
|
|
100
|
+
batch.map((event) => {
|
|
101
|
+
// Trace events (`trace.start` / `trace.log` / `trace.error` /
|
|
102
|
+
// `trace.complete`) arrive with their own caller-provided `context`.
|
|
103
|
+
// Log / userProperties events don't. Narrow via `in` so `event`
|
|
104
|
+
// stays typed as `TelemetryEvent`, then merge so we don't drop the
|
|
105
|
+
// trace context. SDK-owned fields below win on conflict.
|
|
106
|
+
const existing =
|
|
107
|
+
'context' in event ? (event.context as Record<string, unknown> | undefined) : undefined
|
|
108
|
+
return {
|
|
109
|
+
...event,
|
|
110
|
+
context: {
|
|
111
|
+
...existing,
|
|
112
|
+
version: CORE_SDK_VERSION,
|
|
113
|
+
environment,
|
|
114
|
+
origin: typeof window !== 'undefined' ? window.location.origin : 'node',
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const sendEvents = async (batch: TelemetryEvent[]): Promise<unknown> => {
|
|
120
|
+
const client = getClient()
|
|
121
|
+
log.debug('sending event batch', {batchSize: batch.length, environment})
|
|
122
|
+
return client.request({
|
|
123
|
+
uri: '/intake/batch',
|
|
124
|
+
method: 'POST',
|
|
125
|
+
body: {projectId, batch: enrichBatch(batch)},
|
|
126
|
+
tag: BATCH_TAG,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const store: TelemetryStore<Record<string, unknown>> = createBatchedStore(
|
|
131
|
+
sessionId as SessionId,
|
|
132
|
+
{
|
|
133
|
+
flushInterval: FLUSH_INTERVAL_MS,
|
|
134
|
+
resolveConsent,
|
|
135
|
+
sendEvents,
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
const logger: TelemetryLogger<Record<string, unknown>> = store.logger
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
async checkConsent() {
|
|
143
|
+
const {status} = await resolveConsent()
|
|
144
|
+
return status === 'granted'
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
logSessionStarted(data) {
|
|
148
|
+
log.debug('event: SDK Session Started', {
|
|
149
|
+
projectId: data.projectId,
|
|
150
|
+
perspective: data.perspective,
|
|
151
|
+
authMethod: data.authMethod,
|
|
152
|
+
version: CORE_SDK_VERSION,
|
|
153
|
+
environment,
|
|
154
|
+
})
|
|
155
|
+
logger.log(SDKSessionStarted, {
|
|
156
|
+
version: CORE_SDK_VERSION,
|
|
157
|
+
...data,
|
|
158
|
+
})
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
logHookFirstUsed(hookName: string) {
|
|
162
|
+
if (emittedHooks.has(hookName)) return
|
|
163
|
+
emittedHooks.add(hookName)
|
|
164
|
+
log.debug('event: SDK Hook Mounted', {hookName})
|
|
165
|
+
logger.log(SDKHookMounted, {hookName})
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
logError(errorType: string, hookName: string) {
|
|
169
|
+
log.debug('event: SDK Error', {errorType, hookName})
|
|
170
|
+
logger.log(SDKError, {errorType, hookName})
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
endSession() {
|
|
174
|
+
const durationSeconds = Math.round((Date.now() - startedAt) / 1000)
|
|
175
|
+
log.debug('event: SDK Session Ended', {
|
|
176
|
+
durationSeconds,
|
|
177
|
+
hooksUsed: [...emittedHooks],
|
|
178
|
+
environment,
|
|
179
|
+
})
|
|
180
|
+
logger.log(SDKSessionEnded, {
|
|
181
|
+
durationSeconds,
|
|
182
|
+
hooksUsed: [...emittedHooks],
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
store.flush().catch(() => {
|
|
186
|
+
// Best-effort flush on dispose; swallow errors
|
|
187
|
+
})
|
|
188
|
+
store.end()
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
dispose() {
|
|
192
|
+
store.end()
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
get hooksUsed(): ReadonlySet<string> {
|
|
196
|
+
return emittedHooks
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -19,7 +19,9 @@ describe('Users Reducers', () => {
|
|
|
19
19
|
// Mock SanityInstance for testing
|
|
20
20
|
const mockInstance: SanityInstance = {
|
|
21
21
|
instanceId: 'test-instance-id',
|
|
22
|
-
config: {
|
|
22
|
+
config: {
|
|
23
|
+
projectId: 'test-project-id',
|
|
24
|
+
},
|
|
23
25
|
isDisposed: () => false,
|
|
24
26
|
dispose: () => {},
|
|
25
27
|
onDispose: () => () => {},
|
package/src/users/reducers.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import {omit} from 'lodash-es'
|
|
2
|
-
|
|
3
1
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
2
|
+
import {omitProperty} from '../utils/object'
|
|
4
3
|
import {type GetUsersOptions, type SanityUserResponse, type UsersStoreState} from './types'
|
|
5
4
|
import {DEFAULT_USERS_BATCH_SIZE} from './usersConstants'
|
|
6
5
|
|
|
7
6
|
/** @internal */
|
|
8
7
|
export const getUsersKey = (
|
|
9
|
-
|
|
8
|
+
instance: SanityInstance,
|
|
10
9
|
{
|
|
11
10
|
resourceType,
|
|
12
11
|
organizationId,
|
|
13
12
|
batchSize = DEFAULT_USERS_BATCH_SIZE,
|
|
14
|
-
projectId,
|
|
13
|
+
projectId = instance.config.projectId,
|
|
15
14
|
userId,
|
|
16
15
|
}: GetUsersOptions = {},
|
|
17
16
|
): string =>
|
|
@@ -48,7 +47,7 @@ export const removeSubscription =
|
|
|
48
47
|
const group = prev.users[key]
|
|
49
48
|
if (!group) return prev
|
|
50
49
|
const subscriptions = group.subscriptions.filter((id) => id !== subscriptionId)
|
|
51
|
-
if (!subscriptions.length) return {...prev, users:
|
|
50
|
+
if (!subscriptions.length) return {...prev, users: omitProperty(prev.users, key)}
|
|
52
51
|
return {...prev, users: {...prev.users, [key]: {...group, subscriptions}}}
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -83,7 +82,7 @@ export const cancelRequest =
|
|
|
83
82
|
const group = prev.users[key]
|
|
84
83
|
if (!group) return prev
|
|
85
84
|
if (group.subscriptions.length) return prev
|
|
86
|
-
return {...prev, users:
|
|
85
|
+
return {...prev, users: omitProperty(prev.users, key)}
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
export const initializeRequest =
|
package/src/users/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {type ProjectHandle} from '../config/sanityConfig'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @public
|
|
3
5
|
*/
|
|
@@ -40,8 +42,7 @@ export interface UserProfile {
|
|
|
40
42
|
/**
|
|
41
43
|
* @public
|
|
42
44
|
*/
|
|
43
|
-
export interface GetUsersOptions {
|
|
44
|
-
projectId?: string
|
|
45
|
+
export interface GetUsersOptions extends ProjectHandle {
|
|
45
46
|
resourceType?: 'organization' | 'project'
|
|
46
47
|
batchSize?: number
|
|
47
48
|
organizationId?: string
|
|
@@ -87,8 +88,7 @@ export interface ResolveUsersOptions extends GetUsersOptions {
|
|
|
87
88
|
/**
|
|
88
89
|
* @public
|
|
89
90
|
*/
|
|
90
|
-
export interface GetUserOptions {
|
|
91
|
-
projectId?: string
|
|
91
|
+
export interface GetUserOptions extends ProjectHandle {
|
|
92
92
|
userId: string
|
|
93
93
|
resourceType?: 'organization' | 'project'
|
|
94
94
|
organizationId?: string
|
|
@@ -87,7 +87,7 @@ describe('usersStore', () => {
|
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
it('initializes users state and cleans up after unsubscribe', async () => {
|
|
90
|
-
const instance = createSanityInstance()
|
|
90
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
91
91
|
const state = getUsersState(instance, {
|
|
92
92
|
resourceType: 'project',
|
|
93
93
|
projectId: 'project1',
|
|
@@ -122,7 +122,7 @@ describe('usersStore', () => {
|
|
|
122
122
|
})
|
|
123
123
|
|
|
124
124
|
it('maintains state when multiple subscribers exist', async () => {
|
|
125
|
-
const instance = createSanityInstance()
|
|
125
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
126
126
|
const state = getUsersState(instance, {
|
|
127
127
|
resourceType: 'project',
|
|
128
128
|
projectId: 'project1',
|
|
@@ -165,7 +165,7 @@ describe('usersStore', () => {
|
|
|
165
165
|
})
|
|
166
166
|
|
|
167
167
|
it('resolveUsers works without affecting subscriber cleanup', async () => {
|
|
168
|
-
const instance = createSanityInstance()
|
|
168
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
169
169
|
const options: GetUsersOptions = {resourceType: 'project', projectId: 'project1'}
|
|
170
170
|
|
|
171
171
|
const state = getUsersState(instance, options)
|
|
@@ -198,7 +198,7 @@ describe('usersStore', () => {
|
|
|
198
198
|
})
|
|
199
199
|
|
|
200
200
|
it('handles abort signal in resolveUsers', async () => {
|
|
201
|
-
const instance = createSanityInstance()
|
|
201
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
202
202
|
const options: GetUsersOptions = {resourceType: 'project', projectId: 'project1'}
|
|
203
203
|
const abortController = new AbortController()
|
|
204
204
|
|
|
@@ -221,7 +221,7 @@ describe('usersStore', () => {
|
|
|
221
221
|
})
|
|
222
222
|
|
|
223
223
|
it('loads more users when loadMoreUsers is called', async () => {
|
|
224
|
-
const instance = createSanityInstance()
|
|
224
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
225
225
|
const options: GetUsersOptions = {resourceType: 'project', projectId: 'project1'}
|
|
226
226
|
|
|
227
227
|
// First response has nextCursor
|
|
@@ -291,7 +291,7 @@ describe('usersStore', () => {
|
|
|
291
291
|
})
|
|
292
292
|
|
|
293
293
|
it('throws error when loadMoreUsers is called without initial data', async () => {
|
|
294
|
-
const instance = createSanityInstance()
|
|
294
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
295
295
|
|
|
296
296
|
// Expect loadMoreUsers to throw when no data is loaded
|
|
297
297
|
await expect(
|
|
@@ -302,7 +302,7 @@ describe('usersStore', () => {
|
|
|
302
302
|
})
|
|
303
303
|
|
|
304
304
|
it('throws error when loadMoreUsers is called with no more data available', async () => {
|
|
305
|
-
const instance = createSanityInstance()
|
|
305
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
306
306
|
const options: GetUsersOptions = {resourceType: 'project', projectId: 'project1'}
|
|
307
307
|
|
|
308
308
|
// Response with no nextCursor
|
|
@@ -325,7 +325,7 @@ describe('usersStore', () => {
|
|
|
325
325
|
})
|
|
326
326
|
|
|
327
327
|
it('handles errors in users fetching', async () => {
|
|
328
|
-
const instance = createSanityInstance()
|
|
328
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
329
329
|
const errorMessage = 'Failed to fetch users'
|
|
330
330
|
|
|
331
331
|
// Override request to simulate error
|
|
@@ -351,7 +351,7 @@ describe('usersStore', () => {
|
|
|
351
351
|
})
|
|
352
352
|
|
|
353
353
|
it('delays users state removal after unsubscribe', async () => {
|
|
354
|
-
const instance = createSanityInstance()
|
|
354
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
355
355
|
const options: GetUsersOptions = {resourceType: 'project', projectId: 'project1'}
|
|
356
356
|
const state = getUsersState(instance, options)
|
|
357
357
|
const unsubscribe = state.subscribe()
|
|
@@ -370,7 +370,7 @@ describe('usersStore', () => {
|
|
|
370
370
|
})
|
|
371
371
|
|
|
372
372
|
it('preserves users state if a new subscriber subscribes before cleanup delay', async () => {
|
|
373
|
-
const instance = createSanityInstance()
|
|
373
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
374
374
|
const state = getUsersState(instance, {
|
|
375
375
|
resourceType: 'project',
|
|
376
376
|
projectId: 'project1',
|
|
@@ -412,7 +412,7 @@ describe('usersStore', () => {
|
|
|
412
412
|
})
|
|
413
413
|
|
|
414
414
|
it('fetches a single user with a project-scoped ID', async () => {
|
|
415
|
-
const instance = createSanityInstance()
|
|
415
|
+
const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
416
416
|
const projectUserId = 'p12345'
|
|
417
417
|
const mockProjectUser: PatchedSanityUserFromClient = {
|
|
418
418
|
id: projectUserId,
|
|
@@ -451,6 +451,7 @@ describe('usersStore', () => {
|
|
|
451
451
|
expect(specificRequest).toHaveBeenCalledWith({
|
|
452
452
|
method: 'GET',
|
|
453
453
|
uri: `/users/${projectUserId}`,
|
|
454
|
+
tag: 'users.get',
|
|
454
455
|
})
|
|
455
456
|
|
|
456
457
|
const expectedUser: SanityUser = {
|
|
@@ -476,7 +477,10 @@ describe('usersStore', () => {
|
|
|
476
477
|
})
|
|
477
478
|
|
|
478
479
|
it('fetches a single user with a global-scoped ID', async () => {
|
|
479
|
-
const instance = createSanityInstance(
|
|
480
|
+
const instance = createSanityInstance({
|
|
481
|
+
projectId: 'test',
|
|
482
|
+
dataset: 'test',
|
|
483
|
+
})
|
|
480
484
|
const globalUserId = 'g12345'
|
|
481
485
|
const mockGlobalUser: SanityUser = {
|
|
482
486
|
sanityUserId: globalUserId,
|
package/src/users/usersStore.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {createStateSourceAction, type SelectorContext} from '../store/createStat
|
|
|
31
31
|
import {type StoreState} from '../store/createStoreState'
|
|
32
32
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
33
33
|
import {insecureRandomId} from '../utils/ids'
|
|
34
|
+
import {setCleanupTimeout} from '../utils/setCleanupTimeout'
|
|
34
35
|
import {
|
|
35
36
|
addSubscription,
|
|
36
37
|
cancelRequest,
|
|
@@ -130,6 +131,7 @@ const listenForLoadMoreAndFetch = ({state, instance}: StoreContext<UsersStoreSta
|
|
|
130
131
|
.request<PatchedSanityUserFromClient>({
|
|
131
132
|
method: 'GET',
|
|
132
133
|
uri: `/users/${userId}`,
|
|
134
|
+
tag: 'users.get',
|
|
133
135
|
})
|
|
134
136
|
.pipe(
|
|
135
137
|
map((user) => {
|
|
@@ -183,6 +185,7 @@ const listenForLoadMoreAndFetch = ({state, instance}: StoreContext<UsersStoreSta
|
|
|
183
185
|
.request<SanityUser | SanityUserResponse>({
|
|
184
186
|
method: 'GET',
|
|
185
187
|
uri: `access/${resourceType}/${resourceId}/users/${userId}`,
|
|
188
|
+
tag: 'users.get',
|
|
186
189
|
})
|
|
187
190
|
.pipe(
|
|
188
191
|
map((response) => {
|
|
@@ -251,6 +254,7 @@ const listenForLoadMoreAndFetch = ({state, instance}: StoreContext<UsersStoreSta
|
|
|
251
254
|
client.observable.request<SanityUserResponse>({
|
|
252
255
|
method: 'GET',
|
|
253
256
|
uri: `access/${resource.type}/${resource.id}/users`,
|
|
257
|
+
tag: 'users.list',
|
|
254
258
|
query: cursor
|
|
255
259
|
? {nextCursor: cursor, limit: batchSize.toString()}
|
|
256
260
|
: {limit: batchSize.toString()},
|
|
@@ -310,7 +314,7 @@ export const getUsersState = bindActionGlobally(
|
|
|
310
314
|
const key = getUsersKey(instance, options)
|
|
311
315
|
state.set('addSubscription', addSubscription(subscriptionId, key))
|
|
312
316
|
return () => {
|
|
313
|
-
|
|
317
|
+
setCleanupTimeout(
|
|
314
318
|
() => state.set('removeSubscription', removeSubscription(subscriptionId, key)),
|
|
315
319
|
USERS_STATE_CLEAR_DELAY,
|
|
316
320
|
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {buildQuery} from './buildQuery'
|
|
4
|
+
|
|
5
|
+
describe('buildQuery', () => {
|
|
6
|
+
it('stringifies scalars, drops undefined, and passes string arrays through', () => {
|
|
7
|
+
expect(buildQuery({a: true, b: 0, c: 'x', d: undefined, e: ['p', 'q']})).toEqual({
|
|
8
|
+
a: 'true',
|
|
9
|
+
b: '0',
|
|
10
|
+
c: 'x',
|
|
11
|
+
e: ['p', 'q'],
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialises store options into a `@sanity/client` request query object.
|
|
3
|
+
* Scalars are stringified and `undefined` values dropped; string arrays pass
|
|
4
|
+
* through untouched so the client serialises them as repeated keys
|
|
5
|
+
* (`permissions=a&permissions=b`).
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export function buildQuery(
|
|
10
|
+
params: Record<string, string | number | boolean | string[] | undefined>,
|
|
11
|
+
): Record<string, string | string[]> {
|
|
12
|
+
return Object.fromEntries(
|
|
13
|
+
Object.entries(params)
|
|
14
|
+
.filter(([, value]) => value !== undefined)
|
|
15
|
+
.map(([key, value]) => [key, Array.isArray(value) ? value : String(value)]),
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -9,7 +9,7 @@ describe('createFetcherStore', () => {
|
|
|
9
9
|
let instance: SanityInstance
|
|
10
10
|
|
|
11
11
|
beforeEach(() => {
|
|
12
|
-
instance = createSanityInstance()
|
|
12
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
13
13
|
vi.useRealTimers()
|
|
14
14
|
})
|
|
15
15
|
|
|
@@ -127,8 +127,9 @@ describe('createFetcherStore', () => {
|
|
|
127
127
|
expect(fetchSpy).toHaveBeenCalledTimes(1)
|
|
128
128
|
|
|
129
129
|
// Second subscription within throttle interval
|
|
130
|
-
const
|
|
131
|
-
|
|
130
|
+
const stateSource2 = store.getState(instance, 1)
|
|
131
|
+
const sub2 = stateSource2.subscribe()
|
|
132
|
+
await firstValueFrom(stateSource2.observable)
|
|
132
133
|
expect(fetchSpy).toHaveBeenCalledTimes(1)
|
|
133
134
|
|
|
134
135
|
// Advance past throttle interval
|
|
@@ -136,8 +137,9 @@ describe('createFetcherStore', () => {
|
|
|
136
137
|
await vi.advanceTimersByTimeAsync(1000)
|
|
137
138
|
|
|
138
139
|
// Third subscription after throttle interval
|
|
139
|
-
const
|
|
140
|
-
|
|
140
|
+
const stateSource3 = store.getState(instance, 1)
|
|
141
|
+
const sub3 = stateSource3.subscribe()
|
|
142
|
+
await firstValueFrom(stateSource3.observable)
|
|
141
143
|
expect(fetchSpy).toHaveBeenCalledTimes(2)
|
|
142
144
|
|
|
143
145
|
sub1()
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {omit} from 'lodash-es'
|
|
2
1
|
import {asapScheduler, EMPTY, firstValueFrom, from, Observable} from 'rxjs'
|
|
3
2
|
import {
|
|
4
3
|
catchError,
|
|
@@ -23,6 +22,8 @@ import {
|
|
|
23
22
|
} from '../store/createStateSourceAction'
|
|
24
23
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
25
24
|
import {insecureRandomId} from '../utils/ids'
|
|
25
|
+
import {omitProperty} from '../utils/object'
|
|
26
|
+
import {setCleanupTimeout} from './setCleanupTimeout'
|
|
26
27
|
|
|
27
28
|
interface CreateFetcherStoreOptions<TParams extends unknown[], TData> {
|
|
28
29
|
/**
|
|
@@ -182,8 +183,10 @@ export function createFetcherStore<TParams extends unknown[], TData>({
|
|
|
182
183
|
stateByParams: {
|
|
183
184
|
...prev.stateByParams,
|
|
184
185
|
[entry.key]: {
|
|
185
|
-
...
|
|
186
|
-
...
|
|
186
|
+
...omitProperty(entry, 'error'),
|
|
187
|
+
...(prev.stateByParams[entry.key]
|
|
188
|
+
? omitProperty(prev.stateByParams[entry.key], 'error')
|
|
189
|
+
: {}),
|
|
187
190
|
data,
|
|
188
191
|
},
|
|
189
192
|
},
|
|
@@ -247,14 +250,14 @@ export function createFetcherStore<TParams extends unknown[], TData>({
|
|
|
247
250
|
}))
|
|
248
251
|
|
|
249
252
|
return () => {
|
|
250
|
-
|
|
253
|
+
setCleanupTimeout(() => {
|
|
251
254
|
state.set('removeSubscription', (prev: FetcherStoreState<TParams, TData>) => {
|
|
252
255
|
const entry = prev.stateByParams[key]
|
|
253
256
|
if (!entry) return prev
|
|
254
257
|
|
|
255
258
|
const newSubs = (entry.subscriptions || []).filter((id) => id !== subscriptionId)
|
|
256
259
|
if (newSubs.length === 0) {
|
|
257
|
-
return {stateByParams:
|
|
260
|
+
return {stateByParams: omitProperty(prev.stateByParams, key)}
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
return {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {getStagingApiHost} from './getStagingApiHost'
|
|
4
|
+
|
|
5
|
+
describe('getStagingApiHost', () => {
|
|
6
|
+
it('returns staging host when __SANITY_STAGING__ is true', () => {
|
|
7
|
+
vi.stubGlobal('__SANITY_STAGING__', true)
|
|
8
|
+
expect(getStagingApiHost()).toBe('https://api.sanity.work')
|
|
9
|
+
vi.unstubAllGlobals()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('returns undefined when __SANITY_STAGING__ is false', () => {
|
|
13
|
+
vi.stubGlobal('__SANITY_STAGING__', false)
|
|
14
|
+
expect(getStagingApiHost()).toBeUndefined()
|
|
15
|
+
vi.unstubAllGlobals()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('returns undefined when __SANITY_STAGING__ is not defined', () => {
|
|
19
|
+
expect(getStagingApiHost()).toBeUndefined()
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const __SANITY_STAGING__: boolean | undefined
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the staging API host if the `__SANITY_STAGING__` build-time flag is
|
|
5
|
+
* set to `true` (mirroring how Sanity Studio detects staging builds).
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export function getStagingApiHost(): string | undefined {
|
|
10
|
+
if (typeof __SANITY_STAGING__ !== 'undefined' && __SANITY_STAGING__ === true) {
|
|
11
|
+
return 'https://api.sanity.work'
|
|
12
|
+
}
|
|
13
|
+
return undefined
|
|
14
|
+
}
|
package/src/utils/ids.test.ts
CHANGED
|
@@ -1,34 +1,6 @@
|
|
|
1
1
|
import {describe, expect, it} from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
describe('getDraftId', () => {
|
|
6
|
-
it('should add drafts prefix to non-draft ids', () => {
|
|
7
|
-
expect(getDraftId('abc123')).toBe('drafts.abc123')
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('should not modify ids that already have drafts prefix', () => {
|
|
11
|
-
expect(getDraftId('drafts.abc123')).toBe('drafts.abc123')
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('should handle empty string', () => {
|
|
15
|
-
expect(getDraftId('')).toBe('drafts.')
|
|
16
|
-
})
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('getPublishedId', () => {
|
|
20
|
-
it('should remove drafts prefix from draft ids', () => {
|
|
21
|
-
expect(getPublishedId('drafts.abc123')).toBe('abc123')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('should not modify ids that dont have drafts prefix', () => {
|
|
25
|
-
expect(getPublishedId('abc123')).toBe('abc123')
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('should handle empty string', () => {
|
|
29
|
-
expect(getPublishedId('')).toBe('')
|
|
30
|
-
})
|
|
31
|
-
})
|
|
3
|
+
import {insecureRandomId} from './ids'
|
|
32
4
|
|
|
33
5
|
describe('insecureRandomId', () => {
|
|
34
6
|
it('should generate 16-character string', () => {
|
package/src/utils/ids.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
export function getPublishedId(id: string): string {
|
|
2
|
-
const draftsPrefix = 'drafts.'
|
|
3
|
-
return id.startsWith(draftsPrefix) ? id.slice(draftsPrefix.length) : id
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function getDraftId(id: string): string {
|
|
7
|
-
const draftsPrefix = 'drafts.'
|
|
8
|
-
return id.startsWith(draftsPrefix) ? id : `${draftsPrefix}${id}`
|
|
9
|
-
}
|
|
10
|
-
|
|
11
1
|
export function insecureRandomId(): string {
|
|
12
2
|
return Array.from({length: 16}, () => Math.floor(Math.random() * 16).toString(16)).join('')
|
|
13
3
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A response field that the API embeds only when its `include` token was
|
|
3
|
+
* requested. Given the full set of tokens `All`, the token `Key` this field
|
|
4
|
+
* maps to, and the tokens actually `Requested`, it resolves to:
|
|
5
|
+
*
|
|
6
|
+
* - `unknown` (field absent) when nothing — or nothing matching `Key` — was requested
|
|
7
|
+
* - `Field` (present, as declared) when a literal request list contains `Key`
|
|
8
|
+
* - `Partial<Field>` (optional) when `Requested` is the wide `All` union, i.e.
|
|
9
|
+
* an `include` array whose members aren't known at the type level
|
|
10
|
+
*
|
|
11
|
+
* Absent arms return `unknown` so they vanish under intersection. Mirrors the
|
|
12
|
+
* `boolean extends Flag` trick the organization/project types use, but for an
|
|
13
|
+
* include-token union rather than a boolean flag.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export type Included<All extends string, Key extends All, Requested extends All, Field> = [
|
|
18
|
+
All,
|
|
19
|
+
] extends [Requested]
|
|
20
|
+
? Partial<Field>
|
|
21
|
+
: [Key] extends [Requested]
|
|
22
|
+
? Field
|
|
23
|
+
: unknown
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether the requested include tokens intersect a given set — used to decide
|
|
27
|
+
* whether a parent field (e.g. an application's `activeDeployment`) is present,
|
|
28
|
+
* when requesting any of several tokens forces it in.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export type IncludesAny<Requested extends string, Set extends string> = [Requested & Set] extends [
|
|
33
|
+
never,
|
|
34
|
+
]
|
|
35
|
+
? false
|
|
36
|
+
: true
|