@sanity/client 7.19.0 → 7.21.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/stega.d.ts CHANGED
@@ -804,6 +804,32 @@ export declare interface ClientConfig {
804
804
  * Lineage token for recursion control
805
805
  */
806
806
  lineage?: string
807
+ /**
808
+ * A custom request handler that intercepts all HTTP requests made by the client.
809
+ *
810
+ * Useful for logging, adding custom headers, refreshing auth tokens, rate limiting, etc.
811
+ *
812
+ * When using `withConfig()`, the new handler **replaces** the previous one (it does not
813
+ * wrap it). To compose handlers, you can chain them manually:
814
+ *
815
+ * ```ts
816
+ * const parent = createClient({...config, _requestHandler: handlerA})
817
+ * const child = parent.withConfig({
818
+ * _requestHandler: (req, defaultRequester) =>
819
+ * handlerB(req, (opts) => handlerA(opts, defaultRequester)),
820
+ * })
821
+ * ```
822
+ *
823
+ * Setting `_requestHandler` to `undefined` via `withConfig()` removes the handler.
824
+ *
825
+ * Note: This only applies to HTTP requests. Real-time listener connections
826
+ * (`client.listen()`) use EventSource and are not intercepted by this handler.
827
+ *
828
+ * @internal
829
+ * @deprecated Don't use outside of Sanity internals
830
+ * @see {@link RequestHandler}
831
+ */
832
+ _requestHandler?: RequestHandler
807
833
  }
808
834
 
809
835
  declare type ClientConfigResource =
@@ -2299,6 +2325,13 @@ export declare type ListenParams = {
2299
2325
  [key: string]: Any
2300
2326
  }
2301
2327
 
2328
+ declare type ListOptions = {
2329
+ includeMembers?: boolean
2330
+ includeFeatures?: boolean
2331
+ organizationId?: string
2332
+ onlyExplicitMembership?: boolean
2333
+ }
2334
+
2302
2335
  /**
2303
2336
  * @public
2304
2337
  */
@@ -2311,6 +2344,7 @@ export declare class LiveClient {
2311
2344
  events({
2312
2345
  includeDrafts,
2313
2346
  tag: _tag,
2347
+ waitFor,
2314
2348
  }?: {
2315
2349
  includeDrafts?: boolean
2316
2350
  /**
@@ -2319,6 +2353,11 @@ export declare class LiveClient {
2319
2353
  * @defaultValue `undefined`
2320
2354
  */
2321
2355
  tag?: string
2356
+ /**
2357
+ * Delays events until after a Sanity Function has processed them and called the callback endpoint.
2358
+ * When omitted, events are delivered immediately.
2359
+ */
2360
+ waitFor?: 'function'
2322
2361
  }): Observable<LiveEvent>
2323
2362
  }
2324
2363
 
