Jora
JavaScript object query engine
STATUS: A proof of concept. Syntax may change in next releases.
Features:
- Tolerant to data stucture queries (e.g. just returns nothing for paths that not reachable)
- Compact syntax for common tasks
- Aggregate values across arrays and eliminate duplicates by default
- Stat collecting mode (powers suggestions)
- Tolerant parsing mode (useful to provide suggestions on a query editing in an editor)
- Extensible DSL by providing a additional method list on query build
ntl
Related projects:
TODO:
Table of content:
Install
npm install jora
API
const jora = require('jora');
const query = jora('foo.bar') ;
const queryWithCustomMethods = jora('foo.myMethod()', {
methods: {
myMethod(current) { }
}
});
const result = query(data, context);
Options:
-
methods
Type: Object
Default: undefined
Additional methods for using in query passed as an object, where a key is a method name and a value is a function to perform an action. It can override build-in methods.
-
debug
Type: Boolean
Default: false
Enables debug output.
-
tolerant
Type: Boolean
Default: false
Enables tolerant parsing mode. This mode supresses parsing errors when possible.
-
stat
Type: Boolean
Default: false
Enables stat mode. When mode is enabled a query stat interface is returning instead of resulting data.
Quick demo
Get npm dependency paths (as a tree) that have packages with more than one version:
const jora = require('jora');
function printTree() {
}
require('child_process').exec('npm ls --json', (error, stdout) => {
if (error) {
return;
}
const npmTree = JSON.parse(stdout);
const tree = JSON.parse(stdout);
const depsPathsToMultipleVersionPackages = jora(`
$multiVersionPackages:
..(dependencies.mapToArray("name"))
.group(<name>, <version>)
.({ name: key, versions: value.sort() })
.[versions.size() > 1];
$pathToMultiVersionPackages: => .($name; {
name,
version,
otherVersions: $multiVersionPackages.pick(<name=$name>).versions - version,
dependencies: dependencies
.mapToArray("name")
.map($pathToMultiVersionPackages)
.[name in $multiVersionPackages.name or dependencies]
});
map($pathToMultiVersionPackages)
`)(tree);
printTree(depsPathsToMultipleVersionPackages);
});
Example of output:
jora@1.0.0
├─ browserify@16.2.2
│ ├─ assert@1.4.1
│ │ └─ util@0.10.3 [other versions: 0.10.4]
│ │ └─ inherits@2.0.1 [other versions: 2.0.3]
│ ├─ browser-pack@6.1.0
│ │ └─ combine-source-map@0.8.0
│ │ ├─ source-map@0.5.7 [other versions: 0.6.1, 0.4.4, 0.2.0, 0.1.43]
│ │ └─ inline-source-map@0.6.2
│ │ └─ source-map@0.5.7 [other versions: 0.6.1, 0.4.4, 0.2.0, 0.1.43]
│ ├─ browser-resolve@1.11.3
│ │ └─ resolve@1.1.7 [other versions: 1.8.1]
│ ├─ concat-stream@1.6.2
│ │ └─ inherits@2.0.3 [other versions: 2.0.1]
...
Syntax
Primitives
Jora | Description |
---|
42 -123 4.22 1e3 1e-2 | Numbers |
"string" 'string' | Strings |
/regexp/ /regexp/i | A JavaScript regexp, only i flag supported |
{ } | Object initializer/literal syntax. You can use spread operator ... , e.g. { a: 1, ..., ...foo, ...bar } (... with no expression on right side the same as ...$ ) |
[ ] | Array initializer/literal syntax |
< block > => e | A function NOTE: Syntax will be changed |
query asc query desc query asc, query desc, ... | A sorting function that takes two arguments and compare query result for each in specified order (asc – ascending, desc – descending) |
Keywords
Following keywords can be used as in JavaScript:
Operators
Jora | Description |
---|
x + y | Add In case one of the operands is an array it produces new array with elements from x and y excluding duplicates |
x - y | Subtract In case one of the operands is an array with elements from x excluding elements from y |
x * y | Multiply |
x / y | Divide |
x % y | Modulo |
Comparisons
Jora | Description |
---|
x = y | Equals (as === in JS) |
x != y | Not equals (as !== in JS) |
x < y | Less than |
x <= y | Less than or equal to |
x > y | Greater than |
x >= y | Greater than or equal to |
x ~= y | Match operator, behaviour depends on y type: RegExp – test against regexp function – test like filter()
null or undefined – always truthy anything else – always falsy |
Boolean logic
Jora | Description |
---|
( x ) | Explicity operator precedence. Definitions are allowed (i.e. ($a: 1; $a + $a) see bellow) |
x or y | Boolean or . Equivalent to || in JS, but x tests with bool() method |
x and y | Boolean and . Equivalent to && in JS, but x tests with bool() method |
not x no x | Boolean not . Equivalent to && in JS, but x tests with bool() method |
x ? y : z | If x is truthy than return y else return z . x tests with bool() method |
x in [a, b, c] [a, b, c] has x | Equivalent to x = a or x = b or x = c |
x not in [a, b, c] [a, b, c] has no x | Equivalent to x != a and x != b and x != c |
Block & definitions
Some constructions suppose to use a block, which may consists of a definition list (should comes first) and an expression. Both are optional. When an expression is empty, a current value (i.e. $
) returns.
The syntax of definition (white spaces between any part are optional):
$ SYMBOL ;
$ SYMBOL : expression ;
For example:
$foo:123; // Define `$foo`
$bar; // The same as `$bar:$.bar;` or `$a:bar;`
$baz: $foo + $bar; // Definitions may be used in following expressions
In terms of JavaScript, a block creates a new scope. Variables can't be redefined or change a value in the same or nested scopes, otherwise it cause to error.
Special references
Jora | Description |
---|
$ | A scope input data (current value). On top level scope it's the same as @ . In most cases it may be omitted. Used implicitly an input for subquery when no other subjects is defined (e.g. foo() and .foo() are equivalent for $.foo() ). |
@ | A query input data |
# | A query context |
Since Jora's query performs as query(data, context)
, in terms of Jora it looks like query(@, #)
.
Path chaining
jora | Description |
---|
SYMBOL | The same as $.SYMBOL |
.SYMBOL | Child member operator (example: foo.bar.baz , #.foo['use any symbols for name'] ) |
..SYMBOL ..( block ) | Recursive descendant operator (example: ..deps , ..(deps + dependants) ) |
.[ block ] | Filter a current data. Equivalent to a .filter(<block>) |
.( block ) | Map a current data. Equivalent to a .map(<block>) |
.method() ..method() | Invoke a method to current data, or each element of current data if it is an array |
path[e] | Array-like notation to access properties. It works like in JS for everything with exception for arrays, where it equivalents to array.map(e => e[key]) . Use pick() method to get an element by index in array. |
[from:to] [from:to:step] | Slice notation. Examples: $str: '<foo>'; str[1:-1] ('foo' ) or $ar:[1,2,3,4,5,6]; $ar[-3::-1] ([6,5,4] ) |
Build-in methods
jora | Description |
---|
bool() | The same as Boolean() in JS, with exception that empty arrays and objects with no keys treats as falsy |
keys() | The same as Object.keys() in JS |
values() | The same as Object.values() in JS |
entries() | The same as Object.entries() in JS |
mapToArray("key"[, "value"]) | Converts an object to an array, and store object key as "key" |
pick("key") pick(fn) | Get a value by a key, an index or a function. Useful for arrays, e.g. since array[5] applies [5] for each element in an array (equivalent to array.map(e => e[5]) ), array.pick(5) should be used instead. |
size() | Returns count of keys if current data is object, otherwise returns length value or 0 when field is absent |
sort(<fn>) | Sort an array by a value fetched with getter (<fn> ). Keep in mind, you can use sorting function definition syntax using asc and desc keywords, qhich is more effective in many ways. In case of sorting function definition usage, < and > are not needed and you can specify sorting order for each component. Following queries are equivalents:
sort(<foo.bar>) and sort(foo.bar asc)
sort(<foo>).reverse() and sort(foo desc)
sort(<[a, b]>) and sort(a asc, b asc) |
reverse() | Reverse order of items |
group(<fn>[, <fn>]) | Group an array items by a value fetched with first getter. |
filter(<fn>) | The same as Array#filter() in JS |
map(<fn>) | The same as Array#map() in JS |
split(pattern) | The same as String#split() in JS. pattern may be a string or regexp |
join(separator) | The same as Array#join() in JS. When separator is undefined then "," is using |
slice(from, to) | The same as Array#slice() or String#slice() in JS |
match(pattern, matchAll) | The same as String#match() . Since regexp'es in jora doesn't support for g flag, use matchAll argument to get all matches, i.e. 'abcabc'.match(/ab/, true) (jora) instead of 'abcabc'.match(/ab/g) (JS) |
License
MIT