Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

groq-js

Package Overview
Dependencies
Maintainers
0
Versions
72
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

groq-js - npm Package Compare versions

Comparing version 1.9.0 to 1.10.0

10

package.json
{
"name": "groq-js",
"version": "1.9.0",
"version": "1.10.0",
"keywords": [

@@ -67,8 +67,8 @@ "sanity",

"devDependencies": {
"@sanity/pkg-utils": "6.7.1",
"@sanity/pkg-utils": "6.8.14",
"@sanity/semantic-release-preset": "^4.1.7",
"@types/debug": "^4.1.12",
"@types/tap": "^15.0.11",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"@typescript-eslint/parser": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^7.9.0",
"eslint": "^8.57.0",

@@ -85,3 +85,3 @@ "eslint-config-prettier": "^9.1.0",

"tap": "^16.3.10",
"tsx": "^4.7.2",
"tsx": "^4.10.2",
"typescript": "5.4.5"

@@ -88,0 +88,0 @@ },

@@ -19,2 +19,95 @@ import type {FuncCallNode} from '../nodeTypes'

switch (`${node.namespace}.${node.name}`) {
case 'array.compact': {
const arg = walk({node: node.args[0], scope})
return mapConcrete(arg, scope, (arg) => {
if (arg.type !== 'array') {
return {type: 'null'}
}
const of = mapConcrete(arg.of, scope, (of) => of)
return {
type: 'array',
of: unionWithoutNull(of),
}
})
}
case 'array.join': {
const arrayArg = walk({node: node.args[0], scope})
const sepArg = walk({node: node.args[1], scope})
return mapConcrete(arrayArg, scope, (arrayArg) =>
mapConcrete(sepArg, scope, (sepArg) => {
if (arrayArg.type !== 'array') {
return {type: 'null'}
}
if (sepArg.type !== 'string') {
return {type: 'null'}
}
return mapConcrete(arrayArg.of, scope, (of) => {
// we can only join strings, numbers, and booleans
if (of.type !== 'string' && of.type !== 'number' && of.type !== 'boolean') {
return {type: 'unknown'}
}
return {type: 'string'}
})
}),
)
}
case 'array.unique': {
const arg = walk({node: node.args[0], scope})
return mapConcrete(arg, scope, (arg) => {
if (arg.type !== 'array') {
return {type: 'null'}
}
return arg
})
}
case 'global.lower': {
const arg = walk({node: node.args[0], scope})
return mapConcrete(arg, scope, (arg) => {
if (arg.type === 'string') {
if (arg.value !== undefined) {
return {
type: 'string',
value: arg.value.toLowerCase(),
}
}
return {type: 'string'}
}
return {type: 'null'}
})
}
case 'global.upper': {
const arg = walk({node: node.args[0], scope})
return mapConcrete(arg, scope, (arg) => {
if (arg.type === 'string') {
if (arg.value !== undefined) {
return {
type: 'string',
value: arg.value.toUpperCase(),
}
}
return {type: 'string'}
}
return {type: 'null'}
})
}
case 'dateTime.now': {
return {type: 'string'}
}
case 'global.now': {
return {type: 'string'}
}
case 'global.defined': {

@@ -62,2 +155,24 @@ return {type: 'boolean'}

case 'global.round': {
const numNode = walk({node: node.args[0], scope})
return mapConcrete(numNode, scope, (num) => {
if (num.type !== 'number') {
return {type: 'null'}
}
if (node.args.length === 2) {
const precisionNode = walk({node: node.args[1], scope})
return mapConcrete(precisionNode, scope, (precision) => {
if (precision.type !== 'number') {
return {type: 'null'}
}
return {type: 'number'}
})
}
return {type: 'number'}
})
}
case 'global.string': {

@@ -64,0 +179,0 @@ const arg = walk({node: node.args[0], scope})

import type {TypeNode} from './types'
const {compare} = new Intl.Collator('en')
function typeNodesSorter(a: TypeNode, b: TypeNode): number {
if (a.type === 'null') {
return 1
}
return compare(hashField(a), hashField(b))
}
export function hashField(field: TypeNode): string {

@@ -24,4 +32,10 @@ switch (field.type) {

case 'object': {
return `${field.type}:(${Object.entries(field.attributes)
.map(([key, value]) => `${key}:${hashField(value.value)}`)
const attributes = Object.entries(field.attributes)
attributes.sort(([a], [b]) => compare(a, b)) // sort them by name
return `${field.type}:(${attributes
.map(
([key, value]) =>
`${key}:${hashField(value.value)}(${value.optional ? 'optional' : 'non-optional'})`,
)
.join(',')}):ref-${field.dereferencesTo}:${field.rest ? hashField(field.rest) : 'no-rest'}`

@@ -31,3 +45,5 @@ }

case 'union': {
return `${field.type}(${field.of.map(hashField).join(',')})`
const sorted = [...field.of]
sorted.sort(typeNodesSorter)
return `${field.type}(${sorted.map(hashField).join(',')})`
}

@@ -50,3 +66,6 @@

for (const typeNode of typeNodes) {
const sortedTypeNodes = [...typeNodes]
sortedTypeNodes.sort(typeNodesSorter)
for (const typeNode of sortedTypeNodes) {
const hash = hashField(typeNode)

@@ -70,2 +89,6 @@ if (hash === null) {

if (field.type === 'union') {
if (field.of.length === 0) {
return field
}
field.of = removeDuplicateTypeNodes(field.of)

@@ -89,3 +112,2 @@

const compare = new Intl.Collator('en').compare
field.of.sort((a, b) => {

@@ -92,0 +114,0 @@ if (a.type === 'null') {

@@ -23,3 +23,5 @@ import debug from 'debug'

NotNode,
ObjectConditionalSplatNode,
ObjectNode,
ObjectSplatNode,
OpCallNode,

@@ -51,3 +53,3 @@ ParentNode,

} from './types'
import {mapConcrete, nullUnion} from './typeHelpers'
import {mapConcrete, nullUnion, resolveInline} from './typeHelpers'

@@ -60,3 +62,3 @@ const $trace = debug('typeEvaluator:evaluate:trace')

$debug.log = console.log.bind(console) // eslint-disable-line no-console
const $warn = debug('typeEvaluator:evaluate::warn')
const $warn = debug('typeEvaluator:evaluate:warn')

@@ -73,2 +75,3 @@ /**

$debug('evaluateQueryType.ast %O', ast)
$debug('evaluateQueryType.schema %O', schema)
const parsed = walk({

@@ -123,76 +126,336 @@ node: ast,

function mapObjectSplat(
node: TypeNode,
function handleObjectSplatNode(
attr: ObjectSplatNode | ObjectConditionalSplatNode,
scope: Scope,
mapper: (field: Document | ObjectTypeNode) => void,
) {
if (node.type === 'union') {
for (const scoped of node.of) {
mapObjectSplat(scoped, scope, mapper)
): TypeNode {
const value = walk({node: attr.value, scope})
$trace('object.splat.value %O', value)
return mapConcrete(value, scope, (node) => {
// if the node is not an object it means we are splating over a non-object, so we return unknown
if (node.type !== 'object') {
return {type: 'unknown'}
}
}
if (node.type === 'object') {
mapper(node)
}
const attributes: Record<string, ObjectAttribute> = {}
for (const name in node.attributes) {
if (!node.attributes.hasOwnProperty(name)) {
continue
}
attributes[name] = node.attributes[name]
}
if (node.rest !== undefined) {
// Rest is either an object, inline, or unknown - we need to resolve it if it's an inline
const resolvedRest = resolveInline(node.rest, scope)
// if the rest is unknown the entire object is unknown
if (resolvedRest.type === 'unknown') {
return {type: 'unknown'}
}
if (resolvedRest.type !== 'object') {
return {type: 'null'}
}
for (const name in resolvedRest.attributes) {
// eslint-disable-next-line
if (!resolvedRest.attributes.hasOwnProperty(name)) {
continue
}
attributes[name] = resolvedRest.attributes[name]
}
}
return {type: 'object', attributes}
})
}
function handleObjectNode(node: ObjectNode, scope: Scope) {
// eslint-disable-next-line max-statements, complexity
function handleObjectNode(node: ObjectNode, scope: Scope): TypeNode {
$trace('object.node %O', node)
$trace('object.scope %O', scope)
const attributes: Record<string, ObjectAttribute> = {}
for (const attr of node.attributes) {
if (node.attributes.length === 0) {
return {
type: 'object',
attributes: {},
} satisfies ObjectTypeNode
}
// let attributes we a entry of [name, value] or null. We need to keep track of nulls to handle conditional splats
// since we care about the order of the attributes. Later attribute keys will overwrite earlier ones.
const objectAttributes: [number, string, ObjectAttribute][] = []
const splatVariants: [number, ObjectTypeNode | UnionTypeNode<ObjectTypeNode>][] = []
// We keep track of conditional splats separately, since we need to merge them into an object or an union of objects at the end.
// keep track of the index of the conditional splat to be able to merge the attributes correctly.
const conditionalVariants: [number, UnionTypeNode<ObjectTypeNode>][] = []
for (const [idx, attr] of node.attributes.entries()) {
if (attr.type === 'ObjectAttributeValue') {
const field = walk({node: attr.value, scope})
attributes[attr.name] = {
type: 'objectAttribute',
value: field,
}
const attributeNode = walk({node: attr.value, scope})
objectAttributes.push([
idx,
attr.name,
{
type: 'objectAttribute',
value: attributeNode,
},
])
continue
}
if (attr.type === 'ObjectSplat') {
const value = walk({node: attr.value, scope})
$trace('object.splat.value %O', value)
mapObjectSplat(value, scope, (node) => {
for (const name in node.attributes) {
if (!Object.hasOwn(node.attributes, name)) {
continue
}
const attributeNode = handleObjectSplatNode(attr, scope)
$trace('object.splat.result %O', attributeNode)
attributes[name] = node.attributes[name]
switch (attributeNode.type) {
case 'object': {
splatVariants.push([idx, attributeNode])
continue
}
})
case 'union': {
for (const node of attributeNode.of) {
// eslint-disable-next-line max-depth
if (node.type !== 'object') {
return {type: 'unknown'}
}
}
splatVariants.push([idx, attributeNode as UnionTypeNode<ObjectTypeNode>])
continue
}
default: {
return {type: 'unknown'}
}
}
}
if (attr.type === 'ObjectConditionalSplat') {
const condition = resolveCondition(attr.condition, scope)
$trace('object.conditional.splat.condition %O', condition)
if (condition || condition === undefined) {
const value = walk({node: attr.value, scope})
// condition is never met, skip this attribute
if (condition === false) {
continue
}
mapObjectSplat(value, scope, (node) => {
for (const name in node.attributes) {
if (!Object.hasOwn(node.attributes, name)) {
const attributeNode = handleObjectSplatNode(attr, scope)
$trace('object.conditional.splat.result %O', attributeNode)
// condition is always met, we can treat this as a normal splat
if (condition === true) {
switch (attributeNode.type) {
case 'object': {
splatVariants.push([idx, attributeNode])
continue
}
case 'union': {
// eslint-disable-next-line max-depth
for (const node of attributeNode.of) {
// eslint-disable-next-line max-depth
if (node.type !== 'object') {
return {type: 'unknown'}
}
}
splatVariants.push([idx, attributeNode as UnionTypeNode<ObjectTypeNode>])
continue
}
default: {
return {type: 'unknown'}
}
}
}
const variant = mapConcrete(attributeNode, scope, (attributeNode) => {
$trace('object.conditional.splat.result.concrete %O', attributeNode)
if (attributeNode.type !== 'object') {
return {type: 'unknown'}
}
return {
type: 'object',
attributes: attributeNode.attributes,
} satisfies ObjectTypeNode
})
if (variant.type === 'union') {
for (const node of variant.of) {
// We can only splat objects, so we bail out if we encounter a non-object node.
// eslint-disable-next-line max-depth
if (node.type !== 'object') {
return {type: 'unknown'}
}
}
variant.of.push({type: 'object', attributes: {}} as ObjectTypeNode) // add an empty object to the union, since it's conditional
conditionalVariants.push([idx, variant as UnionTypeNode<ObjectTypeNode>])
continue
}
// If the variant is not an object or a union of objects, we bail out early.
if (variant.type !== 'object') {
return {type: 'unknown'}
}
conditionalVariants.push([
idx,
{
type: 'union',
of: [{type: 'object', attributes: {}}, variant],
},
])
continue
}
// @ts-expect-error - we should have handled all cases of ObjectAttributeNode
throw new Error(`Unknown object attribute type: ${attr.type}`)
}
const guaranteedAttributes: [number, string, ObjectAttribute<TypeNode>][] = []
guaranteedAttributes.push(...objectAttributes)
for (const [idx, splatNode] of splatVariants) {
if (splatNode.type === 'object') {
for (const name in splatNode.attributes) {
if (!splatNode.attributes.hasOwnProperty(name)) {
continue
}
const attribute = splatNode.attributes[name]
guaranteedAttributes.push([idx, name, attribute])
}
continue
}
// it's a union of objects, so we keep this as a conditional variant
conditionalVariants.push([idx, splatNode])
}
// make sure they are sorted from lowest index to highest, this ensures that
// attributes with a higher index overwrite attributes with a lower index.
guaranteedAttributes.sort(([a], [b]) => a - b)
// If we have no conditional variants, we can just return the object with the guaranteed attributes.
if (conditionalVariants.length === 0) {
return {
type: 'object',
attributes: Object.fromEntries(
guaranteedAttributes.map(([, name, attribute]) => [name, attribute]),
),
} satisfies ObjectTypeNode
}
// matrix should be a result of if given we have variants [a,b,c] this would lead to a union of [a, a|b, a|c, a|b|c, b|c, c, {EMPTY}]
// if it's given we have variants A + [a|b|c] this would lead to a union of [Aa, Aa|Ab, Aa|Ac, Aa|Ab|Ac, Ab|Ac, Ac, A]
const matrix: (ObjectTypeNode | UnionTypeNode<ObjectTypeNode>)[] = []
for (const [unionIdx, union] of conditionalVariants) {
const unionGuaranteedBefore: [number, string, ObjectAttribute][] = []
const unionGuaranteedAfter: [number, string, ObjectAttribute][] = []
// Collect all guaranteed attributes before and after the conditional variant.
for (const [guaranteedIndex, name, attribute] of guaranteedAttributes) {
if (guaranteedIndex < unionIdx) {
unionGuaranteedBefore.push([guaranteedIndex, name, attribute])
}
if (guaranteedIndex > unionIdx) {
unionGuaranteedAfter.push([guaranteedIndex, name, attribute])
}
}
// build a map of variants from other conditions.
const allVariantsAttributes: [number, Record<string, ObjectAttribute>[]][] = []
for (const [conditionalVariantIdx, otherUnion] of conditionalVariants) {
// We need to build a matrix of all possible combinations of the attributes of the other variants.
// start with an empty object, since it's condtional.
const variantAttributes: Record<string, ObjectAttribute>[] = []
for (const node of otherUnion.of) {
variantAttributes.push(node.attributes)
}
allVariantsAttributes.push([conditionalVariantIdx, variantAttributes])
}
/* eslint-disable max-depth */
for (const node of union.of) {
matrix.push({
type: 'object',
attributes: {
...Object.fromEntries(
unionGuaranteedBefore.map(([, name, attribute]) => [name, attribute]),
),
...node.attributes,
...Object.fromEntries(
unionGuaranteedAfter.map(([, name, attribute]) => [name, attribute]),
),
},
} satisfies ObjectTypeNode)
for (const [outerIdx, outerAttributes] of allVariantsAttributes) {
for (const outer of outerAttributes) {
for (const [innerIdx, innerAttributes] of allVariantsAttributes) {
if (outerIdx === innerIdx) {
continue
}
const attribute = node.attributes[name]
if (condition) {
attributes[name] = attribute
} else if (condition === undefined) {
attributes[name] = {
type: 'objectAttribute',
value: attribute.value,
optional: true,
for (const inner of innerAttributes) {
const _before = [...unionGuaranteedBefore]
const _after = [...unionGuaranteedAfter]
for (const name in outer) {
if (!outer.hasOwnProperty(name)) {
continue
}
if (outerIdx === unionIdx) {
continue
}
if (outerIdx < unionIdx) {
_before.push([outerIdx, name, outer[name]])
}
if (outerIdx > unionIdx) {
_after.push([outerIdx, name, outer[name]])
}
}
} else {
throw new Error('Unexpected condition')
for (const name in inner) {
if (!inner.hasOwnProperty(name)) {
continue
}
if (outerIdx === unionIdx) {
continue
}
if (innerIdx < unionIdx) {
_before.push([innerIdx, name, inner[name]])
}
if (innerIdx > unionIdx) {
_after.push([innerIdx, name, inner[name]])
}
}
_before.sort(([a], [b]) => a - b)
_after.sort(([a], [b]) => a - b)
const before: Record<string, ObjectAttribute> = Object.fromEntries(
_before.map(([, name, attribute]) => [name, attribute]),
)
const after: Record<string, ObjectAttribute> = Object.fromEntries(
_after.map(([, name, attribute]) => [name, attribute]),
)
matrix.push({
type: 'object',
attributes: {
...before,
...node.attributes,
...after,
},
})
}
}
})
}
}
}
/* eslint-disable max-depth */
}
return {
type: 'object',
attributes,
} satisfies ObjectTypeNode
return optimizeUnions({
type: 'union',
of: matrix,
})
}

@@ -202,2 +465,3 @@

function handleOpCallNode(node: OpCallNode, scope: Scope): TypeNode {
$trace('opcall.node %O', node)
const lhs = walk({node: node.left, scope})

@@ -208,3 +472,3 @@ const rhs = walk({node: node.right, scope})

mapConcrete(rhs, scope, (right) => {
$trace('opCallNode "%s" %O', node.op, {left, right})
$trace('opcall.node.concrete "%s" %O', node.op, {left, right})

@@ -380,2 +644,3 @@ switch (node.op) {

}
return {

@@ -587,10 +852,18 @@ type: 'union',

function handleParentNode({n}: ParentNode, scope: Scope): TypeNode {
let current: Scope = scope
$trace('handle.parent.currentScope %d %O', n, scope)
let current: Scope | undefined = scope
for (let i = 0; i < n; i++) {
if (!current.parent) {
return {type: 'null'} satisfies NullTypeNode
// make sure we are not in a hidden scope
while (current?.isHidden) {
current = current.parent
}
current = current.parent
current = current?.parent
}
$trace('parent.scope %d %O', n, current.value)
$trace('handle.parent.newScope %d %O', n, current)
if (!current) {
return {type: 'null'} satisfies NullTypeNode
}
if (current.value.of.length === 0) {

@@ -833,5 +1106,15 @@ return {type: 'null'} satisfies NullTypeNode

switch (expr.type) {
case 'AccessAttribute':
case 'AccessElement':
case 'Value': {
const value = walk({node: expr, scope})
return value.type === 'boolean' && value.value !== false
const value = mapConcrete(walk({node: expr, scope}), scope, (node) => node)
if (value.type === 'boolean') {
return value.value
}
if (value.type === 'null' || value.type === 'object' || value.type === 'array') {
return false
}
return undefined
}

@@ -1061,7 +1344,15 @@ case 'And': {

}
case 'Not': {
const result = resolveCondition(expr.base, scope)
// check if the result is undefined or false. Undefined means that the condition can't be resolved, and we should keep the node
return result === undefined ? undefined : result === false
}
case 'Group': {
return resolveCondition(expr.base, scope)
}
default: {
return true
return undefined
}

@@ -1068,0 +1359,0 @@ }

@@ -6,2 +6,3 @@ import {optimizeUnions} from './optimizations'

BooleanTypeNode,
InlineTypeNode,
NullTypeNode,

@@ -79,3 +80,3 @@ NumberTypeNode,

type ConcreteTypeNode =
export type ConcreteTypeNode =
| BooleanTypeNode

@@ -88,2 +89,11 @@ | NullTypeNode

export function resolveInline(node: TypeNode, scope: Scope): Exclude<TypeNode, InlineTypeNode> {
if (node.type === 'inline') {
const resolvedInline = scope.context.lookupTypeDeclaration(node)
return resolveInline(resolvedInline, scope)
}
return node
}
/**

@@ -120,3 +130,3 @@ * mapConcrete extracts a _concrete type_ from a type node, applies the mapping

case 'inline': {
const resolvedInline = scope.context.lookupTypeDeclaration(node)
const resolvedInline = resolveInline(node, scope)
return mapConcrete(resolvedInline, scope, mapper, mergeUnions)

@@ -123,0 +133,0 @@ }

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc