@sanity/client 7.3.0 → 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.
Files changed (53) hide show
  1. package/README.md +122 -1
  2. package/dist/_chunks-cjs/isRecord.cjs +6 -0
  3. package/dist/_chunks-cjs/isRecord.cjs.map +1 -0
  4. package/dist/_chunks-cjs/resolveEditInfo.cjs +3 -5
  5. package/dist/_chunks-cjs/resolveEditInfo.cjs.map +1 -1
  6. package/dist/_chunks-cjs/stegaClean.cjs +4 -0
  7. package/dist/_chunks-cjs/stegaClean.cjs.map +1 -1
  8. package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs +2 -5
  9. package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs.map +1 -1
  10. package/dist/_chunks-es/isRecord.js +7 -0
  11. package/dist/_chunks-es/isRecord.js.map +1 -0
  12. package/dist/_chunks-es/resolveEditInfo.js +1 -3
  13. package/dist/_chunks-es/resolveEditInfo.js.map +1 -1
  14. package/dist/_chunks-es/stegaClean.js +4 -0
  15. package/dist/_chunks-es/stegaClean.js.map +1 -1
  16. package/dist/_chunks-es/stegaEncodeSourceMap.js +1 -4
  17. package/dist/_chunks-es/stegaEncodeSourceMap.js.map +1 -1
  18. package/dist/index.browser.cjs +155 -32
  19. package/dist/index.browser.cjs.map +1 -1
  20. package/dist/index.browser.d.cts +473 -68
  21. package/dist/index.browser.d.ts +473 -68
  22. package/dist/index.browser.js +156 -33
  23. package/dist/index.browser.js.map +1 -1
  24. package/dist/index.cjs +157 -34
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +473 -68
  27. package/dist/index.d.ts +473 -68
  28. package/dist/index.js +157 -33
  29. package/dist/index.js.map +1 -1
  30. package/dist/stega.browser.d.cts +473 -68
  31. package/dist/stega.browser.d.ts +473 -68
  32. package/dist/stega.d.cts +473 -68
  33. package/dist/stega.d.ts +473 -68
  34. package/package.json +1 -1
  35. package/src/agent/actions/AgentActionsClient.ts +29 -2
  36. package/src/agent/actions/commonTypes.ts +57 -17
  37. package/src/agent/actions/generate.ts +36 -2
  38. package/src/agent/actions/patch.ts +136 -0
  39. package/src/agent/actions/prompt.ts +145 -0
  40. package/src/agent/actions/transform.ts +27 -4
  41. package/src/agent/actions/translate.ts +5 -2
  42. package/src/csm/walkMap.ts +1 -1
  43. package/src/data/eventsource.ts +16 -7
  44. package/src/data/listen.ts +10 -4
  45. package/src/data/live.ts +13 -5
  46. package/src/defineCreateClient.ts +7 -1
  47. package/src/http/errors.ts +92 -27
  48. package/src/http/request.ts +3 -3
  49. package/src/types.ts +25 -10
  50. package/src/util/codeFrame.ts +174 -0
  51. package/src/{csm → util}/isRecord.ts +1 -1
  52. package/umd/sanityClient.js +158 -35
  53. package/umd/sanityClient.min.js +2 -2
@@ -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 or similar.
173
- observer.error(new ChannelError(extractErrorMessage(event?.data), event.data))
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
- if (!err.error) {
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 (err.error.description) {
244
- return err.error.description
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 err.error === 'string' ? err.error : JSON.stringify(err.error, null, 2)
256
+ return typeof error === 'string' ? error : JSON.stringify(error, null, 2)
248
257
  }
249
258
 
250
259
  function isEmptyObject(data: object) {
@@ -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
- Authorization: `Bearer ${token}`,
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 {ClientError, CorsOriginError, ServerError} from './http/errors'
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
 
@@ -1,4 +1,8 @@
1
- import type {ActionError, Any, ErrorProps, MutationError} from '../types'
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 (body.error && body.message) {
45
- props.message = `${body.error} - ${body.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(body) || isActionError(body)) {
51
- const allItems = body.error.items || []
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 = `${body.error.description}${itemsStr}`
84
+ props.message = `${error.description}${itemsStr}`
61
85
  props.details = body.error
62
86
  return props
63
87
  }
64
88
 
65
- // Query/database errors ({error: {description, other, arb, props}})
66
- if (body.error && body.error.description) {
67
- props.message = body.error.description
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 = body.error || body.message || httpErrorMessage(res)
105
+ props.message = httpErrorMessage(res, body)
74
106
  return props
75
107
  }
76
108
 
77
- function isMutationError(body: Any): body is MutationError {
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
- isPlainObject(body) &&
80
- isPlainObject(body.error) &&
81
- body.error.type === 'mutationError' &&
82
- typeof body.error.description === 'string'
120
+ 'type' in error &&
121
+ error.type === 'actionError' &&
122
+ 'description' in error &&
123
+ typeof error.description === 'string'
83
124
  )
84
125
  }
85
126
 
86
- function isActionError(body: Any): body is ActionError {
127
+ /** @internal */
128
+ export function isQueryParseError(error: object): error is QueryParseError {
87
129
  return (
88
- isPlainObject(body) &&
89
- isPlainObject(body.error) &&
90
- body.error.type === 'actionError' &&
91
- typeof body.error.description === 'string'
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
- function isPlainObject(obj: Any): obj is Record<string, unknown> {
96
- return typeof obj === 'object' && obj !== null && !Array.isArray(obj)
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
@@ -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
package/src/types.ts CHANGED
@@ -1373,11 +1373,26 @@ export interface ApiError {
1373
1373
 
1374
1374
  /** @internal */
1375
1375
  export interface MutationError {
1376
- error: {
1377
- type: 'mutationError'
1378
- description: string
1379
- items?: MutationErrorItem[]
1380
- }
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
1381
1396
  }
1382
1397
 
1383
1398
  /** @internal */
@@ -1391,11 +1406,9 @@ export interface MutationErrorItem {
1391
1406
 
1392
1407
  /** @internal */
1393
1408
  export interface ActionError {
1394
- error: {
1395
- type: 'actionError'
1396
- description: string
1397
- items?: ActionErrorItem[]
1398
- }
1409
+ type: 'actionError'
1410
+ description: string
1411
+ items?: ActionErrorItem[]
1399
1412
  }
1400
1413
 
1401
1414
  /** @internal */
@@ -1619,6 +1632,8 @@ export type {
1619
1632
  GenerateTargetDocument,
1620
1633
  GenerateTargetInclude,
1621
1634
  } from './agent/actions/generate'
1635
+ export type {PatchDocument, PatchOperation, PatchTarget} from './agent/actions/patch'
1636
+ export type {PromptRequest} from './agent/actions/prompt'
1622
1637
  export type {
1623
1638
  TransformDocument,
1624
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
+ }
@@ -1,4 +1,4 @@
1
1
  /** @internal */
2
2
  export function isRecord(value: unknown): value is Record<string, unknown> {
3
- return typeof value === 'object' && value !== null
3
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
4
4
  }