@sanity/client 7.2.2 → 7.4.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 +126 -1
- package/dist/_chunks-cjs/isRecord.cjs +6 -0
- package/dist/_chunks-cjs/isRecord.cjs.map +1 -0
- package/dist/_chunks-cjs/resolveEditInfo.cjs +3 -5
- package/dist/_chunks-cjs/resolveEditInfo.cjs.map +1 -1
- package/dist/_chunks-cjs/stegaClean.cjs +4 -0
- package/dist/_chunks-cjs/stegaClean.cjs.map +1 -1
- package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs +2 -5
- package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs.map +1 -1
- package/dist/_chunks-es/isRecord.js +7 -0
- package/dist/_chunks-es/isRecord.js.map +1 -0
- package/dist/_chunks-es/resolveEditInfo.js +1 -3
- package/dist/_chunks-es/resolveEditInfo.js.map +1 -1
- package/dist/_chunks-es/stegaClean.js +4 -0
- package/dist/_chunks-es/stegaClean.js.map +1 -1
- package/dist/_chunks-es/stegaEncodeSourceMap.js +1 -4
- package/dist/_chunks-es/stegaEncodeSourceMap.js.map +1 -1
- package/dist/index.browser.cjs +158 -33
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.d.cts +485 -68
- package/dist/index.browser.d.ts +485 -68
- package/dist/index.browser.js +159 -34
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +160 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +485 -68
- package/dist/index.d.ts +485 -68
- package/dist/index.js +160 -34
- package/dist/index.js.map +1 -1
- package/dist/stega.browser.d.cts +485 -68
- package/dist/stega.browser.d.ts +485 -68
- package/dist/stega.d.cts +485 -68
- package/dist/stega.d.ts +485 -68
- package/package.json +1 -1
- package/src/agent/actions/AgentActionsClient.ts +29 -2
- package/src/agent/actions/commonTypes.ts +57 -17
- package/src/agent/actions/generate.ts +36 -2
- package/src/agent/actions/patch.ts +136 -0
- package/src/agent/actions/prompt.ts +145 -0
- package/src/agent/actions/transform.ts +27 -4
- package/src/agent/actions/translate.ts +5 -2
- package/src/csm/walkMap.ts +1 -1
- package/src/data/eventsource.ts +16 -7
- package/src/data/listen.ts +10 -4
- package/src/data/live.ts +13 -5
- package/src/defineCreateClient.ts +7 -1
- package/src/http/errors.ts +92 -27
- package/src/http/request.ts +3 -3
- package/src/http/requestOptions.ts +4 -0
- package/src/types.ts +39 -10
- package/src/util/codeFrame.ts +174 -0
- package/src/{csm → util}/isRecord.ts +1 -1
- package/umd/sanityClient.js +161 -36
- package/umd/sanityClient.min.js +2 -2
package/src/data/eventsource.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {defer, isObservable, mergeMap, Observable, of} from 'rxjs'
|
|
2
2
|
|
|
3
|
+
import {formatQueryParseError, isQueryParseError} from '../http/errors'
|
|
3
4
|
import {type Any} from '../types'
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -169,8 +170,10 @@ function connectWithESInstance<EventTypeName extends string>(
|
|
|
169
170
|
}
|
|
170
171
|
if (message.type === 'channelError') {
|
|
171
172
|
// An error occurred. This is different from a network-level error (which will be emitted as 'error').
|
|
172
|
-
// Possible causes are things such as malformed filters, non-existant datasets
|
|
173
|
-
|
|
173
|
+
// Possible causes are things such as malformed filters, non-existant datasets
|
|
174
|
+
// or similar.
|
|
175
|
+
const tag = new URL(es.url).searchParams.get('tag')
|
|
176
|
+
observer.error(new ChannelError(extractErrorMessage(event?.data, tag), event.data))
|
|
174
177
|
return
|
|
175
178
|
}
|
|
176
179
|
if (message.type === 'disconnect') {
|
|
@@ -235,16 +238,22 @@ function parseEvent(
|
|
|
235
238
|
}
|
|
236
239
|
}
|
|
237
240
|
|
|
238
|
-
function extractErrorMessage(err: Any) {
|
|
239
|
-
|
|
241
|
+
function extractErrorMessage(err: Any, tag?: string | null) {
|
|
242
|
+
const error = err.error
|
|
243
|
+
|
|
244
|
+
if (!error) {
|
|
240
245
|
return err.message || 'Unknown listener error'
|
|
241
246
|
}
|
|
242
247
|
|
|
243
|
-
if (
|
|
244
|
-
return
|
|
248
|
+
if (isQueryParseError(error)) {
|
|
249
|
+
return formatQueryParseError(error, tag)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (error.description) {
|
|
253
|
+
return error.description
|
|
245
254
|
}
|
|
246
255
|
|
|
247
|
-
return typeof
|
|
256
|
+
return typeof error === 'string' ? error : JSON.stringify(error, null, 2)
|
|
248
257
|
}
|
|
249
258
|
|
|
250
259
|
function isEmptyObject(data: object) {
|
package/src/data/listen.ts
CHANGED
|
@@ -70,7 +70,7 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
|
|
|
70
70
|
params?: ListenParams,
|
|
71
71
|
opts: ListenOptions = {},
|
|
72
72
|
): Observable<MutationEvent<R> | ListenEvent<R>> {
|
|
73
|
-
const {url, token, withCredentials, requestTagPrefix} = this.config()
|
|
73
|
+
const {url, token, withCredentials, requestTagPrefix, headers: configHeaders} = this.config()
|
|
74
74
|
const tag = opts.tag && requestTagPrefix ? [requestTagPrefix, opts.tag].join('.') : opts.tag
|
|
75
75
|
const options = {...defaults(opts, defaultOptions), tag}
|
|
76
76
|
const listenOpts = pick(options, possibleOptions)
|
|
@@ -88,9 +88,15 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
|
|
|
88
88
|
esOptions.withCredentials = true
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
if (token) {
|
|
92
|
-
esOptions.headers = {
|
|
93
|
-
|
|
91
|
+
if (token || configHeaders) {
|
|
92
|
+
esOptions.headers = {}
|
|
93
|
+
|
|
94
|
+
if (token) {
|
|
95
|
+
esOptions.headers.Authorization = `Bearer ${token}`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (configHeaders) {
|
|
99
|
+
Object.assign(esOptions.headers, configHeaders)
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
package/src/data/live.ts
CHANGED
|
@@ -52,6 +52,7 @@ export class LiveClient {
|
|
|
52
52
|
token,
|
|
53
53
|
withCredentials,
|
|
54
54
|
requestTagPrefix,
|
|
55
|
+
headers: configHeaders,
|
|
55
56
|
} = this.#client.config()
|
|
56
57
|
const apiVersion = _apiVersion.replace(/^v/, '')
|
|
57
58
|
if (apiVersion !== 'X' && apiVersion < requiredApiVersion) {
|
|
@@ -76,15 +77,22 @@ export class LiveClient {
|
|
|
76
77
|
url.searchParams.set('includeDrafts', 'true')
|
|
77
78
|
}
|
|
78
79
|
const esOptions: EventSourceInit & {headers?: Record<string, string>} = {}
|
|
79
|
-
if (includeDrafts && token) {
|
|
80
|
-
esOptions.headers = {
|
|
81
|
-
Authorization: `Bearer ${token}`,
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
80
|
if (includeDrafts && withCredentials) {
|
|
85
81
|
esOptions.withCredentials = true
|
|
86
82
|
}
|
|
87
83
|
|
|
84
|
+
if ((includeDrafts && token) || configHeaders) {
|
|
85
|
+
esOptions.headers = {}
|
|
86
|
+
|
|
87
|
+
if (includeDrafts && token) {
|
|
88
|
+
esOptions.headers.Authorization = `Bearer ${token}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (configHeaders) {
|
|
92
|
+
Object.assign(esOptions.headers, configHeaders)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
88
96
|
const key = `${url.href}::${JSON.stringify(esOptions)}`
|
|
89
97
|
const existing = eventsCache.get(key)
|
|
90
98
|
|
|
@@ -17,7 +17,13 @@ export {
|
|
|
17
17
|
} from './data/eventsource'
|
|
18
18
|
export * from './data/patch'
|
|
19
19
|
export * from './data/transaction'
|
|
20
|
-
export {
|
|
20
|
+
export {
|
|
21
|
+
ClientError,
|
|
22
|
+
CorsOriginError,
|
|
23
|
+
formatQueryParseError,
|
|
24
|
+
isQueryParseError,
|
|
25
|
+
ServerError,
|
|
26
|
+
} from './http/errors'
|
|
21
27
|
export * from './SanityClient'
|
|
22
28
|
export * from './types'
|
|
23
29
|
|
package/src/http/errors.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {HttpContext} from 'get-it'
|
|
2
|
+
|
|
3
|
+
import type {ActionError, Any, ErrorProps, MutationError, QueryParseError} from '../types'
|
|
4
|
+
import {codeFrame} from '../util/codeFrame'
|
|
5
|
+
import {isRecord} from '../util/isRecord'
|
|
2
6
|
|
|
3
7
|
const MAX_ITEMS_IN_ERROR_MESSAGE = 5
|
|
4
8
|
|
|
@@ -9,8 +13,8 @@ export class ClientError extends Error {
|
|
|
9
13
|
responseBody: ErrorProps['responseBody']
|
|
10
14
|
details: ErrorProps['details']
|
|
11
15
|
|
|
12
|
-
constructor(res: Any) {
|
|
13
|
-
const props = extractErrorProps(res)
|
|
16
|
+
constructor(res: Any, context?: HttpContext) {
|
|
17
|
+
const props = extractErrorProps(res, context)
|
|
14
18
|
super(props.message)
|
|
15
19
|
Object.assign(this, props)
|
|
16
20
|
}
|
|
@@ -30,7 +34,7 @@ export class ServerError extends Error {
|
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
function extractErrorProps(res: Any): ErrorProps {
|
|
37
|
+
function extractErrorProps(res: Any, context?: HttpContext): ErrorProps {
|
|
34
38
|
const body = res.body
|
|
35
39
|
const props = {
|
|
36
40
|
response: res,
|
|
@@ -40,15 +44,35 @@ function extractErrorProps(res: Any): ErrorProps {
|
|
|
40
44
|
details: undefined as Any,
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
// Fall back early if we didn't get a JSON object returned as expected
|
|
48
|
+
if (!isRecord(body)) {
|
|
49
|
+
props.message = httpErrorMessage(res, body)
|
|
50
|
+
return props
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const error = body.error
|
|
54
|
+
|
|
43
55
|
// API/Boom style errors ({statusCode, error, message})
|
|
44
|
-
if (
|
|
45
|
-
props.message = `${
|
|
56
|
+
if (typeof error === 'string' && typeof body.message === 'string') {
|
|
57
|
+
props.message = `${error} - ${body.message}`
|
|
58
|
+
return props
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Content Lake errors with a `error` prop being an object
|
|
62
|
+
if (typeof error !== 'object' || error === null) {
|
|
63
|
+
if (typeof error === 'string') {
|
|
64
|
+
props.message = error
|
|
65
|
+
} else if (typeof body.message === 'string') {
|
|
66
|
+
props.message = body.message
|
|
67
|
+
} else {
|
|
68
|
+
props.message = httpErrorMessage(res, body)
|
|
69
|
+
}
|
|
46
70
|
return props
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
// Mutation errors (specifically)
|
|
50
|
-
if (isMutationError(
|
|
51
|
-
const allItems =
|
|
74
|
+
if (isMutationError(error) || isActionError(error)) {
|
|
75
|
+
const allItems = error.items || []
|
|
52
76
|
const items = allItems
|
|
53
77
|
.slice(0, MAX_ITEMS_IN_ERROR_MESSAGE)
|
|
54
78
|
.map((item) => item.error?.description)
|
|
@@ -57,48 +81,85 @@ function extractErrorProps(res: Any): ErrorProps {
|
|
|
57
81
|
if (allItems.length > MAX_ITEMS_IN_ERROR_MESSAGE) {
|
|
58
82
|
itemsStr += `\n...and ${allItems.length - MAX_ITEMS_IN_ERROR_MESSAGE} more`
|
|
59
83
|
}
|
|
60
|
-
props.message = `${
|
|
84
|
+
props.message = `${error.description}${itemsStr}`
|
|
61
85
|
props.details = body.error
|
|
62
86
|
return props
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
// Query
|
|
66
|
-
if (
|
|
67
|
-
|
|
89
|
+
// Query parse errors
|
|
90
|
+
if (isQueryParseError(error)) {
|
|
91
|
+
const tag = context?.options?.query?.tag
|
|
92
|
+
props.message = formatQueryParseError(error, tag)
|
|
68
93
|
props.details = body.error
|
|
69
94
|
return props
|
|
70
95
|
}
|
|
71
96
|
|
|
97
|
+
if ('description' in error && typeof error.description === 'string') {
|
|
98
|
+
// Query/database errors ({error: {description, other, arb, props}})
|
|
99
|
+
props.message = error.description
|
|
100
|
+
props.details = error
|
|
101
|
+
return props
|
|
102
|
+
}
|
|
103
|
+
|
|
72
104
|
// Other, more arbitrary errors
|
|
73
|
-
props.message =
|
|
105
|
+
props.message = httpErrorMessage(res, body)
|
|
74
106
|
return props
|
|
75
107
|
}
|
|
76
108
|
|
|
77
|
-
function isMutationError(
|
|
109
|
+
function isMutationError(error: object): error is MutationError {
|
|
110
|
+
return (
|
|
111
|
+
'type' in error &&
|
|
112
|
+
error.type === 'mutationError' &&
|
|
113
|
+
'description' in error &&
|
|
114
|
+
typeof error.description === 'string'
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isActionError(error: object): error is ActionError {
|
|
78
119
|
return (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
typeof
|
|
120
|
+
'type' in error &&
|
|
121
|
+
error.type === 'actionError' &&
|
|
122
|
+
'description' in error &&
|
|
123
|
+
typeof error.description === 'string'
|
|
83
124
|
)
|
|
84
125
|
}
|
|
85
126
|
|
|
86
|
-
|
|
127
|
+
/** @internal */
|
|
128
|
+
export function isQueryParseError(error: object): error is QueryParseError {
|
|
87
129
|
return (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
typeof
|
|
130
|
+
isRecord(error) &&
|
|
131
|
+
error.type === 'queryParseError' &&
|
|
132
|
+
typeof error.query === 'string' &&
|
|
133
|
+
typeof error.start === 'number' &&
|
|
134
|
+
typeof error.end === 'number'
|
|
92
135
|
)
|
|
93
136
|
}
|
|
94
137
|
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Formats a GROQ query parse error into a human-readable string.
|
|
140
|
+
*
|
|
141
|
+
* @param error - The error object containing details about the parse error.
|
|
142
|
+
* @param tag - An optional tag to include in the error message.
|
|
143
|
+
* @returns A formatted error message string.
|
|
144
|
+
* @public
|
|
145
|
+
*/
|
|
146
|
+
export function formatQueryParseError(error: QueryParseError, tag?: string | null) {
|
|
147
|
+
const {query, start, end, description} = error
|
|
148
|
+
|
|
149
|
+
if (!query || typeof start === 'undefined') {
|
|
150
|
+
return `GROQ query parse error: ${description}`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const withTag = tag ? `\n\nTag: ${tag}` : ''
|
|
154
|
+
const framed = codeFrame(query, {start, end}, description)
|
|
155
|
+
|
|
156
|
+
return `GROQ query parse error:\n${framed}${withTag}`
|
|
97
157
|
}
|
|
98
158
|
|
|
99
|
-
function httpErrorMessage(res: Any) {
|
|
159
|
+
function httpErrorMessage(res: Any, body: unknown) {
|
|
160
|
+
const details = typeof body === 'string' ? ` (${sliceWithEllipsis(body, 100)})` : ''
|
|
100
161
|
const statusMessage = res.statusMessage ? ` ${res.statusMessage}` : ''
|
|
101
|
-
return `${res.method}-request to ${res.url} resulted in HTTP ${res.statusCode}${statusMessage}`
|
|
162
|
+
return `${res.method}-request to ${res.url} resulted in HTTP ${res.statusCode}${statusMessage}${details}`
|
|
102
163
|
}
|
|
103
164
|
|
|
104
165
|
function stringifyBody(body: Any, res: Any) {
|
|
@@ -107,6 +168,10 @@ function stringifyBody(body: Any, res: Any) {
|
|
|
107
168
|
return isJson ? JSON.stringify(body, null, 2) : body
|
|
108
169
|
}
|
|
109
170
|
|
|
171
|
+
function sliceWithEllipsis(str: string, max: number) {
|
|
172
|
+
return str.length > max ? `${str.slice(0, max)}…` : str
|
|
173
|
+
}
|
|
174
|
+
|
|
110
175
|
/** @public */
|
|
111
176
|
export class CorsOriginError extends Error {
|
|
112
177
|
projectId: string
|
package/src/http/request.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {getIt, type Middlewares, type Requester} from 'get-it'
|
|
1
|
+
import {getIt, type HttpContext, type Middlewares, type Requester} from 'get-it'
|
|
2
2
|
import {jsonRequest, jsonResponse, observable, progress, retry} from 'get-it/middleware'
|
|
3
3
|
import {Observable} from 'rxjs'
|
|
4
4
|
|
|
@@ -6,11 +6,11 @@ import type {Any} from '../types'
|
|
|
6
6
|
import {ClientError, ServerError} from './errors'
|
|
7
7
|
|
|
8
8
|
const httpError = {
|
|
9
|
-
onResponse: (res: Any) => {
|
|
9
|
+
onResponse: (res: Any, context: HttpContext) => {
|
|
10
10
|
if (res.statusCode >= 500) {
|
|
11
11
|
throw new ServerError(res)
|
|
12
12
|
} else if (res.statusCode >= 400) {
|
|
13
|
-
throw new ClientError(res)
|
|
13
|
+
throw new ClientError(res, context)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
return res
|
|
@@ -7,6 +7,10 @@ const projectHeader = 'X-Sanity-Project-ID'
|
|
|
7
7
|
export function requestOptions(config: Any, overrides: Any = {}): Omit<RequestOptions, 'url'> {
|
|
8
8
|
const headers: Any = {}
|
|
9
9
|
|
|
10
|
+
if (config.headers) {
|
|
11
|
+
Object.assign(headers, config.headers)
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
const token = overrides.token || config.token
|
|
11
15
|
if (token) {
|
|
12
16
|
headers.Authorization = `Bearer ${token}`
|
package/src/types.ts
CHANGED
|
@@ -102,6 +102,14 @@ export interface ClientConfig {
|
|
|
102
102
|
* Optional request tag prefix for all request tags
|
|
103
103
|
*/
|
|
104
104
|
requestTagPrefix?: string
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Optional default headers to include with all requests
|
|
108
|
+
*
|
|
109
|
+
* @remarks request-specific headers will override any default headers with the same name.
|
|
110
|
+
*/
|
|
111
|
+
headers?: Record<string, string>
|
|
112
|
+
|
|
105
113
|
ignoreBrowserTokenWarning?: boolean
|
|
106
114
|
withCredentials?: boolean
|
|
107
115
|
allowReconfigure?: boolean
|
|
@@ -173,6 +181,12 @@ export interface InitializedClientConfig extends ClientConfig {
|
|
|
173
181
|
* The fully initialized stega config, can be used to check if stega is enabled
|
|
174
182
|
*/
|
|
175
183
|
stega: InitializedStegaConfig
|
|
184
|
+
/**
|
|
185
|
+
* Default headers to include with all requests
|
|
186
|
+
*
|
|
187
|
+
* @remarks request-specific headers will override any default headers with the same name.
|
|
188
|
+
*/
|
|
189
|
+
headers?: Record<string, string>
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
/** @public */
|
|
@@ -1359,11 +1373,26 @@ export interface ApiError {
|
|
|
1359
1373
|
|
|
1360
1374
|
/** @internal */
|
|
1361
1375
|
export interface MutationError {
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1376
|
+
type: 'mutationError'
|
|
1377
|
+
description: string
|
|
1378
|
+
items?: MutationErrorItem[]
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Returned from the Content Lake API when a query is malformed, usually with a start
|
|
1383
|
+
* and end column to indicate where the error occurred, but not always. Can we used to
|
|
1384
|
+
* provide a more structured error message to the user.
|
|
1385
|
+
*
|
|
1386
|
+
* This will be located under the response `error` property.
|
|
1387
|
+
*
|
|
1388
|
+
* @public
|
|
1389
|
+
*/
|
|
1390
|
+
export interface QueryParseError {
|
|
1391
|
+
type: 'queryParseError'
|
|
1392
|
+
description: string
|
|
1393
|
+
start?: number
|
|
1394
|
+
end?: number
|
|
1395
|
+
query?: string
|
|
1367
1396
|
}
|
|
1368
1397
|
|
|
1369
1398
|
/** @internal */
|
|
@@ -1377,11 +1406,9 @@ export interface MutationErrorItem {
|
|
|
1377
1406
|
|
|
1378
1407
|
/** @internal */
|
|
1379
1408
|
export interface ActionError {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
items?: ActionErrorItem[]
|
|
1384
|
-
}
|
|
1409
|
+
type: 'actionError'
|
|
1410
|
+
description: string
|
|
1411
|
+
items?: ActionErrorItem[]
|
|
1385
1412
|
}
|
|
1386
1413
|
|
|
1387
1414
|
/** @internal */
|
|
@@ -1605,6 +1632,8 @@ export type {
|
|
|
1605
1632
|
GenerateTargetDocument,
|
|
1606
1633
|
GenerateTargetInclude,
|
|
1607
1634
|
} from './agent/actions/generate'
|
|
1635
|
+
export type {PatchDocument, PatchOperation, PatchTarget} from './agent/actions/patch'
|
|
1636
|
+
export type {PromptRequest} from './agent/actions/prompt'
|
|
1608
1637
|
export type {
|
|
1609
1638
|
TransformDocument,
|
|
1610
1639
|
TransformTarget,
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inlined, modified version of the `codeFrameColumns` function from `@babel/code-frame`.
|
|
3
|
+
* MIT-licensed - https://github.com/babel/babel/blob/main/LICENSE
|
|
4
|
+
* Copyright (c) 2014-present Sebastian McKenzie and other contributors.
|
|
5
|
+
*/
|
|
6
|
+
type Location = {
|
|
7
|
+
column: number
|
|
8
|
+
line: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type NodeLocation = {
|
|
12
|
+
start: Location
|
|
13
|
+
end?: Location
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type GroqLocation = {
|
|
17
|
+
start: number
|
|
18
|
+
end?: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* RegExp to test for newlines.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract what lines should be marked and highlighted.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
type MarkerLines = Record<number, true | [number, number]>
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Highlight a code frame with the given location and message.
|
|
35
|
+
*
|
|
36
|
+
* @param query - The query to be highlighted.
|
|
37
|
+
* @param location - The location of the error in the code/query.
|
|
38
|
+
* @param message - Message to be displayed inline (if possible) next to the highlighted
|
|
39
|
+
* location in the code. If it can't be positioned inline, it will be placed above the
|
|
40
|
+
* code frame.
|
|
41
|
+
* @returns The highlighted code frame.
|
|
42
|
+
*/
|
|
43
|
+
export function codeFrame(query: string, location: GroqLocation, message?: string): string {
|
|
44
|
+
const lines = query.split(NEWLINE)
|
|
45
|
+
const loc = {
|
|
46
|
+
start: columnToLine(location.start, lines),
|
|
47
|
+
end: location.end ? columnToLine(location.end, lines) : undefined,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const {start, end, markerLines} = getMarkerLines(loc, lines)
|
|
51
|
+
|
|
52
|
+
const numberMaxWidth = `${end}`.length
|
|
53
|
+
|
|
54
|
+
return query
|
|
55
|
+
.split(NEWLINE, end)
|
|
56
|
+
.slice(start, end)
|
|
57
|
+
.map((line, index) => {
|
|
58
|
+
const number = start + 1 + index
|
|
59
|
+
const paddedNumber = ` ${number}`.slice(-numberMaxWidth)
|
|
60
|
+
const gutter = ` ${paddedNumber} |`
|
|
61
|
+
const hasMarker = markerLines[number]
|
|
62
|
+
const lastMarkerLine = !markerLines[number + 1]
|
|
63
|
+
if (!hasMarker) {
|
|
64
|
+
return ` ${gutter}${line.length > 0 ? ` ${line}` : ''}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let markerLine = ''
|
|
68
|
+
if (Array.isArray(hasMarker)) {
|
|
69
|
+
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, ' ')
|
|
70
|
+
const numberOfMarkers = hasMarker[1] || 1
|
|
71
|
+
|
|
72
|
+
markerLine = [
|
|
73
|
+
'\n ',
|
|
74
|
+
gutter.replace(/\d/g, ' '),
|
|
75
|
+
' ',
|
|
76
|
+
markerSpacing,
|
|
77
|
+
'^'.repeat(numberOfMarkers),
|
|
78
|
+
].join('')
|
|
79
|
+
|
|
80
|
+
if (lastMarkerLine && message) {
|
|
81
|
+
markerLine += ' ' + message
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return ['>', gutter, line.length > 0 ? ` ${line}` : '', markerLine].join('')
|
|
85
|
+
})
|
|
86
|
+
.join('\n')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getMarkerLines(
|
|
90
|
+
loc: NodeLocation,
|
|
91
|
+
source: Array<string>,
|
|
92
|
+
): {
|
|
93
|
+
start: number
|
|
94
|
+
end: number
|
|
95
|
+
markerLines: MarkerLines
|
|
96
|
+
} {
|
|
97
|
+
const startLoc: Location = {...loc.start}
|
|
98
|
+
const endLoc: Location = {...startLoc, ...loc.end}
|
|
99
|
+
const linesAbove = 2
|
|
100
|
+
const linesBelow = 3
|
|
101
|
+
const startLine = startLoc.line ?? -1
|
|
102
|
+
const startColumn = startLoc.column ?? 0
|
|
103
|
+
const endLine = endLoc.line
|
|
104
|
+
const endColumn = endLoc.column
|
|
105
|
+
|
|
106
|
+
let start = Math.max(startLine - (linesAbove + 1), 0)
|
|
107
|
+
let end = Math.min(source.length, endLine + linesBelow)
|
|
108
|
+
|
|
109
|
+
if (startLine === -1) {
|
|
110
|
+
start = 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (endLine === -1) {
|
|
114
|
+
end = source.length
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const lineDiff = endLine - startLine
|
|
118
|
+
const markerLines: MarkerLines = {}
|
|
119
|
+
|
|
120
|
+
if (lineDiff) {
|
|
121
|
+
for (let i = 0; i <= lineDiff; i++) {
|
|
122
|
+
const lineNumber = i + startLine
|
|
123
|
+
|
|
124
|
+
if (!startColumn) {
|
|
125
|
+
markerLines[lineNumber] = true
|
|
126
|
+
} else if (i === 0) {
|
|
127
|
+
const sourceLength = source[lineNumber - 1].length
|
|
128
|
+
|
|
129
|
+
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1]
|
|
130
|
+
} else if (i === lineDiff) {
|
|
131
|
+
markerLines[lineNumber] = [0, endColumn]
|
|
132
|
+
} else {
|
|
133
|
+
const sourceLength = source[lineNumber - i].length
|
|
134
|
+
|
|
135
|
+
markerLines[lineNumber] = [0, sourceLength]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
if (startColumn === endColumn) {
|
|
140
|
+
if (startColumn) {
|
|
141
|
+
markerLines[startLine] = [startColumn, 0]
|
|
142
|
+
} else {
|
|
143
|
+
markerLines[startLine] = true
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
markerLines[startLine] = [startColumn, endColumn - startColumn]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {start, end, markerLines}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function columnToLine(column: number, lines: string[]): Location {
|
|
154
|
+
let offset = 0
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < lines.length; i++) {
|
|
157
|
+
const lineLength = lines[i].length + 1 // assume '\n' after each line
|
|
158
|
+
|
|
159
|
+
if (offset + lineLength > column) {
|
|
160
|
+
return {
|
|
161
|
+
line: i + 1, // 1-based line
|
|
162
|
+
column: column - offset, // 0-based column
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
offset += lineLength
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Fallback: beyond last line
|
|
170
|
+
return {
|
|
171
|
+
line: lines.length,
|
|
172
|
+
column: lines[lines.length - 1]?.length ?? 0,
|
|
173
|
+
}
|
|
174
|
+
}
|