@sanity/client 7.24.0 → 7.26.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 +7 -20
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +7 -20
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +8 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -21
- 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 +150 -1
- package/dist/stega.browser.d.ts +150 -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 +150 -1
- package/dist/stega.d.ts +150 -1
- package/dist/stega.js +4 -0
- package/dist/stega.js.map +1 -1
- package/package.json +2 -1
- package/src/data/dataMethods.ts +16 -30
- package/src/stega/index.ts +2 -1
- package/src/stega/stegaBrand.ts +111 -0
- package/src/stega/stegaClean.ts +23 -2
- package/umd/sanityClient.js +7 -20
- package/umd/sanityClient.min.js +1 -1
package/dist/stega.d.ts
CHANGED
|
@@ -878,6 +878,26 @@ export declare type ClientReturn<
|
|
|
878
878
|
Fallback = Any,
|
|
879
879
|
> = GroqString extends keyof SanityQueries ? SanityQueries[GroqString] : Fallback
|
|
880
880
|
|
|
881
|
+
/**
|
|
882
|
+
* Drop-in replacement for `ClientReturn` that brands the result strings as `StegaString`, for use
|
|
883
|
+
* as the first generic of `client.fetch` when stega is enabled:
|
|
884
|
+
* ```ts
|
|
885
|
+
* import {createClient} from '@sanity/client'
|
|
886
|
+
* import type {ClientReturnStega} from '@sanity/client/stega'
|
|
887
|
+
*
|
|
888
|
+
* const query = '*[_type == "post"][0]'
|
|
889
|
+
* const post = await client.fetch<ClientReturnStega<typeof query>>(query)
|
|
890
|
+
* ```
|
|
891
|
+
* When the query is registered in the `SanityQueries` interface (for example by `sanity typegen`),
|
|
892
|
+
* the result type is looked up and deeply branded with `StegaBranded`. Otherwise it falls back to
|
|
893
|
+
* `Fallback`, which defaults to `any`, just like `ClientReturn`.
|
|
894
|
+
*
|
|
895
|
+
* @beta
|
|
896
|
+
*/
|
|
897
|
+
export declare type ClientReturnStega<GroqString extends string, Fallback = Any> = StegaBranded<
|
|
898
|
+
ClientReturn<GroqString, Fallback>
|
|
899
|
+
>
|
|
900
|
+
|
|
881
901
|
/**
|
|
882
902
|
* @public
|
|
883
903
|
* @deprecated -- use `ClientConfig` instead
|
|
@@ -6168,12 +6188,118 @@ export declare interface SingleMutationResult {
|
|
|
6168
6188
|
/** @public */
|
|
6169
6189
|
export declare type StackablePerspective = ('published' | 'drafts' | string) & {}
|
|
6170
6190
|
|
|
6191
|
+
/**
|
|
6192
|
+
* Marks strings in an already fetched query result as potentially containing stega-encoded data,
|
|
6193
|
+
* by re-typing them as `StegaString`. Comparing branded strings to string literals is a type error
|
|
6194
|
+
* until they're cleaned with `stegaClean()`, which recovers the original type.
|
|
6195
|
+
*
|
|
6196
|
+
* This is an identity function, it only changes the type of the input, not its value.
|
|
6197
|
+
* Prefer passing `ClientReturnStega` as the first generic to `client.fetch` when possible, and use
|
|
6198
|
+
* this function for data that has already been fetched.
|
|
6199
|
+
*
|
|
6200
|
+
* @beta
|
|
6201
|
+
*/
|
|
6202
|
+
export declare function stegaBrand<Result>(result: Result): StegaBranded<Result>
|
|
6203
|
+
|
|
6204
|
+
/**
|
|
6205
|
+
* Deeply brands the string properties of a query result as `StegaString`, marking them as
|
|
6206
|
+
* potentially containing stega-encoded data.
|
|
6207
|
+
*
|
|
6208
|
+
* String properties that the stega encoder is guaranteed to never encode keep their plain type:
|
|
6209
|
+
* - keys starting with `_` (`_id`, `_type`, `_createdAt`, `_updatedAt`, `_key`, `_ref`, `_rev` and
|
|
6210
|
+
* so on), which also preserves discriminated union narrowing on `_type`
|
|
6211
|
+
* - `slug.current` patterns: a `current` key directly under a `slug` key, as well as string valued
|
|
6212
|
+
* `slug` keys (covering `"slug": slug.current` projections)
|
|
6213
|
+
* - Portable Text internals: the encoder only visits `children` of `_type: 'block'` objects and
|
|
6214
|
+
* `text` of `_type: 'span'` objects, so properties like `style`, `listItem` and `marks` stay
|
|
6215
|
+
* plain
|
|
6216
|
+
*
|
|
6217
|
+
* Every other string is assumed to be "poisoned", even if the runtime `filter` happens to skip it
|
|
6218
|
+
* (URLs, dates, keys ending in `Id`, denylisted keys). Being conservative is safe: the worst case
|
|
6219
|
+
* is a redundant `stegaClean()` call, whereas under-branding would hide real bugs.
|
|
6220
|
+
*
|
|
6221
|
+
* The `Key` and `ParentKey` type parameters track the key the current value is found under, and
|
|
6222
|
+
* the key of the object containing it, they're only used internally during recursion.
|
|
6223
|
+
*
|
|
6224
|
+
* @beta
|
|
6225
|
+
*/
|
|
6226
|
+
export declare type StegaBranded<
|
|
6227
|
+
T,
|
|
6228
|
+
Key extends PropertyKey = number,
|
|
6229
|
+
ParentKey extends PropertyKey = number,
|
|
6230
|
+
> = 0 extends 1 & T
|
|
6231
|
+
? T
|
|
6232
|
+
: T extends string
|
|
6233
|
+
? T extends {
|
|
6234
|
+
readonly ' stegaBrand': string
|
|
6235
|
+
}
|
|
6236
|
+
? T
|
|
6237
|
+
: Key extends `_${string}` | 'slug'
|
|
6238
|
+
? T
|
|
6239
|
+
: Key extends 'current'
|
|
6240
|
+
? ParentKey extends 'slug'
|
|
6241
|
+
? T
|
|
6242
|
+
: StegaString<T>
|
|
6243
|
+
: StegaString<T>
|
|
6244
|
+
: T extends number | bigint | boolean | null | undefined
|
|
6245
|
+
? T
|
|
6246
|
+
: T extends Date | RegExp | ((...args: never[]) => unknown)
|
|
6247
|
+
? T
|
|
6248
|
+
: T extends readonly unknown[]
|
|
6249
|
+
? {
|
|
6250
|
+
[Index in keyof T]: StegaBranded<T[Index], number, number>
|
|
6251
|
+
}
|
|
6252
|
+
: T extends {
|
|
6253
|
+
_type: 'block'
|
|
6254
|
+
}
|
|
6255
|
+
? {
|
|
6256
|
+
[K in keyof T]: K extends 'children' ? StegaBranded<T[K], K, Key> : T[K]
|
|
6257
|
+
}
|
|
6258
|
+
: T extends {
|
|
6259
|
+
_type: 'span'
|
|
6260
|
+
}
|
|
6261
|
+
? {
|
|
6262
|
+
[K in keyof T]: K extends 'text' ? StegaBranded<T[K], K, Key> : T[K]
|
|
6263
|
+
}
|
|
6264
|
+
: T extends object
|
|
6265
|
+
? {
|
|
6266
|
+
[K in keyof T]: StegaBranded<T[K], K, Key>
|
|
6267
|
+
}
|
|
6268
|
+
: T
|
|
6269
|
+
|
|
6171
6270
|
/**
|
|
6172
6271
|
* Can take a `result` JSON from a `const {result} = client.fetch(query, params, {filterResponse: false})`
|
|
6173
6272
|
* and remove all stega-encoded data from it.
|
|
6273
|
+
* If the result type has strings branded as `StegaString` (by `ClientReturnStega` or `stegaBrand()`),
|
|
6274
|
+
* the brand is stripped and the original string type is restored.
|
|
6174
6275
|
* @public
|
|
6175
6276
|
*/
|
|
6176
|
-
export declare function stegaClean<Result = unknown>(result: Result): Result
|
|
6277
|
+
export declare function stegaClean<Result = unknown>(result: Result): StegaCleaned<Result>
|
|
6278
|
+
|
|
6279
|
+
/**
|
|
6280
|
+
* The result of removing stega-encoded data from a value with `stegaClean()`: strings that were
|
|
6281
|
+
* branded as `StegaString` are returned to their original type, everything else is left as-is.
|
|
6282
|
+
* @public
|
|
6283
|
+
*/
|
|
6284
|
+
export declare type StegaCleaned<T> = 0 extends 1 & T
|
|
6285
|
+
? T
|
|
6286
|
+
: T extends {
|
|
6287
|
+
readonly ' stegaBrand': infer Original extends string
|
|
6288
|
+
}
|
|
6289
|
+
? Original
|
|
6290
|
+
: T extends string | number | bigint | boolean | null | undefined
|
|
6291
|
+
? T
|
|
6292
|
+
: T extends Date | RegExp | ((...args: never[]) => unknown)
|
|
6293
|
+
? T
|
|
6294
|
+
: T extends readonly unknown[]
|
|
6295
|
+
? {
|
|
6296
|
+
[Index in keyof T]: StegaCleaned<T[Index]>
|
|
6297
|
+
}
|
|
6298
|
+
: T extends object
|
|
6299
|
+
? {
|
|
6300
|
+
[K in keyof T]: StegaCleaned<T[K]>
|
|
6301
|
+
}
|
|
6302
|
+
: T
|
|
6177
6303
|
|
|
6178
6304
|
/** @public */
|
|
6179
6305
|
export declare interface StegaConfig {
|
|
@@ -6253,6 +6379,29 @@ export declare function stegaEncodeSourceMap<Result = unknown>(
|
|
|
6253
6379
|
config: InitializedStegaConfig_2,
|
|
6254
6380
|
): Result
|
|
6255
6381
|
|
|
6382
|
+
/**
|
|
6383
|
+
* A string that might contain stega-encoded data, and is unsafe to compare against string literals
|
|
6384
|
+
* until it's been cleaned with `stegaClean()`.
|
|
6385
|
+
*
|
|
6386
|
+
* The type intersects a template literal that has a human readable suffix, with a brand property
|
|
6387
|
+
* that holds the original string type:
|
|
6388
|
+
* - The suffix makes TypeScript report "This comparison appears to be unintentional" (TS2367) when
|
|
6389
|
+
* the string is compared to a literal (`===`, `switch` cases), and makes it unassignable to
|
|
6390
|
+
* literal unions like `'left' | 'right'`, while keeping it assignable to `string` so rendering,
|
|
6391
|
+
* interpolation and string methods keep working. The suffix is a type-level fiction, the runtime
|
|
6392
|
+
* value never ends with that text.
|
|
6393
|
+
* - The brand property preserves the original type so `stegaClean()` can recover it exactly. It's
|
|
6394
|
+
* a string-keyed property rather than a `unique symbol` so that branded types stay structurally
|
|
6395
|
+
* compatible across duplicated copies of `@sanity/client` in `node_modules`. It only exists in
|
|
6396
|
+
* the type system and is never present at runtime.
|
|
6397
|
+
*
|
|
6398
|
+
* @beta
|
|
6399
|
+
*/
|
|
6400
|
+
export declare type StegaString<T extends string = string> =
|
|
6401
|
+
`${T} (may contain hidden stega characters)` & {
|
|
6402
|
+
readonly ' stegaBrand': T
|
|
6403
|
+
}
|
|
6404
|
+
|
|
6256
6405
|
/**
|
|
6257
6406
|
* Allowed still image formats (thumbnail + storyboard).
|
|
6258
6407
|
* @public
|
package/dist/stega.js
CHANGED
|
@@ -2,6 +2,9 @@ import { createClient as createClient$1, requester as requester$1, ObservableSan
|
|
|
2
2
|
export * from "@sanity/client";
|
|
3
3
|
import { encodeIntoResult, stegaEncodeSourceMap } from "./_chunks-es/stegaEncodeSourceMap.js";
|
|
4
4
|
import { stegaClean, vercelStegaCleanAll } from "./_chunks-es/stegaClean.js";
|
|
5
|
+
function stegaBrand(result) {
|
|
6
|
+
return result;
|
|
7
|
+
}
|
|
5
8
|
class SanityStegaClient extends SanityClient {
|
|
6
9
|
}
|
|
7
10
|
class ObservableSanityStegaClient extends ObservableSanityClient {
|
|
@@ -13,6 +16,7 @@ export {
|
|
|
13
16
|
createClient,
|
|
14
17
|
encodeIntoResult,
|
|
15
18
|
requester,
|
|
19
|
+
stegaBrand,
|
|
16
20
|
stegaClean,
|
|
17
21
|
stegaEncodeSourceMap,
|
|
18
22
|
vercelStegaCleanAll
|
package/dist/stega.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stega.js","sources":["../src/stega/index.ts"],"sourcesContent":["export * from '@sanity/client'\nimport {\n createClient as originalCreateClient,\n ObservableSanityClient,\n requester as originalRequester,\n SanityClient,\n} from '@sanity/client'\n\nexport {encodeIntoResult} from './encodeIntoResult'\nexport {stegaClean, vercelStegaCleanAll} from './stegaClean'\nexport {stegaEncodeSourceMap} from './stegaEncodeSourceMap'\nexport * from './types'\n\n/**\n * @deprecated -- Use `import {SanityClient} from '@sanity/client'` instead\n * @public\n */\nexport class SanityStegaClient extends SanityClient {}\n\n/**\n * @deprecated -- Use `import {ObservableSanityClient} from '@sanity/client'` instead\n * @public\n */\nexport class ObservableSanityStegaClient extends ObservableSanityClient {}\n\n/**\n * @deprecated -- Use `import {requester} from '@sanity/client'` instead\n * @public\n */\nexport const requester = originalRequester\n\n/**\n * @deprecated -- Use `import {createClient} from '@sanity/client'` instead\n * @public\n */\nexport const createClient = originalCreateClient\n"],"names":["originalRequester","originalCreateClient"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"stega.js","sources":["../src/stega/stegaBrand.ts","../src/stega/index.ts"],"sourcesContent":["import type {Any, ClientReturn} from '@sanity/client'\n\n/**\n * A string that might contain stega-encoded data, and is unsafe to compare against string literals\n * until it's been cleaned with `stegaClean()`.\n *\n * The type intersects a template literal that has a human readable suffix, with a brand property\n * that holds the original string type:\n * - The suffix makes TypeScript report \"This comparison appears to be unintentional\" (TS2367) when\n * the string is compared to a literal (`===`, `switch` cases), and makes it unassignable to\n * literal unions like `'left' | 'right'`, while keeping it assignable to `string` so rendering,\n * interpolation and string methods keep working. The suffix is a type-level fiction, the runtime\n * value never ends with that text.\n * - The brand property preserves the original type so `stegaClean()` can recover it exactly. It's\n * a string-keyed property rather than a `unique symbol` so that branded types stay structurally\n * compatible across duplicated copies of `@sanity/client` in `node_modules`. It only exists in\n * the type system and is never present at runtime.\n *\n * @beta\n */\nexport type StegaString<T extends string = string> =\n `${T} (may contain hidden stega characters)` & {\n readonly ' stegaBrand': T\n }\n\n/**\n * Deeply brands the string properties of a query result as `StegaString`, marking them as\n * potentially containing stega-encoded data.\n *\n * String properties that the stega encoder is guaranteed to never encode keep their plain type:\n * - keys starting with `_` (`_id`, `_type`, `_createdAt`, `_updatedAt`, `_key`, `_ref`, `_rev` and\n * so on), which also preserves discriminated union narrowing on `_type`\n * - `slug.current` patterns: a `current` key directly under a `slug` key, as well as string valued\n * `slug` keys (covering `\"slug\": slug.current` projections)\n * - Portable Text internals: the encoder only visits `children` of `_type: 'block'` objects and\n * `text` of `_type: 'span'` objects, so properties like `style`, `listItem` and `marks` stay\n * plain\n *\n * Every other string is assumed to be \"poisoned\", even if the runtime `filter` happens to skip it\n * (URLs, dates, keys ending in `Id`, denylisted keys). Being conservative is safe: the worst case\n * is a redundant `stegaClean()` call, whereas under-branding would hide real bugs.\n *\n * The `Key` and `ParentKey` type parameters track the key the current value is found under, and\n * the key of the object containing it, they're only used internally during recursion.\n *\n * @beta\n */\nexport type StegaBranded<\n T,\n Key extends PropertyKey = number,\n ParentKey extends PropertyKey = number,\n> = 0 extends 1 & T\n ? T\n : T extends string\n ? T extends {readonly ' stegaBrand': string}\n ? T\n : Key extends `_${string}` | 'slug'\n ? T\n : Key extends 'current'\n ? ParentKey extends 'slug'\n ? T\n : StegaString<T>\n : StegaString<T>\n : T extends number | bigint | boolean | null | undefined\n ? T\n : T extends Date | RegExp | ((...args: never[]) => unknown)\n ? T\n : T extends readonly unknown[]\n ? {[Index in keyof T]: StegaBranded<T[Index], number, number>}\n : T extends {_type: 'block'}\n ? {[K in keyof T]: K extends 'children' ? StegaBranded<T[K], K, Key> : T[K]}\n : T extends {_type: 'span'}\n ? {[K in keyof T]: K extends 'text' ? StegaBranded<T[K], K, Key> : T[K]}\n : T extends object\n ? {[K in keyof T]: StegaBranded<T[K], K, Key>}\n : T\n\n/**\n * Drop-in replacement for `ClientReturn` that brands the result strings as `StegaString`, for use\n * as the first generic of `client.fetch` when stega is enabled:\n * ```ts\n * import {createClient} from '@sanity/client'\n * import type {ClientReturnStega} from '@sanity/client/stega'\n *\n * const query = '*[_type == \"post\"][0]'\n * const post = await client.fetch<ClientReturnStega<typeof query>>(query)\n * ```\n * When the query is registered in the `SanityQueries` interface (for example by `sanity typegen`),\n * the result type is looked up and deeply branded with `StegaBranded`. Otherwise it falls back to\n * `Fallback`, which defaults to `any`, just like `ClientReturn`.\n *\n * @beta\n */\nexport type ClientReturnStega<GroqString extends string, Fallback = Any> = StegaBranded<\n ClientReturn<GroqString, Fallback>\n>\n\n/**\n * Marks strings in an already fetched query result as potentially containing stega-encoded data,\n * by re-typing them as `StegaString`. Comparing branded strings to string literals is a type error\n * until they're cleaned with `stegaClean()`, which recovers the original type.\n *\n * This is an identity function, it only changes the type of the input, not its value.\n * Prefer passing `ClientReturnStega` as the first generic to `client.fetch` when possible, and use\n * this function for data that has already been fetched.\n *\n * @beta\n */\nexport function stegaBrand<Result>(result: Result): StegaBranded<Result> {\n return result as StegaBranded<Result>\n}\n","export * from '@sanity/client'\nimport {\n createClient as originalCreateClient,\n ObservableSanityClient,\n requester as originalRequester,\n SanityClient,\n} from '@sanity/client'\n\nexport {encodeIntoResult} from './encodeIntoResult'\nexport {type ClientReturnStega, stegaBrand, type StegaBranded, type StegaString} from './stegaBrand'\nexport {stegaClean, type StegaCleaned, vercelStegaCleanAll} from './stegaClean'\nexport {stegaEncodeSourceMap} from './stegaEncodeSourceMap'\nexport * from './types'\n\n/**\n * @deprecated -- Use `import {SanityClient} from '@sanity/client'` instead\n * @public\n */\nexport class SanityStegaClient extends SanityClient {}\n\n/**\n * @deprecated -- Use `import {ObservableSanityClient} from '@sanity/client'` instead\n * @public\n */\nexport class ObservableSanityStegaClient extends ObservableSanityClient {}\n\n/**\n * @deprecated -- Use `import {requester} from '@sanity/client'` instead\n * @public\n */\nexport const requester = originalRequester\n\n/**\n * @deprecated -- Use `import {createClient} from '@sanity/client'` instead\n * @public\n */\nexport const createClient = originalCreateClient\n"],"names":["originalRequester","originalCreateClient"],"mappings":";;;;AA4GO,SAAS,WAAmB,QAAsC;AACvE,SAAO;AACT;AC5FO,MAAM,0BAA0B,aAAa;AAAC;AAM9C,MAAM,oCAAoC,uBAAuB;AAAC;AAMlE,MAAM,YAAYA,aAMZ,eAAeC;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/client",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.26.0",
|
|
4
4
|
"description": "Client for retrieving, creating and patching data from Sanity.io",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -145,6 +145,7 @@
|
|
|
145
145
|
"@sanity/pkg-utils": "^7.2.2",
|
|
146
146
|
"@types/json-diff": "^1.0.3",
|
|
147
147
|
"@types/node": "^22.9.0",
|
|
148
|
+
"@types/react": "^19.2.17",
|
|
148
149
|
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
|
149
150
|
"@typescript-eslint/parser": "^8.29.1",
|
|
150
151
|
"@vercel/stega": "1.1.0",
|
package/src/data/dataMethods.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
Any,
|
|
14
14
|
BaseActionOptions,
|
|
15
15
|
BaseMutationOptions,
|
|
16
|
+
ClientVariantConditions,
|
|
16
17
|
CreateVersionAction,
|
|
17
18
|
DiscardVersionAction,
|
|
18
19
|
FirstDocumentIdMutationOptions,
|
|
@@ -703,31 +704,17 @@ export function _requestObservable<R>(
|
|
|
703
704
|
|
|
704
705
|
const variantOption = options.variant || config.variant
|
|
705
706
|
|
|
706
|
-
if (typeof variantOption
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
...options.query,
|
|
711
|
-
}
|
|
707
|
+
if (typeof variantOption === 'string') {
|
|
708
|
+
options.query = {
|
|
709
|
+
variant: variantOption,
|
|
710
|
+
...options.query,
|
|
712
711
|
}
|
|
712
|
+
}
|
|
713
713
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (variantConditions.length > 1) {
|
|
719
|
-
const formatter = new Intl.ListFormat('en')
|
|
720
|
-
|
|
721
|
-
// eslint-disable-next-line no-console -- will be removed in an upcoming version; it's better this behaviour is noisy and obvious
|
|
722
|
-
console.warn(
|
|
723
|
-
`The Sanity client's beta \`variant\` option currently only supports one condition. Dropped: ${formatter.format(variantConditions.slice(1).map(([subject]) => JSON.stringify(subject)))}.`,
|
|
724
|
-
)
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
options.query = {
|
|
728
|
-
...Object.fromEntries(searchParams),
|
|
729
|
-
...options.query,
|
|
730
|
-
}
|
|
714
|
+
if (typeof variantOption === 'object') {
|
|
715
|
+
options.query = {
|
|
716
|
+
variantCondition: variantConditionsToQueryArray(variantOption),
|
|
717
|
+
...options.query,
|
|
731
718
|
}
|
|
732
719
|
}
|
|
733
720
|
|
|
@@ -875,11 +862,10 @@ const resourceDataBase = (config: InitializedClientConfig): string => {
|
|
|
875
862
|
}
|
|
876
863
|
}
|
|
877
864
|
|
|
878
|
-
function
|
|
879
|
-
|
|
880
|
-
):
|
|
881
|
-
return
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
])
|
|
865
|
+
function variantConditionsToQueryArray(
|
|
866
|
+
variantConditions: ClientVariantConditions,
|
|
867
|
+
): `${string}:${string}`[] {
|
|
868
|
+
return Object.entries(variantConditions)
|
|
869
|
+
.map<`${string}:${string}`>(([condition, value]) => `${condition}:${value}`)
|
|
870
|
+
.toSorted()
|
|
885
871
|
}
|
package/src/stega/index.ts
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
} from '@sanity/client'
|
|
8
8
|
|
|
9
9
|
export {encodeIntoResult} from './encodeIntoResult'
|
|
10
|
-
export {
|
|
10
|
+
export {type ClientReturnStega, stegaBrand, type StegaBranded, type StegaString} from './stegaBrand'
|
|
11
|
+
export {stegaClean, type StegaCleaned, vercelStegaCleanAll} from './stegaClean'
|
|
11
12
|
export {stegaEncodeSourceMap} from './stegaEncodeSourceMap'
|
|
12
13
|
export * from './types'
|
|
13
14
|
|
|
@@ -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/umd/sanityClient.js
CHANGED
|
@@ -3146,23 +3146,13 @@ ${selectionOpts}`);
|
|
|
3146
3146
|
}, (Array.isArray(perspectiveOption) && perspectiveOption.length > 0 || // previewDrafts was renamed to drafts, but keep for backwards compat
|
|
3147
3147
|
perspectiveOption === "previewDrafts" || perspectiveOption === "drafts") && useCdn && (useCdn = false, printCdnPreviewDraftsWarning()));
|
|
3148
3148
|
const variantOption = options.variant || config.variant;
|
|
3149
|
-
|
|
3149
|
+
typeof variantOption == "string" && (options.query = {
|
|
3150
3150
|
variant: variantOption,
|
|
3151
3151
|
...options.query
|
|
3152
|
-
}), typeof variantOption == "object"
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
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 });
|
|
3152
|
+
}), typeof variantOption == "object" && (options.query = {
|
|
3153
|
+
variantCondition: variantConditionsToQueryArray(variantOption),
|
|
3154
|
+
...options.query
|
|
3155
|
+
}), 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 });
|
|
3166
3156
|
}
|
|
3167
3157
|
const reqOptions = requestOptions(
|
|
3168
3158
|
config,
|
|
@@ -3237,11 +3227,8 @@ ${selectionOpts}`);
|
|
|
3237
3227
|
throw new Error(`Unsupported resource type: ${type.toString()}`);
|
|
3238
3228
|
}
|
|
3239
3229
|
};
|
|
3240
|
-
function
|
|
3241
|
-
return
|
|
3242
|
-
"variantCondition",
|
|
3243
|
-
`${condition}:${value}`
|
|
3244
|
-
]);
|
|
3230
|
+
function variantConditionsToQueryArray(variantConditions) {
|
|
3231
|
+
return Object.entries(variantConditions).map(([condition, value]) => `${condition}:${value}`).toSorted();
|
|
3245
3232
|
}
|
|
3246
3233
|
function _generate(client, httpRequest, request) {
|
|
3247
3234
|
const dataset2 = hasDataset(client.config());
|