@@ -2862,19 +2901,13 @@ export declare class ObservableProjectsClient {
2862
2901
  *
2863
2902
  * @param options - Options for the list request
2864
2903
  * - `includeMembers` - Whether to include members in the response (default: true)
2904
+ * - `includeFeatures` - Whether to include features in the response (default: true)
2865
2905
  * - `organizationId` - ID of the organization to fetch projects for
2866
- * - `onlyExplicitMembership` - Only include projects where the user has explicit membership (default: false)
2867
- */
2868
- list(options?: {
2869
- includeMembers?: true
2870
- organizationId?: string
2871
- onlyExplicitMembership?: boolean
2872
- }): Observable<SanityProject[]>
2873
- list(options?: {
2874
- includeMembers?: false
2875
- organizationId?: string
2876
- onlyExplicitMembership?: boolean
2877
- }): Observable<Omit<SanityProject, 'members'>[]>
2906
+ * - `onlyExplicitMembership` - Whether to include only projects with explicit membership (default: false)
2907
+ */
2908
+ list<T extends ListOptions>(
2909
+ options?: T,
2910
+ ): Observable<Omit<SanityProject, OmittedProjectFields<T>>[]>
2878
2911
  /**
2879
2912
  * Fetch a project by project ID
2880
2913
  *
@@ -4035,6 +4068,18 @@ export declare class ObservableUsersClient {
4035
4068
  ): Observable<T extends 'me' ? CurrentSanityUser : SanityUser>
4036
4069
  }
4037
4070
 
4071
+ declare type OmittedProjectFields<T extends ListOptions | undefined> =
4072
+ | (T extends {
4073
+ includeMembers: false
4074
+ }
4075
+ ? 'members'
4076
+ : never)
4077
+ | (T extends {
4078
+ includeFeatures: false
4079
+ }
4080
+ ? 'features'
4081
+ : never)
4082
+
4038
4083
  /**
4039
4084
  * The listener connection has been established
4040
4085
  * note: it's usually a better option to use the 'welcome' event
@@ -4251,19 +4296,11 @@ export declare class ProjectsClient {
4251
4296
  *
4252
4297
  * @param options - Options for the list request
4253
4298
  * - `includeMembers` - Whether to include members in the response (default: true)
4299
+ * - `includeFeatures` - Whether to include features in the response (default: true)
4254
4300
  * - `organizationId` - ID of the organization to fetch projects for
4255
- * - `onlyExplicitMembership` - Only include projects where the user has explicit membership (default: false)
4256
- */
4257
- list(options?: {
4258
- includeMembers?: true
4259
- organizationId?: string
4260
- onlyExplicitMembership?: boolean
4261
- }): Promise<SanityProject[]>
4262
- list(options?: {
4263
- includeMembers?: false
4264
- organizationId?: string
4265
- onlyExplicitMembership?: boolean
4266
- }): Promise<Omit<SanityProject, 'members'>[]>
4301
+ * - `onlyExplicitMembership` - Whether to include only projects with explicit membership (default: false)
4302
+ */
4303
+ list<T extends ListOptions>(options?: T): Promise<Omit<SanityProject, OmittedProjectFields<T>>[]>
4267
4304
  /**
4268
4305
  * Fetch a project by project ID
4269
4306
  *
@@ -4946,6 +4983,44 @@ export declare interface ReplaceVersionAction {
4946
4983
  */
4947
4984
  export declare const requester: Requester
4948
4985
 
4986
+ /**
4987
+ * A function that intercepts HTTP requests made by the client.
4988
+ *
4989
+ * Receives the resolved request options, a `defaultRequester` function that
4990
+ * executes the request through the normal pipeline, and a `client` instance
4991
+ * without a `_requestHandler` (to avoid recursive interception).
4992
+ *
4993
+ * The consumer can:
4994
+ * - Modify request options before calling `defaultRequester`
4995
+ * - Transform the response stream (e.g. via `pipe`)
4996
+ * - Skip `defaultRequester` entirely and return a custom Observable
4997
+ * - Use `client` to make additional requests (e.g. refresh an auth token on 401)
4998
+ *
4999
+ * When set via `withConfig()`, the new handler **replaces** (not wraps) the previous one.
5000
+ *
5001
+ * Note: This only applies to HTTP requests. Real-time listener connections
5002
+ * (`client.listen()`) use EventSource and are not intercepted by this handler.
5003
+ *
5004
+ * @param request - The resolved request options including `url`
5005
+ * @param defaultRequester - Executes the request through the normal pipeline
5006
+ * @param client - A client instance with the same configuration but without a `_requestHandler`,
5007
+ * useful for making side requests (e.g. token refresh) without triggering the handler recursively
5008
+ *
5009
+ * @internal
5010
+ * @deprecated Don't use outside of Sanity internals
5011
+ */
5012
+ export declare type RequestHandler = (
5013
+ request: RequestOptions & {
5014
+ url: string
5015
+ },
5016
+ defaultRequester: (
5017
+ options: RequestOptions & {
5018
+ url: string
5019
+ },
5020
+ ) => Observable<HttpRequestEvent>,
5021
+ client: SanityClient,
5022
+ ) => Observable<HttpRequestEvent>
5023
+
4949
5024
  /** @internal */
4950
5025
  export declare interface RequestObservableOptions extends Omit<RequestOptions, 'url'> {
4951
5026
  url?: string
@@ -5922,6 +5997,7 @@ export declare interface SanityProject {
5922
5997
  pendingInvites?: number
5923
5998
  maxRetentionDays?: number
5924
5999
  members: SanityProjectMember[]
6000
+ features: string[]
5925
6001
  metadata: {
5926
6002
  cliInitializedAt?: string
5927
6003
  color?: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/client",
3
- "version": "7.19.0",
3
+ "version": "7.21.0",
4
4
  "description": "Client for retrieving, creating and patching data from Sanity.io",
5
5
  "keywords": [
6
6
  "sanity",
@@ -130,7 +130,7 @@
130
130
  },
131
131
  "dependencies": {
132
132
  "@sanity/eventsource": "^5.0.2",
133
- "get-it": "^8.7.0",
133
+ "get-it": "^8.7.2",
134
134
  "nanoid": "^3.3.11",
135
135
  "rxjs": "^7.0.0"
136
136
  },
@@ -43,6 +43,7 @@ import type {
43
43
  RawQuerylessQueryResponse,
44
44
  RawQueryResponse,
45
45
  RawRequestOptions,
46
+ RequestOptions,
46
47
  SanityDocument,
47
48
  SanityDocumentStub,
48
49
  SingleActionResult,
@@ -87,6 +88,7 @@ export class ObservableSanityClient {
87
88
  * Private properties
88
89
  */
89
90
  #clientConfig: InitializedClientConfig
91
+ #originalHttpRequest: HttpRequest
90
92
  #httpRequest: HttpRequest
91
93
 
92
94
  /**
@@ -97,7 +99,22 @@ export class ObservableSanityClient {
97
99
  constructor(httpRequest: HttpRequest, config: ClientConfig = defaultConfig) {
98
100
  this.config(config)
99
101
 
100
- this.#httpRequest = httpRequest
102
+ this.#originalHttpRequest = httpRequest
103
+ const requestHandler = config._requestHandler
104
+
105
+ this.#httpRequest = requestHandler
106
+ ? (() => {
107
+ let bareClient: SanityClient | undefined
108
+ const wrapped: HttpRequest = (options, requester) => {
109
+ const opts = options as RequestOptions & {url: string}
110
+ if (!bareClient) {
111
+ bareClient = new SanityClient(httpRequest, {...config, _requestHandler: undefined})
112
+ }
113
+ return requestHandler(opts, (o) => httpRequest(o, requester), bareClient)
114
+ }
115
+ return wrapped
116
+ })()
117
+ : httpRequest
101
118
 
102
119
  this.assets = new ObservableAssetsClient(this, this.#httpRequest)
103
120
  this.datasets = new ObservableDatasetsClient(this, this.#httpRequest)
@@ -117,7 +134,7 @@ export class ObservableSanityClient {
117
134
  * Clone the client - returns a new instance
118
135
  */
119
136
  clone(): ObservableSanityClient {
120
- return new ObservableSanityClient(this.#httpRequest, this.config())
137
+ return new ObservableSanityClient(this.#originalHttpRequest, this.config())
121
138
  }
122
139
 
123
140
  /**
@@ -150,7 +167,7 @@ export class ObservableSanityClient {
150
167
  */
151
168
  withConfig(newConfig?: Partial<ClientConfig>): ObservableSanityClient {
152
169
  const thisConfig = this.config()
153
- return new ObservableSanityClient(this.#httpRequest, {
170
+ return new ObservableSanityClient(this.#originalHttpRequest, {
154
171
  ...thisConfig,
155
172
  ...newConfig,
156
173
  stega: {
@@ -1128,6 +1145,7 @@ export class SanityClient {
1128
1145
  * Private properties
1129
1146
  */
1130
1147
  #clientConfig: InitializedClientConfig
1148
+ #originalHttpRequest: HttpRequest
1131
1149
  #httpRequest: HttpRequest
1132
1150
 
1133
1151
  /**
@@ -1138,7 +1156,21 @@ export class SanityClient {
1138
1156
  constructor(httpRequest: HttpRequest, config: ClientConfig = defaultConfig) {
1139
1157
  this.config(config)
1140
1158
 
1141
- this.#httpRequest = httpRequest
1159
+ this.#originalHttpRequest = httpRequest
1160
+ const requestHandler = config._requestHandler
1161
+ this.#httpRequest = requestHandler
1162
+ ? (() => {
1163
+ let bareClient: SanityClient | undefined
1164
+ const wrapped: HttpRequest = (options, requester) => {
1165
+ const opts = options as RequestOptions & {url: string}
1166
+ if (!bareClient) {
1167
+ bareClient = new SanityClient(httpRequest, {...config, _requestHandler: undefined})
1168
+ }
1169
+ return requestHandler(opts, (o) => httpRequest(o, requester), bareClient)
1170
+ }
1171
+ return wrapped
1172
+ })()
1173
+ : httpRequest
1142
1174
 
1143
1175
  this.assets = new AssetsClient(this, this.#httpRequest)
1144
1176
  this.datasets = new DatasetsClient(this, this.#httpRequest)
@@ -1160,7 +1192,7 @@ export class SanityClient {
1160
1192
  * Clone the client - returns a new instance
1161
1193
  */
1162
1194
  clone(): SanityClient {
1163
- return new SanityClient(this.#httpRequest, this.config())
1195
+ return new SanityClient(this.#originalHttpRequest, this.config())
1164
1196
  }
1165
1197
 
1166
1198
  /**
@@ -1197,7 +1229,7 @@ export class SanityClient {
1197
1229
  */
1198
1230
  withConfig(newConfig?: Partial<ClientConfig>): SanityClient {
1199
1231
  const thisConfig = this.config()
1200
- return new SanityClient(this.#httpRequest, {
1232
+ return new SanityClient(this.#originalHttpRequest, {
1201
1233
  ...thisConfig,
1202
1234
  ...newConfig,
1203
1235
  stega: {
package/src/data/live.ts CHANGED
@@ -35,6 +35,7 @@ export class LiveClient {
35
35
  events({
36
36
  includeDrafts = false,
37
37
  tag: _tag,
38
+ waitFor,
38
39
  }: {
39
40
  includeDrafts?: boolean
40
41
  /**
@@ -43,6 +44,11 @@ export class LiveClient {
43
44
  * @defaultValue `undefined`
44
45
  */
45
46
  tag?: string
47
+ /**
48
+ * Delays events until after a Sanity Function has processed them and called the callback endpoint.
49
+ * When omitted, events are delivered immediately.
50
+ */
51
+ waitFor?: 'function'
46
52
  } = {}): Observable<LiveEvent> {
47
53
  const {
48
54
  projectId,
@@ -74,6 +80,9 @@ export class LiveClient {
74
80
  if (includeDrafts) {
75
81
  url.searchParams.set('includeDrafts', 'true')
76
82
  }
83
+ if (waitFor) {
84
+ url.searchParams.set('waitFor', waitFor)
85
+ }
77
86
  const esOptions: EventSourceInit & {headers?: Record<string, string>} = {}
78
87
  if (includeDrafts && withCredentials) {
79
88
  esOptions.withCredentials = true
@@ -4,6 +4,17 @@ import {_request} from '../data/dataMethods'
4
4
  import type {ObservableSanityClient, SanityClient} from '../SanityClient'
5
5
  import type {HttpRequest, SanityProject} from '../types'
6
6
 
7
+ type ListOptions = {
8
+ includeMembers?: boolean
9
+ includeFeatures?: boolean
10
+ organizationId?: string
11
+ onlyExplicitMembership?: boolean
12
+ }
13
+
14
+ type OmittedProjectFields<T extends ListOptions | undefined> =
15
+ | (T extends {includeMembers: false} ? 'members' : never)
16
+ | (T extends {includeFeatures: false} ? 'features' : never)
17
+
7
18
  /** @internal */
8
19
  export class ObservableProjectsClient {
9
20
  #client: ObservableSanityClient
@@ -18,37 +29,31 @@ export class ObservableProjectsClient {
18
29
  *
19
30
  * @param options - Options for the list request
20
31
  * - `includeMembers` - Whether to include members in the response (default: true)
32
+ * - `includeFeatures` - Whether to include features in the response (default: true)
21
33
  * - `organizationId` - ID of the organization to fetch projects for
22
- * - `onlyExplicitMembership` - Only include projects where the user has explicit membership (default: false)
34
+ * - `onlyExplicitMembership` - Whether to include only projects with explicit membership (default: false)
23
35
  */
24
- list(options?: {
25
- includeMembers?: true
26
- organizationId?: string
27
- onlyExplicitMembership?: boolean
28
- }): Observable<SanityProject[]>
29
- list(options?: {
30
- includeMembers?: false
31
- organizationId?: string
32
- onlyExplicitMembership?: boolean
33
- }): Observable<Omit<SanityProject, 'members'>[]>
34
- list(options?: {
35
- includeMembers?: boolean
36
- organizationId?: string
37
- onlyExplicitMembership?: boolean
38
- }): Observable<SanityProject[] | Omit<SanityProject, 'members'>[]> {
36
+ list<T extends ListOptions>(
37
+ options?: T,
38
+ ): Observable<Omit<SanityProject, OmittedProjectFields<T>>[]> {
39
39
  const query: Record<string, string> = {}
40
40
  const uri = '/projects'
41
41
  if (options?.includeMembers === false) {
42
42
  query.includeMembers = 'false'
43
43
  }
44
+ if (options?.includeFeatures === false) {
45
+ query.includeFeatures = 'false'
46
+ }
44
47
  if (options?.organizationId) {
45
48
  query.organizationId = options.organizationId
46
49
  }
47
- if (options?.onlyExplicitMembership === true) {
50
+ if (options?.onlyExplicitMembership) {
48
51
  query.onlyExplicitMembership = 'true'
49
52
  }
50
53
 
51
- return _request<SanityProject[]>(this.#client, this.#httpRequest, {uri, query})
54
+ return _request<SanityProject[]>(this.#client, this.#httpRequest, {uri, query}) as Observable<
55
+ Omit<SanityProject, OmittedProjectFields<T>>[]
56
+ >
52
57
  }
53
58
 
54
59
  /**
@@ -75,36 +80,32 @@ export class ProjectsClient {
75
80
  *
76
81
  * @param options - Options for the list request
77
82
  * - `includeMembers` - Whether to include members in the response (default: true)
83
+ * - `includeFeatures` - Whether to include features in the response (default: true)
78
84
  * - `organizationId` - ID of the organization to fetch projects for
79
- * - `onlyExplicitMembership` - Only include projects where the user has explicit membership (default: false)
85
+ * - `onlyExplicitMembership` - Whether to include only projects with explicit membership (default: false)
80
86
  */
81
- list(options?: {
82
- includeMembers?: true
83
- organizationId?: string
84
- onlyExplicitMembership?: boolean
85
- }): Promise<SanityProject[]>
86
- list(options?: {
87
- includeMembers?: false
88
- organizationId?: string
89
- onlyExplicitMembership?: boolean
90
- }): Promise<Omit<SanityProject, 'members'>[]>
91
- list(options?: {
92
- includeMembers?: boolean
93
- organizationId?: string
94
- onlyExplicitMembership?: boolean
95
- }): Promise<SanityProject[] | Omit<SanityProject, 'members'>[]> {
87
+ list<T extends ListOptions>(
88
+ options?: T,
89
+ ): Promise<Omit<SanityProject, OmittedProjectFields<T>>[]> {
96
90
  const query: Record<string, string> = {}
97
91
  const uri = '/projects'
98
92
  if (options?.includeMembers === false) {
99
93
  query.includeMembers = 'false'
100
94
  }
95
+ if (options?.includeFeatures === false) {
96
+ query.includeFeatures = 'false'
97
+ }
101
98
  if (options?.organizationId) {
102
99
  query.organizationId = options.organizationId
103
100
  }
104
- if (options?.onlyExplicitMembership === true) {
101
+ if (options?.onlyExplicitMembership) {
105
102
  query.onlyExplicitMembership = 'true'
106
103
  }
107
- return lastValueFrom(_request<SanityProject[]>(this.#client, this.#httpRequest, {uri, query}))
104
+ return lastValueFrom(
105
+ _request<SanityProject[]>(this.#client, this.#httpRequest, {uri, query}) as Observable<
106
+ Omit<SanityProject, OmittedProjectFields<T>>[]
107
+ >,
108
+ )
108
109
  }
109
110
 
110
111
  /**
package/src/types.ts CHANGED
@@ -2,7 +2,9 @@
2
2
  /* eslint-disable @typescript-eslint/no-empty-object-type */
3
3
 
4
4
  import type {Requester} from 'get-it'
5
+ import type {Observable} from 'rxjs'
5
6
 
7
+ import type {SanityClient} from './SanityClient'
6
8
  import type {InitializedStegaConfig, StegaConfig} from './stega/types'
7
9
 
8
10
  /**
@@ -203,6 +205,33 @@ export interface ClientConfig {
203
205
  * Lineage token for recursion control
204
206
  */
205
207
  lineage?: string
208
+
209
+ /**
210
+ * A custom request handler that intercepts all HTTP requests made by the client.
211
+ *
212
+ * Useful for logging, adding custom headers, refreshing auth tokens, rate limiting, etc.
213
+ *
214
+ * When using `withConfig()`, the new handler **replaces** the previous one (it does not
215
+ * wrap it). To compose handlers, you can chain them manually:
216
+ *
217
+ * ```ts
218
+ * const parent = createClient({...config, _requestHandler: handlerA})
219
+ * const child = parent.withConfig({
220
+ * _requestHandler: (req, defaultRequester) =>
221
+ * handlerB(req, (opts) => handlerA(opts, defaultRequester)),
222
+ * })
223
+ * ```
224
+ *
225
+ * Setting `_requestHandler` to `undefined` via `withConfig()` removes the handler.
226
+ *
227
+ * Note: This only applies to HTTP requests. Real-time listener connections
228
+ * (`client.listen()`) use EventSource and are not intercepted by this handler.
229
+ *
230
+ * @internal
231
+ * @deprecated Don't use outside of Sanity internals
232
+ * @see {@link RequestHandler}
233
+ */
234
+ _requestHandler?: RequestHandler
206
235
  }
207
236
 
208
237
  /** @public */
@@ -412,6 +441,38 @@ export type HttpRequest = {
412
441
  (options: RequestOptions, requester: Requester): ReturnType<Requester>
413
442
  }
414
443
 
444
+ /**
445
+ * A function that intercepts HTTP requests made by the client.
446
+ *
447
+ * Receives the resolved request options, a `defaultRequester` function that
448
+ * executes the request through the normal pipeline, and a `client` instance
449
+ * without a `_requestHandler` (to avoid recursive interception).
450
+ *
451
+ * The consumer can:
452
+ * - Modify request options before calling `defaultRequester`
453
+ * - Transform the response stream (e.g. via `pipe`)
454
+ * - Skip `defaultRequester` entirely and return a custom Observable
455
+ * - Use `client` to make additional requests (e.g. refresh an auth token on 401)
456
+ *
457
+ * When set via `withConfig()`, the new handler **replaces** (not wraps) the previous one.
458
+ *
459
+ * Note: This only applies to HTTP requests. Real-time listener connections
460
+ * (`client.listen()`) use EventSource and are not intercepted by this handler.
461
+ *
462
+ * @param request - The resolved request options including `url`
463
+ * @param defaultRequester - Executes the request through the normal pipeline
464
+ * @param client - A client instance with the same configuration but without a `_requestHandler`,
465
+ * useful for making side requests (e.g. token refresh) without triggering the handler recursively
466
+ *
467
+ * @internal
468
+ * @deprecated Don't use outside of Sanity internals
469
+ */
470
+ export type RequestHandler = (
471
+ request: RequestOptions & {url: string},
472
+ defaultRequester: (options: RequestOptions & {url: string}) => Observable<HttpRequestEvent>,
473
+ client: SanityClient,
474
+ ) => Observable<HttpRequestEvent>
475
+
415
476
  /** @internal */
416
477
  export interface RequestObservableOptions extends Omit<RequestOptions, 'url'> {
417
478
  url?: string
@@ -529,6 +590,7 @@ export interface SanityProject {
529
590
  pendingInvites?: number
530
591
  maxRetentionDays?: number
531
592
  members: SanityProjectMember[]
593
+ features: string[]
532
594
  metadata: {
533
595
  cliInitializedAt?: string
534
596
  color?: string