New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

groq-js

Package Overview
Dependencies
Maintainers
14
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 0.0.1 to 0.1.0

src/evaluator/equality.js

19

package.json
{
"name": "groq-js",
"version": "0.0.1",
"version": "0.1.0",
"main": "src/index.js",
"scripts": {
"test": "jest"
"test": "jest",
"build-api": "jsdoc2md -t utils/docs.hbs 'src/**/*.js' > API.md",
"prettify": "prettier --write src/**/*.js"
},
"prettier": {
"semi": false,
"printWidth": 100,
"bracketSpacing": false,
"singleQuote": true
},
"devDependencies": {
"jest": "^24.8.0",
"jsdoc-to-markdown": "^5.0.1",
"ndjson": "^1.5.0",
"prettier": "^1.18.2"
},
"files": ["src", "README.md"]
"files": [
"src",
"README.md"
]
}

@@ -19,3 +19,8 @@ # GROQ-JS

// Evaluate a tree against a set of documents
let result = await evaluate(tree, {documents})
let value = await evaluate(tree, {documents})
// Gather everything into one JavaScript object
let result = await value.get()
console.log(result)
```

@@ -26,4 +31,6 @@

- [Installation](#installation)
- [Documentation](#documentation)
- [Versioning](#versioning)
- [License](#license)
- [Tests](#tests)

@@ -36,14 +43,52 @@ ## Installation

# NPM
npm i git+https://git@github.com/sanity-io/groq-js.git
npm i groq-js
# Yarn
yarn add git+https://git@github.com/sanity-io/groq-js.git
yarn add groq-js
```
## Documentation
See [API.md](API.md) for the public API.
## Versioning
GROQ-JS is currently not released.
GROQ-JS follows [SemVer](https://semver.org) and is currently at version v0.1.
This is an "experimental" release and anything *may* change at any time, but we're trying to keep changes as minimal as possible:
- The public API of the parser/evaluator will most likely stay the same in future version.
- The syntax tree is *not* considered a public API and may change at any time.
- This package always implements the latest version of [GROQ according to the specification](https://github.com/sanity-io/groq).
- The goal is to release a v1.0.0 by the end of 2019.
## License
MIT © [Sanity.io](https://www.sanity.io/)
MIT © [Sanity.io](https://www.sanity.io/)
## Tests
Tests are written in [Jest](https://jestjs.io/):
```bash
# Install dependencies
yarn
# Run tests
yarn test
```
You can also generate tests from [the official GROQ test suite](https://github.com/sanity-io/groq-test-suite):
```bash
# Clone the repo somewhere:
git clone https://github.com/sanity-io/groq-test-suite somewhere
# Install dependencies:
(cd somewhere && yarn)
# Generate test file (in this repo):
./test/generate.sh somewhere
# Run tests as usual:
yarn test
```

@@ -1,23 +0,198 @@

const Value = require('./value')
const {StaticValue, getType, fromNumber, TRUE_VALUE, FALSE_VALUE, NULL_VALUE} = require('./value')
const {totalCompare} = require('./ordering')
exports.count = function count(args, scope, execute) {
if (args.length !== 1) throw new Error("count: 1 argument required")
const functions = (exports.functions = {})
const pipeFunctions = (exports.pipeFunctions = {})
return new Value(async () => {
functions.coalesce = async function coalesce(args, scope, execute) {
for (let arg of args) {
let value = await execute(arg, scope)
if (value.getType() != 'null') return value
}
return NULL_VALUE
}
functions.count = async function count(args, scope, execute) {
if (args.length !== 1) return NULL_VALUE
let inner = await execute(args[0], scope)
if (inner.getType() != 'array') return NULL_VALUE
let num = 0
for await (let _ of inner) {
num++
}
return new StaticValue(num)
}
functions.defined = async function defined(args, scope, execute) {
if (args.length !== 1) return NULL_VALUE
let inner = await execute(args[0], scope)
return inner.getType() == 'null' ? FALSE_VALUE : TRUE_VALUE
}
functions.identity = async function identity(args, scope, execute) {
if (args.length !== 0) return NULL_VALUE
return new StaticValue('me')
}
function countUTF8(str) {
let count = 0
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i)
if (code >= 0xd800 && code <= 0xdbff) {
// High surrogate. Don't count this.
// By only counting the low surrogate we will correctly
// count the number of UTF-8 code points.
continue
}
count++
}
return count
}
functions.length = async function length(args, scope, execute) {
if (args.length !== 1) return NULL_VALUE
let inner = await execute(args[0], scope)
if (inner.getType() == 'string') {
let data = await inner.get()
return fromNumber(countUTF8(data))
}
if (inner.getType() == 'array') {
let num = 0
let inner = execute(args[0], scope)
for await (let _ of inner) {
num++
}
return num
})
return fromNumber(num)
}
return NULL_VALUE
}
exports.defined = function defined(args, scope, execute) {
if (args.length !== 1) throw new Error("defined: 1 argument required")
functions.select = async function select(args, scope, execute) {
// First check if everything is valid:
let seenFallback = false
for (let arg of args) {
if (seenFallback) return NULL_VALUE
return new Value(async () => {
let inner = await execute(args[0], scope).get()
return inner != null
if (arg.type == 'Pair') {
// This is fine.
} else {
seenFallback = true
}
}
for (let arg of args) {
if (arg.type == 'Pair') {
let cond = await execute(arg.left, scope)
if (cond.getBoolean()) {
return await execute(arg.right, scope)
}
} else {
return await execute(arg, scope)
}
}
return NULL_VALUE
}
function hasReference(value, id) {
switch (getType(value)) {
case 'array':
for (let v of value) {
if (hasReference(v, id)) return true
}
break
case 'object':
if (value._ref === id) return true
for (let v of Object.values(value)) {
if (hasReference(v, id)) return true
}
break
}
return false
}
functions.references = async function references(args, scope, execute) {
if (args.length != 1) return NULL_VALUE
let idValue = await execute(args[0], scope)
if (idValue.getType() != 'string') return NULL_VALUE
let id = await idValue.get()
let scopeValue = scope.value
return hasReference(scopeValue, id) ? TRUE_VALUE : FALSE_VALUE
}
functions.round = async function round(args, scope, execute) {
if (args.length < 1 || args.length > 2) return NULL_VALUE
let value = await execute(args[0], scope)
if (value.getType() != 'number') return NULL_VALUE
let num = await value.get()
let prec = 0
if (args.length == 2) {
let precValue = await execute(args[1], scope)
if (precValue.getType() != 'number') return NULL_VALUE
prec = await precValue.get()
}
if (prec == 0) {
return fromNumber(Math.round(num))
} else {
return fromNumber(Number(num.toFixed(prec)))
}
}
pipeFunctions.order = async function order(base, args, scope, execute) {
if (args.length == 0) throw new Error('order: at least one argument required')
if (base.getType() != 'array') return NULL_VALUE
let mappers = []
let directions = []
let n = 0
for (let mapper of args) {
let direction = 'asc'
if (mapper.type == 'Desc') {
direction = 'desc'
mapper = mapper.base
} else if (mapper.type == 'Asc') {
mapper = mapper.base
}
mappers.push(mapper)
directions.push(direction)
n++
}
let aux = []
for await (let value of base) {
let newScope = scope.createNested(value)
let tuple = [await value.get()]
for (let i = 0; i < n; i++) {
let result = await execute(mappers[i], newScope)
tuple.push(await result.get())
}
aux.push(tuple)
}
aux.sort((aTuple, bTuple) => {
for (let i = 0; i < n; i++) {
let c = totalCompare(aTuple[i + 1], bTuple[i + 1])
if (directions[i] == 'desc') c = -c
if (c != 0) return c
}
return 0
})
}
return new StaticValue(aux.map(v => v[0]))
}

@@ -1,5 +0,29 @@

const Value = require('./value')
const functions = require('./functions')
const {
StaticValue,
StreamValue,
MapperValue,
NULL_VALUE,
TRUE_VALUE,
FALSE_VALUE,
Range,
Pair,
fromNumber
} = require('./value')
const {functions, pipeFunctions} = require('./functions')
const operators = require('./operators')
function inMapper(value, fn) {
if (value instanceof MapperValue) {
return new MapperValue(
new StreamValue(async function*() {
for await (let elementValue of value) {
yield await fn(elementValue)
}
})
)
} else {
return fn(value)
}
}
class Scope {

@@ -26,11 +50,15 @@ constructor(params, source, value, parent) {

This(_, scope) {
return new Value(scope.value)
return scope.value
},
Star(_, scope) {
return scope.source.createSink()
return scope.source
},
Parent(_, scope) {
return new Value(scope.parent && scope.parent.value)
Parent({n}, scope) {
for (let i = 0; i < n; i++) {
scope = scope.parent
if (!scope) return NULL_VALUE
}
return scope.value
},

@@ -40,3 +68,3 @@

let func = operators[op]
if (!func) throw new Error("Unknown operator: " + op)
if (!func) throw new Error('Unknown operator: ' + op)
return func(left, right, scope, execute)

@@ -47,29 +75,51 @@ },

let func = functions[name]
if (!func) throw new Error("Unknown function: " + name)
if (!func) throw new Error('Unknown function: ' + name)
return func(args, scope, execute)
},
Filter({base, query}, scope) {
return new Value(async function*() {
let b = execute(base, scope)
for await (let value of b) {
let newScope = scope.createNested(value)
let didMatch = await execute(query, newScope).get()
if (didMatch) yield value
}
})
async PipeFuncCall({base, name, args}, scope) {
let func = pipeFunctions[name]
if (!func) throw new Error('Unknown function: ' + name)
let baseValue = await execute(base, scope)
return func(baseValue, args, scope, execute)
},
Identifier({name}, scope) {
return new Value(name in scope.value ? scope.value[name] : null)
async Filter({base, query}, scope) {
let baseValue = await execute(base, scope)
return inMapper(baseValue, async value => {
if (value.getType() != 'array') return NULL_VALUE
return new StreamValue(async function*() {
for await (let element of value) {
let newScope = scope.createNested(element)
let condValue = await execute(query, newScope)
if (condValue.getBoolean()) yield element
}
})
})
},
GetIdentifier({base, name}, scope) {
return new Value(async () => {
let obj = await execute(base, scope).get()
async Element({base, index}, scope) {
let baseValue = await execute(base, scope)
if (obj && typeof obj === 'object') {
return obj[name]
return inMapper(baseValue, async arrayValue => {
if (arrayValue.getType() != 'array') return NULL_VALUE
let idxValue = await execute(index, scope)
if (idxValue.getType() != 'number') return NULL_VALUE
// OPT: Here we can optimize when idx >= 0
let array = await arrayValue.get()
let idx = await idxValue.get()
if (idx < 0) {
idx = array.length + idx
}
if (idx >= 0 && idx < array.length) {
return new StaticValue(array[idx])
} else {
return null
// Make sure we return `null` for out-of-bounds access
return NULL_VALUE
}

@@ -79,68 +129,192 @@ })

Value({value}) {
return new Value(value)
async Slice({base, left, right, isExclusive}, scope) {
let baseValue = await execute(base, scope)
return inMapper(baseValue, async arrayValue => {
if (arrayValue.getType() != 'array') return NULL_VALUE
let leftIdxValue = await execute(left, scope)
let rightIdxValue = await execute(right, scope)
if (leftIdxValue.getType() != 'number' || rightIdxValue.getType() != 'number') {
return NULL_VALUE
}
// OPT: Here we can optimize when either indices are >= 0
let array = await arrayValue.get()
let leftIdx = await leftIdxValue.get()
let rightIdx = await rightIdxValue.get()
// Handle negative index
if (leftIdx < 0) leftIdx = array.length + leftIdx
if (rightIdx < 0) rightIdx = array.length + rightIdx
// Convert from inclusive to exclusive index
if (!isExclusive) rightIdx++
if (leftIdx < 0) leftIdx = 0
if (rightIdx < 0) rightIdx = 0
// Note: At this point the indices might point out-of-bound, but
// .slice handles this correctly.
return new StaticValue(array.slice(leftIdx, rightIdx))
})
},
Project({base, query}, scope) {
let b = execute(base, scope)
return new Value(async function*() {
for await (let data of b) {
let newScope = scope.createNested(data)
let newData = await execute(query, newScope).get()
yield newData
async Attribute({base, name}, scope) {
let baseValue = await execute(base, scope)
return inMapper(baseValue, async value => {
if (value.getType() == 'object') {
let data = await value.get()
if (data.hasOwnProperty(name)) {
return new StaticValue(data[name])
}
}
return NULL_VALUE
})
},
ArrProject({base, query}, scope) {
let b = execute(base, scope)
return new Value(async function*() {
for await (let data of b) {
let newScope = scope.createNested(data)
let newData = await execute(query, newScope).get()
yield newData
async Identifier({name}, scope) {
if (scope.value.getType() == 'object') {
let data = await scope.value.get()
if (data.hasOwnProperty(name)) {
return new StaticValue(data[name])
}
}
return NULL_VALUE
},
Value({value}) {
return new StaticValue(value)
},
async Mapper({base}, scope) {
let baseValue = await execute(base, scope)
if (baseValue.getType() != 'array') return baseValue
if (baseValue instanceof MapperValue) {
return new MapperValue(
new StreamValue(async function*() {
for await (let element of baseValue) {
if (element.getType() == 'array') {
for await (let subelement of element) {
yield subelement
}
} else {
yield NULL_VALUE
}
}
})
)
} else {
return new MapperValue(baseValue)
}
},
async Parenthesis({base}, scope) {
let baseValue = await execute(base, scope)
if (baseValue instanceof MapperValue) {
baseValue = baseValue.value
}
return baseValue
},
async Projection({base, query}, scope) {
let baseValue = await execute(base, scope)
return inMapper(baseValue, async baseValue => {
if (baseValue.getType() == 'null') return NULL_VALUE
if (baseValue.getType() == 'array') {
return new StreamValue(async function*() {
for await (let value of baseValue) {
let newScope = scope.createNested(value)
let newValue = await execute(query, newScope)
yield newValue
}
})
} else {
let newScope = scope.createNested(baseValue)
return await execute(query, newScope)
}
})
},
Deref({base}, scope) {
return new Value(async function() {
let ref = await execute(base, scope).get()
if (!ref) return
async Deref({base}, scope) {
let baseValue = await execute(base, scope)
return inMapper(baseValue, async baseValue => {
if (baseValue.getType() != 'object') return NULL_VALUE
for await (let doc of scope.source.createSink()) {
if (typeof doc._id === 'string' && ref._ref === doc._id) {
let id = (await baseValue.get())._ref
if (typeof id != 'string') return NULL_VALUE
for await (let doc of scope.source) {
if (id === doc.data._id) {
return doc
}
}
return NULL_VALUE
})
},
Object({properties}, scope) {
return new Value(async () => {
let result = {}
for (let prop of properties) {
switch (prop.type) {
case 'ObjectSplat':
Object.assign(result, scope.value)
break
async Object({attributes}, scope) {
let result = {}
for (let attr of attributes) {
switch (attr.type) {
case 'ObjectAttribute': {
let key = await execute(attr.key, scope)
if (key.getType() != 'string') continue
case 'Property':
let key = await execute(prop.key, scope).get()
let value = await execute(prop.value, scope).get()
result[key] = value
let value = await execute(attr.value, scope)
if (value.getType() == 'null') {
delete result[key.data]
break
}
default:
throw new Error("Unknown node type: " + prop.type)
result[key.data] = await value.get()
break
}
case 'ObjectConditionalSplat': {
let cond = await execute(attr.condition, scope)
if (!cond.getBoolean()) continue
let value = await execute(attr.value, scope)
if (value.getType() != 'object') continue
Object.assign(result, value.data)
break
}
case 'ObjectSplat': {
let value = await execute(attr.value, scope)
if (value.getType('object')) {
Object.assign(result, value.data)
}
break
}
default:
throw new Error('Unknown node type: ' + attr.type)
}
return result
})
}
return new StaticValue(result)
},
Array({elements}, scope) {
return new Value(async function*() {
return new StreamValue(async function*() {
for (let element of elements) {
yield await execute(element, scope).get()
let value = await execute(element.value, scope)
if (element.isSplat) {
if (value.getType() == 'array') {
for await (let v of value) {
yield v
}
}
} else {
yield value
}
}

@@ -150,37 +324,125 @@ })

And({left, right}, scope) {
return new Value(async () => {
let leftData = await execute(left, scope).get()
if (leftData === false) return false
let rightData = await execute(right, scope).get()
// TODO: Correct boolean semantics
return rightData
})
}
}
async Range({left, right, isExclusive}, scope) {
let leftValue = await execute(left, scope)
let rightValue = await execute(right, scope)
class StaticSource {
constructor(documents) {
this.documents = documents
}
if (!Range.isConstructible(leftValue.getType(), rightValue.getType())) {
return NULL_VALUE
}
createSink() {
return new Value(this.documents)
let range = new Range(await leftValue.get(), await rightValue.get(), isExclusive)
return new StaticValue(range)
},
async Pair({left, right}, scope) {
let leftValue = await execute(left, scope)
let rightValue = await execute(right, scope)
let pair = new Pair(await leftValue.get(), await rightValue.get())
return new StaticValue(pair)
},
async Or({left, right}, scope) {
let leftValue = await execute(left, scope)
let rightValue = await execute(right, scope)
if (leftValue.getType() == 'boolean') {
if (leftValue.data == true) return TRUE_VALUE
}
if (rightValue.getType() == 'boolean') {
if (rightValue.data == true) return TRUE_VALUE
}
if (leftValue.getType() != 'boolean') return NULL_VALUE
if (rightValue.getType() != 'boolean') return NULL_VALUE
return FALSE_VALUE
},
async And({left, right}, scope) {
let leftValue = await execute(left, scope)
let rightValue = await execute(right, scope)
if (leftValue.getType() == 'boolean') {
if (leftValue.data == false) return FALSE_VALUE
}
if (rightValue.getType() == 'boolean') {
if (rightValue.data == false) return FALSE_VALUE
}
if (leftValue.getType() != 'boolean') return NULL_VALUE
if (rightValue.getType() != 'boolean') return NULL_VALUE
return TRUE_VALUE
},
async Not({base}, scope) {
let value = await execute(base, scope)
if (value.getType() != 'boolean') {
return NULL_VALUE
}
return value.getBoolean() ? FALSE_VALUE : TRUE_VALUE
},
async Neg({base}, scope) {
let value = await execute(base, scope)
if (value.getType() != 'number') return NULL_VALUE
return fromNumber(-(await value.get()))
},
async Pos({base}, scope) {
let value = await execute(base, scope)
if (value.getType() != 'number') return NULL_VALUE
return fromNumber(await value.get())
},
async Asc() {
return NULL_VALUE
},
async Desc() {
return NULL_VALUE
}
}
function evaluate(tree, options = {}) {
function isIterator(obj) {
return obj != null && typeof obj.next == 'function'
}
/**
* Evaluates a syntax tree (which you can get from {@link module:groq-js.parse}).
*
* @param {SyntaxNode} tree
* @param {object} [options] Options.
* @param {object} [options.params] Parameters availble in the GROQ query (using `$param` syntax).
* @param {array | async-iterator} [options.documents] The documents that will be available as `*` in GROQ.
* @return {Value}
* @alias module:groq-js.evaluate
*/
async function evaluate(tree, options = {}) {
let source
let params = {identity: 'groot'}
let root = NULL_VALUE
let params = {}
if (options.documents != null) {
if (!Array.isArray(options.documents)) {
throw new Error('documents must be an array')
}
source = new StaticSource(options.documents)
if (options.documents == null) {
source = new StaticValue([])
} else if (Array.isArray(options.documents)) {
source = new StaticValue(options.documents)
} else if (isIterator(options.documents)) {
let iter = options.documents
source = new StreamValue(async function*() {
for await (let value of iter) {
yield new StaticValue(value)
}
})
} else {
source = new StaticSource([])
throw new Error('documents must be an array or an iterable')
}
if (options.root != null) {
root = new StaticValue(options.root)
}
if (options.params) {

@@ -190,6 +452,6 @@ Object.assign(params, options.params)

let scope = new Scope(params, source, null, null)
return execute(tree, scope)
let scope = new Scope(params, source, root, null)
return await execute(tree, scope)
}
exports.evaluate = evaluate

@@ -1,69 +0,181 @@

const Value = require('./value')
const {StaticValue, TRUE_VALUE, FALSE_VALUE, NULL_VALUE, fromNumber} = require('./value')
const isEqual = require('./equality')
const {partialCompare} = require('./ordering')
exports['=='] = function count(left, right, scope, execute) {
return new Value(async () => {
let a = await execute(left, scope).get()
let b = await execute(right, scope).get()
function isComparable(a, b) {
let aType = a.getType()
let bType = b.getType()
return aType == bType && (aType == 'number' || aType == 'string' || aType == 'boolean')
}
return a == b
})
exports['=='] = async function eq(left, right, scope, execute) {
let a = await execute(left, scope)
let b = await execute(right, scope)
let result = await isEqual(a, b)
return result ? TRUE_VALUE : FALSE_VALUE
}
exports['!='] = function count(left, right, scope, execute) {
return new Value(async () => {
let a = await execute(left, scope).get()
let b = await execute(right, scope).get()
exports['!='] = async function neq(left, right, scope, execute) {
let a = await execute(left, scope)
let b = await execute(right, scope)
let result = await isEqual(a, b)
return result ? FALSE_VALUE : TRUE_VALUE
}
return a != b
})
exports['>'] = async function gt(left, right, scope, execute) {
let a = await (await execute(left, scope)).get()
let b = await (await execute(right, scope)).get()
let result = partialCompare(a, b)
if (result == null) {
return NULL_VALUE
} else {
return result > 0 ? TRUE_VALUE : FALSE_VALUE
}
}
exports['>'] = function count(left, right, scope, execute) {
return new Value(async () => {
let a = await execute(left, scope).get()
let b = await execute(right, scope).get()
exports['>='] = async function gte(left, right, scope, execute) {
let a = await (await execute(left, scope)).get()
let b = await (await execute(right, scope)).get()
let result = partialCompare(a, b)
return a > b
})
if (result == null) {
return NULL_VALUE
} else {
return result >= 0 ? TRUE_VALUE : FALSE_VALUE
}
}
exports['>='] = function count(left, right, scope, execute) {
return new Value(async () => {
let a = await execute(left, scope).get()
let b = await execute(right, scope).get()
exports['<'] = async function lt(left, right, scope, execute) {
let a = await (await execute(left, scope)).get()
let b = await (await execute(right, scope)).get()
let result = partialCompare(a, b)
return a >= b
})
if (result == null) {
return NULL_VALUE
} else {
return result < 0 ? TRUE_VALUE : FALSE_VALUE
}
}
exports['<'] = function count(left, right, scope, execute) {
return new Value(async () => {
let a = await execute(left, scope).get()
let b = await execute(right, scope).get()
exports['<='] = async function lte(left, right, scope, execute) {
let a = await (await execute(left, scope)).get()
let b = await (await execute(right, scope)).get()
let result = partialCompare(a, b)
return a < b
})
if (result == null) {
return NULL_VALUE
} else {
return result <= 0 ? TRUE_VALUE : FALSE_VALUE
}
}
exports['<='] = function count(left, right, scope, execute) {
return new Value(async () => {
let a = await execute(left, scope).get()
let b = await execute(right, scope).get()
exports['in'] = async function inop(left, right, scope, execute) {
let a = await execute(left, scope)
let choices = await execute(right, scope)
return a <= b
switch (choices.getType()) {
case 'array':
for await (let b of choices) {
if (await isEqual(a, b)) {
return TRUE_VALUE
}
}
return FALSE_VALUE
case 'range':
let value = await a.get()
let range = await choices.get()
let leftCmp = partialCompare(value, range.left)
if (leftCmp == null) return NULL_VALUE
let rightCmp = partialCompare(value, range.right)
if (rightCmp == null) return NULL_VALUE
if (range.isExclusive()) {
return leftCmp >= 0 && rightCmp < 0 ? TRUE_VALUE : FALSE_VALUE
} else {
return leftCmp >= 0 && rightCmp <= 0 ? TRUE_VALUE : FALSE_VALUE
}
}
return NULL_VALUE
}
async function gatherText(value, cb) {
switch (value.getType()) {
case 'string':
cb(await value.get())
return true
case 'array':
for await (let part of value) {
if (part.getType() == 'string') {
cb(await part.get())
} else {
return false
}
}
return true
}
return false
}
exports['match'] = async function match(left, right, scope, execute) {
let text = await execute(left, scope)
let pattern = await execute(right, scope)
let tokens = []
let patterns = []
let didSucceed = await gatherText(text, part => {
tokens = tokens.concat(part.match(/[A-Za-z0-9]+/g))
})
if (!didSucceed) return NULL_VALUE
didSucceed = await gatherText(pattern, part => {
patterns = patterns.concat(part.match(/[A-Za-z0-9*]+/g))
})
if (!didSucceed) return NULL_VALUE
let matched = patterns.every(p => {
let regexp = new RegExp('^' + p.replace('*', '.*') + '$', 'i')
return tokens.some(token => regexp.test(token))
})
return matched ? TRUE_VALUE : FALSE_VALUE
}
exports['in'] = function count(left, right, scope, execute) {
return new Value(async () => {
let a = await execute(left, scope).get()
for await (let b of execute(right, scope)) {
if (a == b) {
return true
}
exports['+'] = async function plus(left, right, scope, execute) {
let a = await execute(left, scope)
let b = await execute(right, scope)
let aType = a.getType()
let bType = b.getType()
if ((aType == 'number' && bType == 'number') || (aType == 'string' && bType == 'string')) {
return new StaticValue((await a.get()) + (await b.get()))
}
return NULL_VALUE
}
function numericOperator(impl) {
return async function(left, right, scope, execute) {
let a = await execute(left, scope)
let b = await execute(right, scope)
let aType = a.getType()
let bType = b.getType()
if (aType == 'number' && bType == 'number') {
let result = impl(await a.get(), await b.get())
return fromNumber(result)
}
return false
})
}
return NULL_VALUE
}
}
exports['-'] = numericOperator((a, b) => a - b)
exports['*'] = numericOperator((a, b) => a * b)
exports['/'] = numericOperator((a, b) => a / b)
exports['%'] = numericOperator((a, b) => a % b)
exports['**'] = numericOperator((a, b) => Math.pow(a, b))

@@ -1,75 +0,219 @@

const ArrayIterator = Array.prototype[Symbol.iterator]
const getType = (exports.getType = function getType(data) {
if (data == null) return 'null'
if (Array.isArray(data)) return 'array'
if (data instanceof Range) return 'range'
return typeof data
})
function isIterator(obj) {
return obj && typeof obj.next === 'function'
}
/**
* A type of a value in GROQ.
*
* This can be one of:
* - 'null'
* - 'boolean'
* - 'number'
* - 'string'
* - 'array'
* - 'object'
* - 'range'
* - 'pair'
* @typedef {string} ValueType
*/
function isPromise(obj) {
return obj && typeof obj.then === 'function'
}
/** The result of an expression.
*
* @interface Value
*/
void 0
const EmptyIterator = {
next() {
return {done: true}
/**
* Returns the type of the value.
* @function
* @name Value#getType
* @return {ValueType}
*/
/**
* Returns a JavaScript representation of the value.
* @async
* @function
* @return {Promise}
* @name Value#get
*/
class StaticValue {
constructor(data) {
this.data = data
}
getType() {
return getType(this.data)
}
async get() {
return this.data
}
[Symbol.asyncIterator]() {
if (Array.isArray(this.data)) {
return (function*(data) {
for (let element of data) {
yield new StaticValue(element)
}
})(this.data)
} else {
throw new Error('Cannot iterate over: ' + this.getType())
}
}
getBoolean() {
return this.data === true
}
}
/** A Value represents a value that can be produced during execution of a query.
/** A StreamValue accepts a generator which yields values.
*
* Value provides a `get()` method for returning the whole data, but also
* implements the async iterator protocol for streaming data.
* @private
*/
class Value {
/** Constructs a new Value.
*
* The `inner` parameter can take the following types:
*
* (a) JSON-data
* (b) Promise which resolves to JSON-data
* (c) Function which returns (a) or (b). This function will be invoked synchronously.
* (d) Generator function which yields JSON-data
*/
constructor(inner) {
this.inner = typeof inner === 'function' ? inner() : inner
class StreamValue {
constructor(generator) {
this._generator = generator
this._ticker = null
this._isDone = false
this._data = []
}
/** Returns the data inside the Value. */
getType() {
return 'array'
}
async get() {
if (isIterator(this.inner)) {
let result = []
for await (let data of this.inner) {
result.push(data)
let result = []
for await (let value of this) {
result.push(await value.get())
}
return result
}
async *[Symbol.asyncIterator]() {
let i = 0
while (true) {
for (; i < this._data.length; i++) {
yield this._data[i]
}
return result
} else {
return this.inner
if (this._isDone) return
await this._nextTick()
}
}
/** Iterates over every element in the Value. */
[Symbol.asyncIterator]() {
if (isIterator(this.inner)) {
return this.inner
} else if (isPromise(this.inner)) {
return {
iterator: null,
promise: this.inner,
async next() {
if (!this.iterator) {
let inner = await this.promise
this.iterator = ArrayIterator.call(inner)
}
return this.iterator.next()
}
getBoolean() {
return false
}
_nextTick() {
if (this._ticker) return this._ticker
let currentResolver
let setupTicker = () => {
this._ticker = new Promise(resolve => {
currentResolver = resolve
})
}
let tick = () => {
currentResolver()
setupTicker()
}
let fetch = async () => {
for await (let value of this._generator()) {
this._data.push(value)
tick()
}
} else {
if (Array.isArray(this.inner)) {
return ArrayIterator.call(this.inner)
} else {
return EmptyIterator
}
this._isDone = true
tick()
}
setupTicker()
fetch()
return this._ticker
}
}
module.exports = Value
class MapperValue {
constructor(value) {
this.value = value
}
getType() {
return 'array'
}
async get() {
return await this.value.get()
}
[Symbol.asyncIterator]() {
return this.value[Symbol.asyncIterator].call(this.value)
}
getBoolean() {
return false
}
}
class Range {
static isConstructible(leftType, rightType) {
if (leftType == rightType) {
if (leftType == 'number') return true
if (leftType == 'string') return true
}
return false
}
constructor(left, right, exclusive) {
this.left = left
this.right = right
this.exclusive = exclusive
}
isExclusive() {
return this.exclusive
}
toJSON() {
return [this.left, this.right]
}
}
class Pair {
constructor(first, second) {
this.first = first
this.second = second
}
toJSON() {
return [this.first, this.second]
}
}
function fromNumber(num) {
if (Number.isFinite(num)) {
return new StaticValue(num)
} else {
return exports.NULL_VALUE
}
}
exports.StaticValue = StaticValue
exports.Range = Range
exports.Pair = Pair
exports.StreamValue = StreamValue
exports.MapperValue = MapperValue
exports.fromNumber = fromNumber
exports.NULL_VALUE = new StaticValue(null)
exports.TRUE_VALUE = new StaticValue(true)
exports.FALSE_VALUE = new StaticValue(false)

@@ -0,1 +1,5 @@

/**
* @module groq-js
*/
const {parse} = require('./parser')

@@ -2,0 +6,0 @@ const {evaluate} = require('./evaluator')

@@ -1,2 +0,5 @@

/** Helper class for processing a mark stream (which is what the rawParser returns). */
/** Helper class for processing a mark stream (which is what the rawParser returns).
*
* @private
*/
class MarkProcessor {

@@ -3,0 +6,0 @@ constructor(visitor, string, marks) {

const {parse: rawParse} = require('./rawParser')
const MarkProcessor = require('./markProcessor')
function isNumber(node) {
return node.type == 'Value' && typeof node.value == 'number'
}
function isString(node) {
return node.type == 'Value' && typeof node.value == 'string'
}
/**
* A tree-structure representing a GROQ query.
*
* @typedef {object} SyntaxNode
* @property {string} type The type of the node.
* @abstract
*/
const BUILDER = {
paren(p) {
let inner = p.process()
return {
type: 'Parenthesis',
base: inner
}
},
filter(p, mark) {
let base = p.process()
let query = p.process()
return unwrapArrProjection(base, base => ({
if (isNumber(query)) {
return {
type: 'Element',
base,
index: query
}
}
if (isString(query)) {
return {
type: 'Attribute',
base,
name: query.value
}
}
if (query.type == 'Range') {
return {
type: 'Slice',
base,
left: query.left,
right: query.right,
isExclusive: query.isExclusive
}
}
return {
type: 'Filter',
base,
query
}))
}
},

@@ -18,7 +69,7 @@

let query = p.process()
return unwrapArrProjection(base, base => ({
type: 'Project',
return {
type: 'Projection',
base,
query
}))
}
},

@@ -35,5 +86,16 @@

parent(p, mark) {
return {type: 'Parent'}
return {
type: 'Parent',
n: 1,
}
},
dblparent(p, mark) {
let next = p.process()
return {
type: 'Parent',
n: next.n + 1
}
},
ident(p, mark) {

@@ -56,7 +118,7 @@ let name = p.processStringEnd()

return unwrapArrProjection(base, base => ({
type: 'GetIdentifier',
return {
type: 'Attribute',
base,
name
}))
}
},

@@ -67,5 +129,4 @@

return {
type: 'ArrProject',
base,
query: {type: 'This'}
type: 'Mapper',
base
}

@@ -80,10 +141,22 @@ },

left,
right
right,
isExclusive: false
}
},
exc_range(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'Range',
left,
right,
isExclusive: true
}
},
neg(p, mark) {
let base = p.process()
if (base.type === 'Value') {
if (base.type === 'Value' && typeof base.value == 'number') {
return {

@@ -101,2 +174,84 @@ type: 'Value',

pos(p, mark) {
let base = p.process()
if (base.type === 'Value' && typeof base.value == 'number') {
return {
type: 'Value',
value: +base.value
}
}
return {
type: 'Pos',
base
}
},
add(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'OpCall',
op: '+',
left,
right,
}
},
sub(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'OpCall',
op: '-',
left,
right,
}
},
mul(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'OpCall',
op: '*',
left,
right,
}
},
div(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'OpCall',
op: '/',
left,
right,
}
},
mod(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'OpCall',
op: '%',
left,
right,
}
},
pow(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'OpCall',
op: '**',
left,
right,
}
},
deref(p, mark) {

@@ -106,11 +261,14 @@ let base = p.process()

let nextMark = p.getMark()
let result = {type: 'Deref', base}
if (nextMark && nextMark.name === 'deref_field') {
throw new Error('TODO: Handle deref properly')
let name = p.processString()
result = {
type: 'Attribute',
base: result,
name
}
}
return unwrapArrProjection(base, base => ({
type: 'Deref',
base
}))
return result
},

@@ -154,6 +312,24 @@

sci(p, mark) {
let strValue = p.processStringEnd()
return {
type: 'Value',
value: Number(strValue)
}
},
pair(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'Pair',
left,
right
}
},
object(p, mark) {
let properties = []
let attributes = []
while (p.getMark().name !== 'object_end') {
properties.push(p.process())
attributes.push(p.process())
}

@@ -163,3 +339,3 @@ p.shift()

type: 'Object',
properties: properties
attributes
}

@@ -170,4 +346,13 @@ },

let value = p.process()
if (value.type == 'Pair') {
return {
type: 'ObjectConditionalSplat',
condition: value.left,
value: value.right
}
}
return {
type: 'Property',
type: 'ObjectAttribute',
key: {

@@ -185,3 +370,3 @@ type: 'Value',

return {
type: 'Property',
type: 'ObjectAttribute',
key: key,

@@ -193,11 +378,31 @@ value: value

object_splat(p, mark) {
let value = p.process()
return {
type: 'ObjectSplat'
type: 'ObjectSplat',
value
}
},
object_splat_this(p, mark) {
return {
type: 'ObjectSplat',
value: {type: 'This'}
}
},
array(p, mark) {
let elements = []
while (p.getMark().name !== 'array_end') {
elements.push(p.process())
let isSplat = false
if (p.getMark().name == 'array_splat') {
isSplat = true
p.shift()
}
let value = p.process()
elements.push({
type: 'ArrayElement',
value,
isSplat
})
}

@@ -225,2 +430,12 @@ p.shift()

pipecall(p, mark) {
let base = p.process()
let func = p.process()
return {
...func,
type: 'PipeFuncCall',
base
}
},
and(p, mark) {

@@ -234,5 +449,50 @@ let left = p.process()

}
},
or(p, mark) {
let left = p.process()
let right = p.process()
return {
type: 'Or',
left,
right
}
},
not(p, mark) {
let base = p.process()
return {
type: 'Not',
base
}
},
asc(p, mark) {
let base = p.process()
return {
type: 'Asc',
base
}
},
desc(p, mark) {
let base = p.process()
return {
type: 'Desc',
base
}
}
}
const NESTED_PROPERTY_TYPES = [
'Deref',
'Projection',
'Mapper',
'Filter',
'Element',
'Slice',
]
function extractPropertyKey(node) {

@@ -243,3 +503,3 @@ if (node.type === 'Identifier') {

if (node.type === 'Deref' || node.type === 'ArrProject') {
if (NESTED_PROPERTY_TYPES.includes(node.type)) {
return extractPropertyKey(node.base)

@@ -251,14 +511,10 @@ }

function unwrapArrProjection(base, func) {
if (base.type === 'ArrProject') {
return {
type: 'ArrProject',
base: base.base,
query: func(base.query)
}
} else {
return func(base)
}
}
/**
* Parses a GROQ query and returns a tree structure.
*
* @param {string} input GROQ query
* @returns {SyntaxNode}
* @alias module:groq-js.parse
* @static
*/
function parse(input) {

@@ -265,0 +521,0 @@ let result = rawParse(input)

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

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