@sanity/client 6.6.0 → 6.7.1-canary.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/client",
3
- "version": "6.6.0",
3
+ "version": "6.7.1-canary.0",
4
4
  "description": "Client for retrieving, creating and patching data from Sanity.io",
5
5
  "keywords": [
6
6
  "sanity",
@@ -44,6 +44,13 @@
44
44
  },
45
45
  "default": "./dist/index.js"
46
46
  },
47
+ "./csm": {
48
+ "types": "./dist/csm.d.ts",
49
+ "source": "./src/csm/index.ts",
50
+ "import": "./dist/csm.js",
51
+ "require": "./dist/csm.cjs",
52
+ "default": "./dist/csm.js"
53
+ },
47
54
  "./package.json": "./package.json"
48
55
  },
49
56
  "main": "./dist/index.cjs",
@@ -55,6 +62,13 @@
55
62
  "./dist/index.js": "./dist/index.browser.js"
56
63
  },
57
64
  "types": "./dist/index.d.ts",
65
+ "typesVersions": {
66
+ "*": {
67
+ "csm": [
68
+ "./dist/csm.d.ts"
69
+ ]
70
+ }
71
+ },
58
72
  "files": [
59
73
  "dist",
60
74
  "src",
@@ -91,33 +105,33 @@
91
105
  },
92
106
  "dependencies": {
93
107
  "@sanity/eventsource": "^5.0.0",
94
- "get-it": "^8.4.3",
108
+ "get-it": "^8.4.4",
95
109
  "rxjs": "^7.0.0"
96
110
  },
