@sanity/client 8.1.0 → 8.3.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.
@@ -2,7 +2,7 @@ import {lastValueFrom, type Observable} from 'rxjs'
2
2
 
3
3
  import type {ObservableSanityClient, SanityClient} from '../SanityClient'
4
4
  import type {HttpRequest} from '../types'
5
- import {_invoke, type InvokeFunctionRequest} from './invoke'
5
+ import {_invoke, type InvokeFunctionOptions, type InvokeFunctionRequest} from './invoke'
6
6
 
7
7
  /** @public */
8
8
  export class ObservableFunctionsClient {
@@ -17,16 +17,36 @@ export class ObservableFunctionsClient {
17
17
  * Invoke a deployed function by its blueprint name.
18
18
  *
19
19
  * The name is resolved within the stack given by `stackId` on the request or
20
- * the client config. Passes the function's return value once it finishes.
20
+ * the client config. Starts the invocation and emits `undefined` as soon as
21
+ * it is accepted; pass `{sync: true}` to wait for the function's return value
22
+ * instead.
21
23
  *
22
24
  * @param functionName - name of the function, as declared in the blueprint
23
25
  * @param request - payload and request options
26
+ * @param options - invocation options
24
27
  */
28
+ invoke(
29
+ functionName: string,
30
+ request?: InvokeFunctionRequest,
31
+ options?: InvokeFunctionOptions & {sync?: false},
32
+ ): Observable<undefined>
33
+ invoke<R = unknown>(
34
+ functionName: string,
35
+ request: InvokeFunctionRequest | undefined,
36
+ options: InvokeFunctionOptions & {sync: true},
37
+ ): Observable<R>
38
+ invoke<R = unknown>(
39
+ functionName: string,
40
+ request?: InvokeFunctionRequest,
41
+ options?: InvokeFunctionOptions,
42
+ ): Observable<R | undefined>
43
+ // Implementation signature — not part of the public API.
25
44
  invoke<R = unknown>(
26
45
  functionName: string,
27
46
  request?: InvokeFunctionRequest,
47
+ options?: InvokeFunctionOptions,
28
48
  ): Observable<R | undefined> {
29
- return _invoke<R>(this.#client, this.#httpRequest, functionName, request)
49
+ return _invoke<R>(this.#client, this.#httpRequest, functionName, request, options)
30
50
  }
31
51
  }
32
52
 
@@ -44,23 +64,46 @@ export class FunctionsClient {
44
64
  *
45
65
  * The name is resolved within the stack given by `stackId` on the request or
46
66
  * the client config, which costs one extra request per call. Rejects if the
47
- * stack has no function by that name, or if the name resolves to anything
48
- * other than a `sanity.function.pubsub` function.
67
+ * stack has no function by that name, or if the name resolves to a function
68
+ * type that cannot be invoked the way it was asked for.
49
69
  *
50
70
  * The lookup is scoped to `projectId`, or to `organizationId` when one is set
51
71
  * for a stack deployed at organization scope.
52
72
  *
53
- * The request stays open until the function finishes, and resolves with its
54
- * return value, or `undefined` if it returns nothing. Long-running functions
55
- * may need an explicit `timeout`.
73
+ * The invocation is started by default: the promise resolves with `undefined`
74
+ * as soon as the call is accepted, without waiting for the function to run.
75
+ * Pass `{sync: true}` to keep the request open until the function finishes
76
+ * and resolve with its return value — long-running functions may then need an
77
+ * explicit `timeout`. Only `sanity.function.pubsub` functions can be invoked
78
+ * synchronously.
56
79
  *
57
80
  * @param functionName - name of the function, as declared in the blueprint
58
81
  * @param request - payload and request options
82
+ * @param options - invocation options
59
83
  */
84
+ invoke(
85
+ functionName: string,
86
+ request?: InvokeFunctionRequest,
87
+ options?: InvokeFunctionOptions & {sync?: false},
88
+ ): Promise<undefined>
89
+ invoke<R = unknown>(
90
+ functionName: string,
91
+ request: InvokeFunctionRequest | undefined,
92
+ options: InvokeFunctionOptions & {sync: true},
93
+ ): Promise<R>
94
+ invoke<R = unknown>(
95
+ functionName: string,
96
+ request?: InvokeFunctionRequest,
97
+ options?: InvokeFunctionOptions,
98
+ ): Promise<R | undefined>
99
+ // Implementation signature — not part of the public API.
60
100
  invoke<R = unknown>(
61
101
  functionName: string,
62
102
  request?: InvokeFunctionRequest,
103
+ options?: InvokeFunctionOptions,
63
104
  ): Promise<R | undefined> {
64
- return lastValueFrom(_invoke<R>(this.#client, this.#httpRequest, functionName, request))
105
+ return lastValueFrom(
106
+ _invoke<R>(this.#client, this.#httpRequest, functionName, request, options),
107
+ )
65
108
  }
66
109
  }
@@ -6,7 +6,15 @@ import type {HttpRequest, InitializedClientConfig} from '../types'
6
6
 
7
7
  /** Function resource types in a blueprint are namespaced under this prefix. */
8
8
  const FUNCTION_RESOURCE_PREFIX = 'sanity.function.'
9
- const INVOKABLE_FUNCTION_TYPE = 'sanity.function.pubsub'
9
+ const SYNC_INVOCABLE_FUNCTION_TYPES = ['sanity.function.pubsub']
10
+ const ASYNC_INVOCABLE_FUNCTION_TYPES = [
11
+ 'sanity.function.durable',
12
+ 'sanity.function.pubsub',
13
+ 'sanity.function.queue',
14
+ ]
15
+ const INVOCABLE_FUNCTION_TYPES = [
16
+ ...new Set([...SYNC_INVOCABLE_FUNCTION_TYPES, ...ASYNC_INVOCABLE_FUNCTION_TYPES]),
17
+ ]
10
18
 
11
19
  /** @public */
12
20
  export interface InvokeFunctionEvent {
@@ -37,6 +45,18 @@ export interface InvokeFunctionRequest {
37
45
  signal?: AbortSignal
38
46
  }
39
47
 
48
+ /** @public */
49
+ export interface InvokeFunctionOptions {
50
+ /**
51
+ * Wait for the function to finish and resolve with its return value.
52
+ *
53
+ * Defaults to `false`: the invocation is started, the request resolves as soon
54
+ * as it is accepted, and the value is always `undefined`. Only function types
55
+ * that support running inline can be invoked synchronously.
56
+ */
57
+ sync?: boolean
58
+ }
59
+
40
60
  /**
41
61
  * Subset of a stack resource the client needs in order to resolve a name.
42
62
  *
@@ -109,6 +129,7 @@ function _resolveFunctionId(
109
129
  stackId: string,
110
130
  headers: Record<string, string>,
111
131
  request: InvokeFunctionRequest | undefined,
132
+ sync: boolean,
112
133
  ): Observable<string> {
113
134
  return _requestObservable<{resources?: StackResource[]}>(client, httpRequest, {
114
135
  method: 'GET',
@@ -132,9 +153,15 @@ function _resolveFunctionId(
132
153
  )
133
154
  }
134
155
 
135
- if (match.type !== INVOKABLE_FUNCTION_TYPE) {
156
+ if (!INVOCABLE_FUNCTION_TYPES.includes(match.type)) {
136
157
  throw new Error(`Function invocation is not supported for ${match.type}`)
137
158
  }
159
+ if (sync && !SYNC_INVOCABLE_FUNCTION_TYPES.includes(match.type)) {
160
+ throw new Error(`Synchronous function invocation is not supported for ${match.type}`)
161
+ }
162
+ if (!sync && !ASYNC_INVOCABLE_FUNCTION_TYPES.includes(match.type)) {
163
+ throw new Error(`Asynchronous function invocation is not supported for ${match.type}`)
164
+ }
138
165
 
139
166
  return match.externalId
140
167
  }),
@@ -147,6 +174,7 @@ export function _invoke<R = unknown>(
147
174
  httpRequest: HttpRequest,
148
175
  functionName: string,
149
176
  request?: InvokeFunctionRequest,
177
+ options?: InvokeFunctionOptions,
150
178
  ): Observable<R | undefined> {
151
179
  // Deferred so a bad config surfaces as an error on the returned observable
152
180
  // (and so a rejected promise) rather than throwing at the call site.
@@ -154,17 +182,30 @@ export function _invoke<R = unknown>(
154
182
  const config = client.config()
155
183
  const headers = scopeHeaders(config, request)
156
184
  const stackId = resolveStackId(config, request)
157
-
158
- return _resolveFunctionId(client, httpRequest, functionName, stackId, headers, request).pipe(
159
- // A function that returns nothing answers 204, which the transport parses
185
+ const sync = options?.sync ?? false
186
+
187
+ return _resolveFunctionId(
188
+ client,
189
+ httpRequest,
190
+ functionName,
191
+ stackId,
192
+ headers,
193
+ request,
194
+ sync,
195
+ ).pipe(
196
+ // An async invocation is only acknowledged (202, no body), and a sync
197
+ // function that returns nothing answers 204, which the transport parses
160
198
  // to an `undefined` body. The status code is not observable from here —
161
199
  // the `HttpRequest` boundary resolves to the body alone — so an empty
162
200
  // response and a function that returned nothing both surface as
163
201
  // `undefined`.
202
+ //
203
+ // The route is async by default, so `sync=false` is left off the wire
204
+ // rather than spelled out.
164
205
  mergeMap((functionId) =>
165
206
  _requestObservable<R | undefined>(client, httpRequest, {
166
207
  method: 'POST',
167
- url: `/functions/${functionId}/invoke`,
208
+ url: `/functions/${functionId}/invoke${sync ? '?sync=true' : ''}`,
168
209
  headers,
169
210
  body: {event: {data: request?.event?.data ?? {}}},
170
211
  timeout: request?.timeout,
package/src/types.ts CHANGED
@@ -304,6 +304,16 @@ export interface ClientConfig {
304
304
  * ID of the organization owning the blueprints stack
305
305
  */
306
306
  organizationId?: string
307
+ /**
308
+ * Organization-scoped configuration for collaboration APIs.
309
+ *
310
+ * Currently this is used by `collaboration.comments` methods.
311
+ *
312
+ * @alpha
313
+ */
314
+ collaboration?: {
315
+ organizationId?: string
316
+ }
307
317
  }
308
318
 
309
319
  /** @public */
@@ -840,6 +850,17 @@ export type VersionAction =
840
850
  | ReplaceVersionAction
841
851
  | UnpublishVersionAction
842
852
 
853
+ /**
854
+ * @public
855
+ * @beta
856
+ */
857
+ export type VariantAction =
858
+ | CreateVariantAction
859
+ | EditVariantAction
860
+ | DeleteVariantAction
861
+ | PublishVariantAction
862
+ | UnpublishVariantAction
863
+
843
864
  /** @public */
844
865
  export type Action =
845
866
  | CreateAction
@@ -850,6 +871,7 @@ export type Action =
850
871
  | PublishAction
851
872
  | UnpublishAction
852
873
  | VersionAction
874
+ | VariantAction
853
875
  | ReleaseAction
854
876
  | VariantDefinitionAction
855
877
 
@@ -1009,6 +1031,203 @@ export interface UnpublishVersionAction {
1009
1031
  publishedId: string
1010
1032
  }
1011
1033
 
1034
+ /**
1035
+ * Creates a variant of a document, either by supplying the full document
1036
+ * content, or the base ID of a document to copy.
1037
+ *
1038
+ * @public
1039
+ * @beta
1040
+ */
1041
+ export type CreateVariantAction = {
1042
+ actionType: 'sanity.action.document.variant.create'
1043
+
1044
+ /**
1045
+ * ID of the document group to create a variant in. Must be a published
1046
+ * document ID, without a `drafts.` or `versions.` prefix.
1047
+ */
1048
+ publishedId: string
1049
+
1050
+ /**
1051
+ * Name of the variant definition this document belongs to, as in
1052
+ * `_.variants.{variantName}`. Must be a bare name, not a full document ID.
1053
+ */
1054
+ variantId: string
1055
+
1056
+ /**
1057
+ * Source bundle: `'drafts'`, or a release id.
1058
+ *
1059
+ * Defaults to the published bundle.
1060
+ */
1061
+ bundleId?: 'drafts' | (string & {})
1062
+ } & (
1063
+ | {
1064
+ /**
1065
+ * The full document content. Requires a `_type` property.
1066
+ */
1067
+ document: SanityDocumentStub
1068
+ baseId?: never
1069
+ ifBaseRevisionId?: never
1070
+ }
1071
+ | {
1072
+ /**
1073
+ * ID of an existing document to copy the content from.
1074
+ */
1075
+ baseId: string
1076
+
1077
+ /**
1078
+ * When set, the action fails unless the current revision of the base
1079
+ * document matches this value.
1080
+ */
1081
+ ifBaseRevisionId?: string
1082
+ document?: never
1083
+ }
1084
+ )
1085
+
1086
+ /**
1087
+ * Modifies a variant version of a document by applying a patch.
1088
+ *
1089
+ * If no such variant document exists it is first created, by copying the
1090
+ * variant's published sibling, or the published document if the variant was
1091
+ * never published.
1092
+ *
1093
+ * @public
1094
+ * @beta
1095
+ */
1096
+ export interface EditVariantAction {
1097
+ actionType: 'sanity.action.document.variant.edit'
1098
+
1099
+ /**
1100
+ * ID of the document group the variant belongs to. Must be a published
1101
+ * document ID, without a `drafts.` or `versions.` prefix.
1102
+ */
1103
+ publishedId: string
1104
+
1105
+ /**
1106
+ * Name of the variant definition this document belongs to, as in
1107
+ * `_.variants.{variantName}`. Must be a bare name, not a full document ID.
1108
+ */
1109
+ variantId: string
1110
+
1111
+ /**
1112
+ * Source bundle: `'drafts'`, or a release id.
1113
+ *
1114
+ * Defaults to the published bundle.
1115
+ */
1116
+ bundleId?: 'drafts' | (string & {})
1117
+
1118
+ /**
1119
+ * Patch operations to apply.
1120
+ */
1121
+ patch: PatchOperations
1122
+ }
1123
+
1124
+ /**
1125
+ * Deletes a variant of a document.
1126
+ *
1127
+ * @public
1128
+ * @beta
1129
+ */
1130
+ export interface DeleteVariantAction {
1131
+ actionType: 'sanity.action.document.variant.delete'
1132
+
1133
+ /**
1134
+ * ID of the document group the variant belongs to. Must be a published
1135
+ * document ID, without a `drafts.` or `versions.` prefix.
1136
+ */
1137
+ publishedId: string
1138
+
1139
+ /**
1140
+ * Name of the variant definition this document belongs to, as in
1141
+ * `_.variants.{variantName}`. Must be a bare name, not a full document ID.
1142
+ */
1143
+ variantId: string
1144
+
1145
+ /**
1146
+ * Source bundle: `'drafts'`, or a release id.
1147
+ *
1148
+ * Defaults to the published bundle.
1149
+ */
1150
+ bundleId?: 'drafts' | (string & {})
1151
+
1152
+ /**
1153
+ * Delete document history.
1154
+ */
1155
+ purge?: boolean
1156
+ }
1157
+
1158
+ /**
1159
+ * Publishes a variant version of a document, replacing the published variant
1160
+ * and removing the source variant document.
1161
+ *
1162
+ * @public
1163
+ * @beta
1164
+ */
1165
+ export interface PublishVariantAction {
1166
+ actionType: 'sanity.action.document.variant.publish'
1167
+
1168
+ /**
1169
+ * ID of the document group the variant belongs to. Must be a published
1170
+ * document ID, without a `drafts.` or `versions.` prefix.
1171
+ */
1172
+ publishedId: string
1173
+
1174
+ /**
1175
+ * Name of the variant definition this document belongs to, as in
1176
+ * `_.variants.{variantName}`. Must be a bare name, not a full document ID.
1177
+ */
1178
+ variantId: string
1179
+
1180
+ /**
1181
+ * Bundle to publish from: `'drafts'`, or a release id.
1182
+ */
1183
+ bundleId: 'drafts' | (string & {})
1184
+
1185
+ /**
1186
+ * When set, publishing fails unless the current revision of the source
1187
+ * variant document matches this value.
1188
+ */
1189
+ ifVersionRevisionId?: string
1190
+
1191
+ /**
1192
+ * When set, publishing fails unless the current revision of the published
1193
+ * variant document matches this value.
1194
+ */
1195
+ ifPublishedVariantRevisionId?: string
1196
+ }
1197
+
1198
+ /**
1199
+ * Unpublishes a variant version of a document.
1200
+ *
1201
+ * By default the published variant is removed and preserved as a draft
1202
+ * variant. When a release id is given as the `bundleId`, the deletion is
1203
+ * instead staged in that release, and takes effect when it is published.
1204
+ *
1205
+ * @public
1206
+ * @beta
1207
+ */
1208
+ export interface UnpublishVariantAction {
1209
+ actionType: 'sanity.action.document.variant.unpublish'
1210
+
1211
+ /**
1212
+ * ID of the document group the variant belongs to. Must be a published
1213
+ * document ID, without a `drafts.` or `versions.` prefix.
1214
+ */
1215
+ publishedId: string
1216
+
1217
+ /**
1218
+ * Name of the variant definition this document belongs to, as in
1219
+ * `_.variants.{variantName}`. Must be a bare name, not a full document ID.
1220
+ */
1221
+ variantId: string
1222
+
1223
+ /**
1224
+ * The content release in which to stage the unpublish.
1225
+ *
1226
+ * By default, the currently published document is unpublished immediately.
1227
+ */
1228
+ bundleId?: string
1229
+ }
1230
+
1012
1231
  /**
1013
1232
  * Creates a new `system.variant` definition document.
1014
1233
  *
@@ -2078,7 +2297,27 @@ export type {
2078
2297
  TranslateTarget,
2079
2298
  TranslateTargetInclude,
2080
2299
  } from './agent/actions/translate'
2081
- export type {InvokeFunctionEvent, InvokeFunctionRequest} from './functions/invoke'
2300
+ export type {
2301
+ InvokeFunctionEvent,
2302
+ InvokeFunctionOptions,
2303
+ InvokeFunctionRequest,
2304
+ } from './functions/invoke'
2305
+ export type {
2306
+ CollaborationCommentCreate,
2307
+ CollaborationCommentDocument,
2308
+ CollaborationCommentMessage,
2309
+ CollaborationCommentPortableTextBlock,
2310
+ CollaborationCommentReactionShortName,
2311
+ CollaborationCommentSelection,
2312
+ CollaborationCommentsListenOptions,
2313
+ CollaborationCommentsRequestOptions,
2314
+ CollaborationCommentStatus,
2315
+ CollaborationCommentsWriteOptions,
2316
+ CollaborationCommentTarget,
2317
+ CollaborationCommentUpdate,
2318
+ CollaborationCommentRange,
2319
+ CollaborationCommentFieldValue,
2320
+ } from './collaboration/types'
2082
2321
  export type {
2083
2322
  ContentSourceMapParsedPath,
2084
2323
  ContentSourceMapParsedPathKeyedSegment,
@@ -5,20 +5,30 @@ import {
5
5
  isDraftId,
6
6
  isVersionId,
7
7
  } from '@sanity/client/csm'
8
- import {customAlphabet} from 'nanoid'
9
8
 
10
9
  import type {IdentifiedSanityDocumentStub, SanityDocumentStub} from '../types'
11
10
  import {validateVersionIdMatch} from '../validators'
12
11
 
12
+ const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
13
+
13
14
  /**
14
15
  * @internal
15
16
  *
16
17
  * ~24 years (or 7.54e+8 seconds) needed, in order to have a 1% probability of at least one collision if 10 ID's are generated every hour.
17
18
  */
18
- export const generateReleaseId = customAlphabet(
19
- 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
20
- 8,
21
- )
19
+ export function generateReleaseId() {
20
+ let id = ''
21
+
22
+ while (id.length < 8) {
23
+ const bytes = crypto.getRandomValues(new Uint8Array(8 - id.length))
24
+ for (const byte of bytes) {
25
+ const index = byte & 63
26
+ if (index < alphabet.length) id += alphabet[index]
27
+ }
28
+ }
29
+
30
+ return id
31
+ }
22
32
 
23
33
  /** @internal */
24
34
  export const getDocumentVersionId = (publishedId: string, releaseId?: string) =>