First of all: great work! I like the light-weight database approach and the comprehensive documentation.
I understand it's possible to get the parents of a node:
child = db.select('/user/handle');
child.parent(); // -> gives the user node
My feature request is if it's possible to extend the query language so I can write child.select('..') to get the same result.
I also wonder how I could get the previous or next sibling of a node in an array.
Hey! Very pleasantly surprising to get a feature request before I properly announce the library (I'm working on a decent REPL and API docs before publishing fully).
I actually agree that this would be a neat feature, but it requires some fairly tough changes to the descent operation performed during query operation - while SpahQL currently uses the actual JS call stack for descent, instead we'd have to pass about an array stack which may be popped when encountering a parent operator (".."). I wonder if "+1" would be a valid next-sibling operator as well - what do you think?
// Parent selector
db.select("//status/..") //-> select all parent objects of a "status" key
// Sibling selector
db.select("/timeline/first/+1") //-> equivalent to "/timeline/1"
db.select("/timeline/last/-1") //-> select penultimate status
Actually, let's revise the sibling selectors. Applying a path component semantic for elements at the same depth seems wildly inconsistent. Here's some possibilities for you to comment on:
db.select("/timeline/first|sibling(+1)") // general pattern of KEY . PIPE . TRAVERSAL_FUNCTION . BRACKET_LEFT . ARGS . BRACKET_RIGHT . FILTER_QUERIES
db.select("/timeline/first+1") // potentially dangerous, as "+1" and "-1" are also valid string object keys
Sorry about my late response. I checked the in-browser REPL which is nice to play with, I also had a look at the source.
I think your PIPE syntax would be great, and it wouldn't break the current operation. Here is my slightly different syntax
db.select("/timeline/first/|next/name") // the next function takes no argument
db.select("/timeline/last/|sibling(-1)") // sibling takes an argument
It could be implemented in a general (an unsafe) way: db.select("/foo/|traversal/bar") would be equivalent with db.select("/foo").traversal().select("/bar"). This would make extension quite easy, the user just has to add their traversal function to the SpahQL prototype. So db.select("/foo/bar").select("|parent") would work out of the box.
I think including the slash for arbitrary traversal functions isn't quite right - let's assume for a moment that the slash doesn't exist in the language. The query:
Could be seen as a macro for "descend" and "descendRecursive" traversals:
However we allow / and // as aliases for baked in descent functions - meaning the slashes have an inherent meaning relating to descent. A pipe is traversal-agnostic and essentially boils down to function-currying:
descend('a')|descend('b')|parent //-> pass output of each function into the next and return the final value
/a/b|parent //-> Truncated version of above
Bear in mind that pipes are not parts of a path component but would actually terminate parsing of the current query and tell the query parser to expect a new path query or traversal function, which will receive the result array from the previous token as input data. Here's an example with filters:
//*[/.type == 'array'] | firstChild //-> Find all arrays and return the first child
//*[/.type == 'array']/0 //-> Equivalent in current syntax
I'm still in two minds about whether or not to actually implement this, but I'm fairly certain the currying pattern is what you're after - which would mean no slashes before pipes.