97
111
  "devDependencies": {
98
- "@edge-runtime/types": "^2.2.4",
99
- "@edge-runtime/vm": "^3.1.4",
100
- "@rollup/plugin-commonjs": "^25.0.5",
112
+ "@edge-runtime/types": "^2.2.6",
113
+ "@edge-runtime/vm": "^3.1.6",
114
+ "@rollup/plugin-commonjs": "^25.0.7",
101
115
  "@rollup/plugin-node-resolve": "^15.2.3",
102
- "@sanity/pkg-utils": "^3.0.0",
103
- "@types/node": "^20.8.4",
104
- "@typescript-eslint/eslint-plugin": "^6.7.5",
105
- "@typescript-eslint/parser": "^6.7.5",
116
+ "@sanity/pkg-utils": "^3.2.1",
117
+ "@types/node": "^20.8.8",
118
+ "@typescript-eslint/eslint-plugin": "^6.9.0",
119
+ "@typescript-eslint/parser": "^6.9.0",
106
120
  "@vitest/coverage-v8": "^0.34.6",
107
- "eslint": "^8.51.0",
121
+ "eslint": "^8.52.0",
108
122
  "eslint-config-prettier": "^9.0.0",
109
123
  "eslint-plugin-prettier": "^5.0.1",
110
124
  "eslint-plugin-simple-import-sort": "^10.0.0",
111
125
  "faucet": "^0.0.4",
112
- "happy-dom": "^12.9.1",
126
+ "happy-dom": "^12.10.1",
113
127
  "ls-engines": "^0.9.0",
114
- "nock": "^13.3.4",
128
+ "nock": "^13.3.6",
115
129
  "prettier": "^3.0.3",
116
130
  "prettier-plugin-packagejson": "^2.4.6",
117
131
  "rimraf": "^5.0.1",
118
- "rollup": "^4.0.2",
132
+ "rollup": "^4.1.4",
119
133
  "sse-channel": "^4.0.0",
120
- "terser": "^5.21.0",
134
+ "terser": "^5.22.0",
121
135
  "typescript": "^5.2.2",
122
136
  "vitest": "^0.34.6",
123
137
  "vitest-github-actions-reporter": "^0.10.0"
@@ -0,0 +1,42 @@
1
+ import {ContentSourceMapDocument, ContentSourceMapDocuments} from '../types'
2
+ import {parseJsonPath} from './jsonpath'
3
+ import type {PathSegment, StudioUrl} from './types'
4
+
5
+ /** @alpha */
6
+ export type EditLink = `/intent/edit/id=${string};type=${string};path=${string}`
7
+
8
+ /** @alpha */
9
+ export interface EditLinkProps {
10
+ studioUrl: StudioUrl
11
+ document: ContentSourceMapDocument
12
+ }
13
+
14
+ /** @alpha */
15
+ export type DefineEditLink = (
16
+ studioUrl: StudioUrl,
17
+ ) => (sourceDocument: ContentSourceMapDocuments[number]) => `${StudioUrl}${EditLink}`
18
+
19
+ /** @alpha */
20
+ export function defineEditLink(
21
+ _studioUrl: StudioUrl,
22
+ ): (
23
+ sourceDocument: ContentSourceMapDocuments[number],
24
+ path: string | PathSegment[],
25
+ ) => `${StudioUrl}${EditLink}` {
26
+ const studioUrl = _studioUrl.replace(/\/$/, '')
27
+ return ({_id, _type}, path) =>
28
+ `${studioUrl}/intent/edit/id=${_id};type=${_type};path=${encodeJsonPathToUriComponent(path)}`
29
+ }
30
+
31
+ /** @alpha */
32
+ export function encodeJsonPathToUriComponent(path: string | PathSegment[]): string {
33
+ const sourcePath = Array.isArray(path) ? path : parseJsonPath(path)
34
+ return encodeURIComponent(
35
+ sourcePath
36
+ .map((key, i) =>
37
+ // eslint-disable-next-line no-nested-ternary
38
+ typeof key === 'number' ? `[${key}]` : i > 0 ? `.${key}` : key,
39
+ )
40
+ .join(''),
41
+ )
42
+ }
@@ -0,0 +1,20 @@
1
+ export type {
2
+ ContentSourceMap,
3
+ ContentSourceMapDocument,
4
+ ContentSourceMapDocumentBase,
5
+ ContentSourceMapDocuments,
6
+ ContentSourceMapDocumentValueSource,
7
+ ContentSourceMapLiteralSource,
8
+ ContentSourceMapMapping,
9
+ ContentSourceMapMappings,
10
+ ContentSourceMapPaths,
11
+ ContentSourceMapRemoteDocument,
12
+ ContentSourceMapSource,
13
+ ContentSourceMapUnknownSource,
14
+ ContentSourceMapValueMapping,
15
+ } from '../types'
16
+ export {encodeJsonPathToUriComponent} from './editIntent'
17
+ export {jsonPath} from './jsonpath'
18
+ export * from './mapToEditLinks'
19
+ export {resolveMapping, walkMap, type WalkMapFn} from './sourcemap'
20
+ export type * from './types'
@@ -0,0 +1,40 @@
1
+ import { expect, test } from 'vitest'
2
+
3
+ import { jsonPath, parseJsonPath } from './jsonpath'
4
+
5
+ test('formats normalised JSON Paths', () => {
6
+ expect(jsonPath(['foo', 'bar', 0, 'baz'])).toBe(
7
+ "$['foo']['bar'][0]['baz']",
8
+ )
9
+ })
10
+
11
+ test('formats normalised JSON Paths with escaped characters', () => {
12
+ expect(jsonPath(['foo', 'bar', 0, 'baz', "it's a 'test'"])).toBe(
13
+ "$['foo']['bar'][0]['baz']['it\\'s a \\'test\\'']",
14
+ )
15
+ })
16
+
17
+ test('parses normalised JSON Paths', () => {
18
+ expect(parseJsonPath("$['foo']['bar'][0]['baz']")).toEqual([
19
+ 'foo',
20
+ 'bar',
21
+ 0,
22
+ 'baz',
23
+ ])
24
+ })
25
+
26
+ test('parses normalised JSON Paths with escaped characters', () => {
27
+ expect(
28
+ parseJsonPath("$['foo']['bar'][0]['baz']['it\\'s a \\'test\\'']"),
29
+ ).toEqual(['foo', 'bar', 0, 'baz', "it's a 'test'"])
30
+ })
31
+
32
+ test('parses normalised JSON Paths with key array filter selectors', () => {
33
+ expect(parseJsonPath("$['foo'][?(@._key=='section-1')][0]['baz'][?(@._key=='section-2')]")).toEqual([
34
+ 'foo',
35
+ { key: 'section-1', index: -1 },
36
+ 0,
37
+ 'baz',
38
+ { key: 'section-2', index: -1},
39
+ ])
40
+ })
@@ -0,0 +1,89 @@
1
+ import type { PathSegment } from './types'
2
+
3
+ const ESCAPE: Record<string, string> = {
4
+ '\f': '\\f',
5
+ '\n': '\\n',
6
+ '\r': '\\r',
7
+ '\t': '\\t',
8
+ "'": "\\'",
9
+ '\\': '\\\\',
10
+ }
11
+
12
+ const UNESCAPE: Record<string, string> = {
13
+ '\\f': '\f',
14
+ '\\n': '\n',
15
+ '\\r': '\r',
16
+ '\\t': '\t',
17
+ "\\'": "'",
18
+ '\\\\': '\\',
19
+ }
20
+
21
+ /** @internal */
22
+ export function jsonPath(
23
+ path: PathSegment[],
24
+ opts?: {
25
+ keyArraySelectors: boolean
26
+ },
27
+ ): string {
28
+ return `$${path
29
+ .map((segment) => {
30
+ if (typeof segment === 'string') {
31
+ const escapedKey = segment.replace(/[\f\n\r\t'\\]/g, (match) => {
32
+ return ESCAPE[match]
33
+ })
34
+ return `['${escapedKey}']`
35
+ }
36
+
37
+ if (typeof segment === 'number') {
38
+ return `[${segment}]`
39
+ }
40
+
41
+ if (opts?.keyArraySelectors && segment.key !== '') {
42
+ const escapedKey = segment.key.replace(/['\\]/g, (match) => {
43
+ return ESCAPE[match]
44
+ })
45
+ return `[?(@._key=='${escapedKey}')]`
46
+ }
47
+
48
+ return `[${segment.index}]`
49
+ })
50
+ .join('')}`
51
+ }
52
+
53
+ /** @internal */
54
+ export function parseJsonPath(path: string): PathSegment[] {
55
+ const parsed: PathSegment[] = []
56
+
57
+ const parseRe = /\['(.*?)'\]|\[(\d+)\]|\[\?\(@\._key=='(.*?)'\)\]/g
58
+ let match: RegExpExecArray | null
59
+
60
+ while ((match = parseRe.exec(path)) !== null) {
61
+ if (match[1] !== undefined) {
62
+ const key = match[1].replace(/\\(\\|f|n|r|t|')/g, (m) => {
63
+ return UNESCAPE[m]
64
+ })
65
+
66
+ parsed.push(key)
67
+ continue
68
+ }
69
+
70
+ if (match[2] !== undefined) {
71
+ parsed.push(parseInt(match[2], 10))
72
+ continue
73
+ }
74
+
75
+ if (match[3] !== undefined) {
76
+ const key = match[3].replace(/\\(\\')/g, (m) => {
77
+ return UNESCAPE[m]
78
+ })
79
+
80
+ parsed.push({
81
+ key,
82
+ index: -1,
83
+ })
84
+ continue
85
+ }
86
+ }
87
+
88
+ return parsed
89
+ }
@@ -0,0 +1,103 @@
1
+ import {expect, test} from 'vitest'
2
+
3
+ import {ContentSourceMapDocument} from '../types'
4
+ import {mapToEditLinks} from './mapToEditLinks'
5
+
6
+ const resultEditLinksTestCases = [
7
+ {
8
+ name: 'returns links for all strings in the result',
9
+ queryResult: {
10
+ result: [
11
+ {
12
+ linkMe: 'that',
13
+ deep: [
14
+ {
15
+ linkMe: 'that',
16
+ },
17
+ ],
18
+ },
19
+ ],
20
+ resultSourceMap: {
21
+ documents: [
22
+ {
23
+ _id: 'foo',
24
+ _type: 'foo',
25
+ },
26
+ ] satisfies ContentSourceMapDocument[],
27
+ paths: ["$['linkMe']", "$['deep']"],
28
+ mappings: {
29
+ "$[0]['linkMe']": {
30
+ source: {
31
+ document: 0,
32
+ path: 0,
33
+ type: 'documentValue',
34
+ },
35
+ type: 'value',
36
+ },
37
+ "$[0]['deep']": {
38
+ source: {
39
+ document: 0,
40
+ path: 1,
41
+ type: 'documentValue',
42
+ },
43
+ type: 'value',
44
+ },
45
+ },
46
+ },
47
+ },
48
+ expected: [
49
+ {
50
+ linkMe: 'test.sanity.studio/intent/edit/id=foo;type=foo;path=linkMe',
51
+ deep: [
52
+ {
53
+ linkMe: 'test.sanity.studio/intent/edit/id=foo;type=foo;path=deep%5B0%5D.linkMe',
54
+ },
55
+ ],
56
+ },
57
+ ],
58
+ },
59
+ {
60
+ name: 'results should be unchanged if no compatible types are found',
61
+ queryResult: {
62
+ result: [
63
+ {
64
+ dontLinkMe: 1,
65
+ },
66
+ ],
67
+ resultSourceMap: {
68
+ documents: [
69
+ {
70
+ _id: 'foo',
71
+ _type: 'foo',
72
+ },
73
+ ] satisfies ContentSourceMapDocument[],
74
+ paths: ["$['dontLinkMe']"],
75
+ mappings: {
76
+ "$[0]['dontLinkMe']": {
77
+ source: {
78
+ document: 0,
79
+ path: 0,
80
+ type: 'documentValue',
81
+ },
82
+ type: 'value',
83
+ },
84
+ },
85
+ },
86
+ },
87
+ expected: [
88
+ {
89
+ dontLinkMe: 1,
90
+ },
91
+ ],
92
+ },
93
+ ]
94
+
95
+ test.each(resultEditLinksTestCases)('mapToEditLinks $name', ({queryResult, expected}) => {
96
+ const response = mapToEditLinks(
97
+ queryResult.result,
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ queryResult.resultSourceMap as any,
100
+ 'test.sanity.studio/',
101
+ )
102
+ expect(response).toEqual(expected)
103
+ })
@@ -0,0 +1,11 @@
1
+ import type {ContentSourceMap} from '../types'
2
+ import {defineEditLink} from './editIntent'
3
+ import {encodeIntoResult} from './sourcemap'
4
+
5
+ /** @alpha */
6
+ export function mapToEditLinks<R>(result: R, csm: ContentSourceMap, studioUrl: string): R {
7
+ const createEditLink = defineEditLink(studioUrl)
8
+ return encodeIntoResult(result, csm, (_, sourceDocument, path) => {
9
+ return createEditLink(sourceDocument, path)
10
+ }) as R
11
+ }
@@ -0,0 +1,212 @@
1
+ import {expect, test, vi} from 'vitest'
2
+
3
+ import type {ContentSourceMap} from '../types'
4
+ import {encode} from './sourcemap'
5
+
6
+ const encodeTestCases: {
7
+ name: string
8
+ queryResult: {
9
+ result: unknown
10
+ resultSourceMap: ContentSourceMap
11
+ }
12
+ options?: {
13
+ keyArraySelectors: boolean
14
+ }
15
+ expected: {
16
+ encoderCalls: number
17
+ encoderArgs: unknown[][]
18
+ }
19
+ }[] = [
20
+ {
21
+ name: 'resolves exact mappings to source',
22
+ queryResult: {
23
+ result: [
24
+ {
25
+ _id: 'foo',
26
+ this: 'that',
27
+ },
28
+ ],
29
+ resultSourceMap: {
30
+ documents: [
31
+ {
32
+ _id: 'foo',
33
+ _type: 'bar',
34
+ },
35
+ ],
36
+ paths: ["$['this']"],
37
+ mappings: {
38
+ "$[0]['this']": {
39
+ source: {
40
+ document: 0,
41
+ path: 0,
42
+ type: 'documentValue',
43
+ },
44
+ type: 'value',
45
+ },
46
+ },
47
+ },
48
+ },
49
+ expected: {
50
+ encoderCalls: 1,
51
+ encoderArgs: [['that', {_id: 'foo', _type: 'bar'}, ['this']]],
52
+ },
53
+ },
54
+ {
55
+ name: 'resolves aggregated mappings to source',
56
+ queryResult: {
57
+ result: [
58
+ {
59
+ _id: 'foo',
60
+ nested: {
61
+ object: {
62
+ this: 'that',
63
+ },
64
+ },
65
+ },
66
+ ],
67
+ resultSourceMap: {
68
+ documents: [
69
+ {
70
+ _id: 'foo',
71
+ _type: 'bar',
72
+ },
73
+ ],
74
+ paths: ["$['something']['nested']"],
75
+ mappings: {
76
+ "$[0]['nested']": {
77
+ source: {
78
+ document: 0,
79
+ path: 0,
80
+ type: 'documentValue',
81
+ },
82
+ type: 'value',
83
+ },
84
+ },
85
+ },
86
+ },
87
+ expected: {
88
+ encoderCalls: 1,
89
+ encoderArgs: [
90
+ ['that', {_id: 'foo', _type: 'bar'}, ['something', 'nested', 'object', 'this']],
91
+ ],
92
+ },
93
+ },
94
+ {
95
+ name: 'plucks out _key to use as path segment',
96
+ queryResult: {
97
+ result: [
98
+ {
99
+ _id: 'foo',
100
+ nested: {
101
+ arr: [
102
+ {
103
+ _key: 'im_a_key',
104
+ value: 'that',
105
+ },
106
+ ],
107
+ },
108
+ },
109
+ ],
110
+ resultSourceMap: {
111
+ documents: [
112
+ {
113
+ _id: 'foo',
114
+ _type: 'bar',
115
+ },
116
+ ],
117
+ paths: ["$['projected']['nested']"],
118
+ mappings: {
119
+ "$[0]['nested']": {
120
+ source: {
121
+ document: 0,
122
+ path: 0,
123
+ type: 'documentValue',
124
+ },
125
+ type: 'value',
126
+ },
127
+ },
128
+ },
129
+ },
130
+ options: {
131
+ keyArraySelectors: true,
132
+ },
133
+ expected: {
134
+ encoderCalls: 2,
135
+ encoderArgs: [
136
+ [
137
+ 'im_a_key',
138
+ {_id: 'foo', _type: 'bar'},
139
+ ['projected', 'nested', 'arr', {key: 'im_a_key', index: 0}, '_key'],
140
+ ],
141
+ [
142
+ 'that',
143
+ {_id: 'foo', _type: 'bar'},
144
+ ['projected', 'nested', 'arr', {key: 'im_a_key', index: 0}, 'value'],
145
+ ],
146
+ ],
147
+ },
148
+ },
149
+ {
150
+ name: 'handles _key array filter selectors in source paths',
151
+ queryResult: {
152
+ result: [
153
+ {
154
+ _id: 'foo',
155
+ arr: [
156
+ {
157
+ _key: 'im_a_key',
158
+ value: 'that',
159
+ },
160
+ ],
161
+ },
162
+ ],
163
+ resultSourceMap: {
164
+ documents: [
165
+ {
166
+ _id: 'foo',
167
+ _type: 'bar',
168
+ },
169
+ ],
170
+ paths: ["$['projected'][?(@._key=='fooKey')]"],
171
+ mappings: {
172
+ "$[0]['arr']": {
173
+ source: {
174
+ document: 0,
175
+ path: 0,
176
+ type: 'documentValue',
177
+ },
178
+ type: 'value',
179
+ },
180
+ },
181
+ },
182
+ },
183
+ options: {
184
+ keyArraySelectors: true,
185
+ },
186
+ expected: {
187
+ encoderCalls: 2,
188
+ encoderArgs: [
189
+ [
190
+ 'im_a_key',
191
+ {_id: 'foo', _type: 'bar'},
192
+ ['projected', {key: 'fooKey', index: -1}, {key: 'im_a_key', index: 0}, '_key'],
193
+ ],
194
+ [
195
+ 'that',
196
+ {_id: 'foo', _type: 'bar'},
197
+ ['projected', {key: 'fooKey', index: -1}, {key: 'im_a_key', index: 0}, 'value'],
198
+ ],
199
+ ],
200
+ },
201
+ },
202
+ ]
203
+
204
+ test.each(encodeTestCases)('encode $name', ({queryResult, options, expected}) => {
205
+ const mockTranscoder = vi.fn().mockImplementation((input: string) => input)
206
+ encode(queryResult.result, queryResult.resultSourceMap, mockTranscoder, options)
207
+
208
+ expect(mockTranscoder).toBeCalledTimes(expected.encoderCalls)
209
+ for (const args of expected.encoderArgs) {
210
+ expect(mockTranscoder).toBeCalledWith(...args)
211
+ }
212
+ })