@sanity/client 7.23.2 → 7.25.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/README.md +42 -1
- package/dist/_chunks-cjs/stegaClean.cjs.map +1 -1
- package/dist/_chunks-es/stegaClean.js.map +1 -1
- package/dist/index.browser.cjs +26 -1
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.d.cts +26 -0
- package/dist/index.browser.d.ts +26 -0
- package/dist/index.browser.js +26 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +27 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -1
- package/dist/stega.browser.cjs +4 -0
- package/dist/stega.browser.cjs.map +1 -1
- package/dist/stega.browser.d.cts +176 -1
- package/dist/stega.browser.d.ts +176 -1
- package/dist/stega.browser.js +4 -0
- package/dist/stega.browser.js.map +1 -1
- package/dist/stega.cjs +4 -0
- package/dist/stega.cjs.map +1 -1
- package/dist/stega.d.cts +176 -1
- package/dist/stega.d.ts +176 -1
- package/dist/stega.js +4 -0
- package/dist/stega.js.map +1 -1
- package/package.json +2 -1
- package/src/data/dataMethods.ts +40 -0
- package/src/stega/index.ts +2 -1
- package/src/stega/stegaBrand.ts +111 -0
- package/src/stega/stegaClean.ts +23 -2
- package/src/types.ts +26 -0
- package/umd/sanityClient.js +26 -1
- package/umd/sanityClient.min.js +2 -2
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {Any, ClientReturn} from '@sanity/client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A string that might contain stega-encoded data, and is unsafe to compare against string literals
|
|
5
|
+
* until it's been cleaned with `stegaClean()`.
|
|
6
|
+
*
|
|
7
|
+
* The type intersects a template literal that has a human readable suffix, with a brand property
|
|
8
|
+
* that holds the original string type:
|
|
9
|
+
* - The suffix makes TypeScript report "This comparison appears to be unintentional" (TS2367) when
|
|
10
|
+
* the string is compared to a literal (`===`, `switch` cases), and makes it unassignable to
|
|
11
|
+
* literal unions like `'left' | 'right'`, while keeping it assignable to `string` so rendering,
|
|
12
|
+
* interpolation and string methods keep working. The suffix is a type-level fiction, the runtime
|
|
13
|
+
* value never ends with that text.
|
|
14
|
+
* - The brand property preserves the original type so `stegaClean()` can recover it exactly. It's
|
|
15
|
+
* a string-keyed property rather than a `unique symbol` so that branded types stay structurally
|
|
16
|
+
* compatible across duplicated copies of `@sanity/client` in `node_modules`. It only exists in
|
|
17
|
+
* the type system and is never present at runtime.
|
|
18
|
+
*
|
|
19
|
+
* @beta
|
|
20
|
+
*/
|
|
21
|
+
export type StegaString<T extends string = string> =
|
|
22
|
+
`${T} (may contain hidden stega characters)` & {
|
|
23
|
+
readonly ' stegaBrand': T
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Deeply brands the string properties of a query result as `StegaString`, marking them as
|
|
28
|
+
* potentially containing stega-encoded data.
|
|
29
|
+
*
|
|
30
|
+
* String properties that the stega encoder is guaranteed to never encode keep their plain type:
|
|
31
|
+
* - keys starting with `_` (`_id`, `_type`, `_createdAt`, `_updatedAt`, `_key`, `_ref`, `_rev` and
|
|
32
|
+
* so on), which also preserves discriminated union narrowing on `_type`
|
|
33
|
+
* - `slug.current` patterns: a `current` key directly under a `slug` key, as well as string valued
|
|
34
|
+
* `slug` keys (covering `"slug": slug.current` projections)
|
|
35
|
+
* - Portable Text internals: the encoder only visits `children` of `_type: 'block'` objects and
|
|
36
|
+
* `text` of `_type: 'span'` objects, so properties like `style`, `listItem` and `marks` stay
|
|
37
|
+
* plain
|
|
38
|
+
*
|
|
39
|
+
* Every other string is assumed to be "poisoned", even if the runtime `filter` happens to skip it
|
|
40
|
+
* (URLs, dates, keys ending in `Id`, denylisted keys). Being conservative is safe: the worst case
|
|
41
|
+
* is a redundant `stegaClean()` call, whereas under-branding would hide real bugs.
|
|
42
|
+
*
|
|
43
|
+
* The `Key` and `ParentKey` type parameters track the key the current value is found under, and
|
|
44
|
+
* the key of the object containing it, they're only used internally during recursion.
|
|
45
|
+
*
|
|
46
|
+
* @beta
|
|
47
|
+
*/
|
|
48
|
+
export type StegaBranded<
|
|
49
|
+
T,
|
|
50
|
+
Key extends PropertyKey = number,
|
|
51
|
+
ParentKey extends PropertyKey = number,
|
|
52
|
+
> = 0 extends 1 & T
|
|
53
|
+
? T
|
|
54
|
+
: T extends string
|
|
55
|
+
? T extends {readonly ' stegaBrand': string}
|
|
56
|
+
? T
|
|
57
|
+
: Key extends `_${string}` | 'slug'
|
|
58
|
+
? T
|
|
59
|
+
: Key extends 'current'
|
|
60
|
+
? ParentKey extends 'slug'
|
|
61
|
+
? T
|
|
62
|
+
: StegaString<T>
|
|
63
|
+
: StegaString<T>
|
|
64
|
+
: T extends number | bigint | boolean | null | undefined
|
|
65
|
+
? T
|
|
66
|
+
: T extends Date | RegExp | ((...args: never[]) => unknown)
|
|
67
|
+
? T
|
|
68
|
+
: T extends readonly unknown[]
|
|
69
|
+
? {[Index in keyof T]: StegaBranded<T[Index], number, number>}
|
|
70
|
+
: T extends {_type: 'block'}
|
|
71
|
+
? {[K in keyof T]: K extends 'children' ? StegaBranded<T[K], K, Key> : T[K]}
|
|
72
|
+
: T extends {_type: 'span'}
|
|
73
|
+
? {[K in keyof T]: K extends 'text' ? StegaBranded<T[K], K, Key> : T[K]}
|
|
74
|
+
: T extends object
|
|
75
|
+
? {[K in keyof T]: StegaBranded<T[K], K, Key>}
|
|
76
|
+
: T
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Drop-in replacement for `ClientReturn` that brands the result strings as `StegaString`, for use
|
|
80
|
+
* as the first generic of `client.fetch` when stega is enabled:
|
|
81
|
+
* ```ts
|
|
82
|
+
* import {createClient} from '@sanity/client'
|
|
83
|
+
* import type {ClientReturnStega} from '@sanity/client/stega'
|
|
84
|
+
*
|
|
85
|
+
* const query = '*[_type == "post"][0]'
|
|
86
|
+
* const post = await client.fetch<ClientReturnStega<typeof query>>(query)
|
|
87
|
+
* ```
|
|
88
|
+
* When the query is registered in the `SanityQueries` interface (for example by `sanity typegen`),
|
|
89
|
+
* the result type is looked up and deeply branded with `StegaBranded`. Otherwise it falls back to
|
|
90
|
+
* `Fallback`, which defaults to `any`, just like `ClientReturn`.
|
|
91
|
+
*
|
|
92
|
+
* @beta
|
|
93
|
+
*/
|
|
94
|
+
export type ClientReturnStega<GroqString extends string, Fallback = Any> = StegaBranded<
|
|
95
|
+
ClientReturn<GroqString, Fallback>
|
|
96
|
+
>
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Marks strings in an already fetched query result as potentially containing stega-encoded data,
|
|
100
|
+
* by re-typing them as `StegaString`. Comparing branded strings to string literals is a type error
|
|
101
|
+
* until they're cleaned with `stegaClean()`, which recovers the original type.
|
|
102
|
+
*
|
|
103
|
+
* This is an identity function, it only changes the type of the input, not its value.
|
|
104
|
+
* Prefer passing `ClientReturnStega` as the first generic to `client.fetch` when possible, and use
|
|
105
|
+
* this function for data that has already been fetched.
|
|
106
|
+
*
|
|
107
|
+
* @beta
|
|
108
|
+
*/
|
|
109
|
+
export function stegaBrand<Result>(result: Result): StegaBranded<Result> {
|
|
110
|
+
return result as StegaBranded<Result>
|
|
111
|
+
}
|
package/src/stega/stegaClean.ts
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import {vercelStegaClean} from '@vercel/stega'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The result of removing stega-encoded data from a value with `stegaClean()`: strings that were
|
|
5
|
+
* branded as `StegaString` are returned to their original type, everything else is left as-is.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export type StegaCleaned<T> = 0 extends 1 & T
|
|
9
|
+
? T
|
|
10
|
+
: T extends {readonly ' stegaBrand': infer Original extends string}
|
|
11
|
+
? Original
|
|
12
|
+
: T extends string | number | bigint | boolean | null | undefined
|
|
13
|
+
? T
|
|
14
|
+
: T extends Date | RegExp | ((...args: never[]) => unknown)
|
|
15
|
+
? T
|
|
16
|
+
: T extends readonly unknown[]
|
|
17
|
+
? {[Index in keyof T]: StegaCleaned<T[Index]>}
|
|
18
|
+
: T extends object
|
|
19
|
+
? {[K in keyof T]: StegaCleaned<T[K]>}
|
|
20
|
+
: T
|
|
21
|
+
|
|
3
22
|
/**
|
|
4
23
|
* Can take a `result` JSON from a `const {result} = client.fetch(query, params, {filterResponse: false})`
|
|
5
24
|
* and remove all stega-encoded data from it.
|
|
25
|
+
* If the result type has strings branded as `StegaString` (by `ClientReturnStega` or `stegaBrand()`),
|
|
26
|
+
* the brand is stripped and the original string type is restored.
|
|
6
27
|
* @public
|
|
7
28
|
*/
|
|
8
|
-
export function stegaClean<Result = unknown>(result: Result): Result {
|
|
9
|
-
return vercelStegaClean<Result>
|
|
29
|
+
export function stegaClean<Result = unknown>(result: Result): StegaCleaned<Result> {
|
|
30
|
+
return vercelStegaClean(result) as StegaCleaned<Result>
|
|
10
31
|
}
|
|
11
32
|
|
|
12
33
|
/**
|
package/src/types.ts
CHANGED
|
@@ -55,6 +55,18 @@ export type ClientPerspective =
|
|
|
55
55
|
| 'raw'
|
|
56
56
|
| StackablePerspective[]
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @public
|
|
60
|
+
* @beta
|
|
61
|
+
*/
|
|
62
|
+
export type ClientVariantConditions = Record<string, string>
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @public
|
|
66
|
+
* @beta
|
|
67
|
+
*/
|
|
68
|
+
export type ClientVariant = ClientVariantConditions | string
|
|
69
|
+
|
|
58
70
|
type ClientConfigResource =
|
|
59
71
|
| {
|
|
60
72
|
type: 'canvas'
|
|
@@ -111,6 +123,10 @@ export interface ClientConfig {
|
|
|
111
123
|
* @defaultValue 'published'
|
|
112
124
|
*/
|
|
113
125
|
perspective?: ClientPerspective
|
|
126
|
+
/**
|
|
127
|
+
* @beta
|
|
128
|
+
*/
|
|
129
|
+
variant?: ClientVariant
|
|
114
130
|
apiHost?: string
|
|
115
131
|
|
|
116
132
|
/**
|
|
@@ -484,6 +500,10 @@ export interface RequestObservableOptions extends Omit<RequestOptions, 'url'> {
|
|
|
484
500
|
returnQuery?: boolean
|
|
485
501
|
resultSourceMap?: boolean | 'withKeyArraySelector'
|
|
486
502
|
perspective?: ClientPerspective
|
|
503
|
+
/**
|
|
504
|
+
* @beta
|
|
505
|
+
*/
|
|
506
|
+
variant?: ClientVariant
|
|
487
507
|
lastLiveEventId?: string
|
|
488
508
|
cacheMode?: 'noStale'
|
|
489
509
|
}
|
|
@@ -679,6 +699,8 @@ export interface QueryParams {
|
|
|
679
699
|
/** @deprecated you're using a fetch option as a GROQ parameter, this is likely a mistake */
|
|
680
700
|
perspective?: never
|
|
681
701
|
/** @deprecated you're using a fetch option as a GROQ parameter, this is likely a mistake */
|
|
702
|
+
variant?: never
|
|
703
|
+
/** @deprecated you're using a fetch option as a GROQ parameter, this is likely a mistake */
|
|
682
704
|
query?: never
|
|
683
705
|
/** @deprecated you're using a fetch option as a GROQ parameter, this is likely a mistake */
|
|
684
706
|
resultSourceMap?: never
|
|
@@ -1424,6 +1446,10 @@ export interface ResumableListenOptions extends Omit<ListenOptions, 'events' | '
|
|
|
1424
1446
|
/** @public */
|
|
1425
1447
|
export interface ResponseQueryOptions extends RequestOptions {
|
|
1426
1448
|
perspective?: ClientPerspective
|
|
1449
|
+
/**
|
|
1450
|
+
* @beta
|
|
1451
|
+
*/
|
|
1452
|
+
variant?: ClientVariant
|
|
1427
1453
|
resultSourceMap?: boolean | 'withKeyArraySelector'
|
|
1428
1454
|
returnQuery?: boolean
|
|
1429
1455
|
useCdn?: boolean
|
package/umd/sanityClient.js
CHANGED
|
@@ -3096,6 +3096,7 @@ ${selectionOpts}`);
|
|
|
3096
3096
|
tag,
|
|
3097
3097
|
returnQuery,
|
|
3098
3098
|
perspective: options.perspective,
|
|
3099
|
+
variant: options.variant,
|
|
3099
3100
|
resultSourceMap: options.resultSourceMap,
|
|
3100
3101
|
lastLiveEventId: Array.isArray(lastLiveEventId) ? lastLiveEventId[0] : lastLiveEventId,
|
|
3101
3102
|
cacheMode,
|
|
@@ -3143,7 +3144,25 @@ ${selectionOpts}`);
|
|
|
3143
3144
|
perspective: Array.isArray(perspectiveOption) ? perspectiveOption.join(",") : perspectiveOption,
|
|
3144
3145
|
...options.query
|
|
3145
3146
|
}, (Array.isArray(perspectiveOption) && perspectiveOption.length > 0 || // previewDrafts was renamed to drafts, but keep for backwards compat
|
|
3146
|
-
perspectiveOption === "previewDrafts" || perspectiveOption === "drafts") && useCdn && (useCdn = false, printCdnPreviewDraftsWarning()))
|
|
3147
|
+
perspectiveOption === "previewDrafts" || perspectiveOption === "drafts") && useCdn && (useCdn = false, printCdnPreviewDraftsWarning()));
|
|
3148
|
+
const variantOption = options.variant || config.variant;
|
|
3149
|
+
if (typeof variantOption < "u" && (typeof variantOption == "string" && (options.query = {
|
|
3150
|
+
variant: variantOption,
|
|
3151
|
+
...options.query
|
|
3152
|
+
}), typeof variantOption == "object")) {
|
|
3153
|
+
const variantConditions = Object.entries(variantOption), searchParams = variantConditionPairsToSearchParams(variantConditions).slice(0, 1);
|
|
3154
|
+
if (variantConditions.length > 1) {
|
|
3155
|
+
const formatter = new Intl.ListFormat("en");
|
|
3156
|
+
console.warn(
|
|
3157
|
+
`The Sanity client's beta \`variant\` option currently only supports one condition. Dropped: ${formatter.format(variantConditions.slice(1).map(([subject]) => JSON.stringify(subject)))}.`
|
|
3158
|
+
);
|
|
3159
|
+
}
|
|
3160
|
+
options.query = {
|
|
3161
|
+
...Object.fromEntries(searchParams),
|
|
3162
|
+
...options.query
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
options.lastLiveEventId && (options.query = { ...options.query, lastLiveEventId: options.lastLiveEventId }), options.returnQuery === false && (options.query = { returnQuery: "false", ...options.query }), useCdn && options.cacheMode == "noStale" && (options.query = { cacheMode: "noStale", ...options.query });
|
|
3147
3166
|
}
|
|
3148
3167
|
const reqOptions = requestOptions(
|
|
3149
3168
|
config,
|
|
@@ -3218,6 +3237,12 @@ ${selectionOpts}`);
|
|
|
3218
3237
|
throw new Error(`Unsupported resource type: ${type.toString()}`);
|
|
3219
3238
|
}
|
|
3220
3239
|
};
|
|
3240
|
+
function variantConditionPairsToSearchParams(variantConditionPairs) {
|
|
3241
|
+
return variantConditionPairs.map(([condition, value]) => [
|
|
3242
|
+
"variantCondition",
|
|
3243
|
+
`${condition}:${value}`
|
|
3244
|
+
]);
|
|
3245
|
+
}
|
|
3221
3246
|
function _generate(client, httpRequest, request) {
|
|
3222
3247
|
const dataset2 = hasDataset(client.config());
|
|
3223
3248
|
return _request(client, httpRequest, {
|