├── .gitignore ├── .npmignore ├── .npmrc ├── .travis.yml ├── README.md ├── package.json ├── src ├── index.ts └── visitor.ts ├── test ├── query.spec.js └── visitor.spec.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | .vscode 36 | lib 37 | report/ 38 | test-results.xml 39 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Report directory generated by mocha 18 | report 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | node_modules 31 | 32 | # Optional npm cache directory 33 | .npm 34 | 35 | # Optional REPL history 36 | .node_repl_history 37 | 38 | .vscode -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | @types:registry=https://registry.npmjs.org 3 | loglevel="warn" 4 | //SECRET -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OData V4 Service modules - MongoDB Connector 2 | 3 | Service OData v4 requests from a MongoDB data store. 4 | 5 | ## Synopsis 6 | The OData V4 MongoDB Connector provides functionality to convert the various types of OData segments 7 | into MongoDB query objects, that you can execute over a MongoDB database. 8 | 9 | ## Potential usage scenarios 10 | 11 | - Create high speed, standard compliant data sharing APIs 12 | 13 | ## Usage as server - TypeScript 14 | ```javascript 15 | import { createFilter } from 'odata-v4-mongodb' 16 | 17 | //example request: GET /api/products?$filter=category/id eq 5 or color eq 'Red' 18 | app.get("/api/products", (req: Request, res: Response) => { 19 | const filter = createFilter(req.query.$filter); 20 | // collection instance from MongoDB Node.JS Driver 21 | collection.find(filter, function(err, data){ 22 | res.json({ 23 | '@odata.context': req.protocol + '://' + req.get('host') + '/api/$metadata#products', 24 | value: data 25 | }); 26 | }); 27 | }); 28 | ``` 29 | 30 | ## Usage ES5 31 | ```javascript 32 | var createFilter = require('odata-v4-mongodb').createFilter; 33 | 34 | app.get("/api/products", function(req, res) { 35 | var filter = createFilter(req.query.$filter); 36 | // collection instance from MongoDB Node.JS Driver 37 | collection.find(filter, function(err, data){ 38 | res.json({ 39 | '@odata.context': req.protocol + '://' + req.get('host') + '/api/$metadata#products', 40 | value: data 41 | }); 42 | }); 43 | }) 44 | ``` 45 | 46 | ## Supported OData segments 47 | 48 | For now **$filter**, **$select**, **$skip** and **$top** 49 | 50 | Support for **$expand** is next. 51 | 52 | ### Supported $filter expressions 53 | 54 | The [OData v4 Parser](https://www.npmjs.com/package/odata-v4-parser) layer supports 100% of the specification. 55 | The Connector is supporting basic MongoDB queries. 56 | 57 | *We are into creating a comprehensive feature availability chart for V1 release* 58 | 59 | √ expression 5.1.1.6.1: NullValue eq null 60 | √ expression 5.1.1.6.1: TrueValue eq true 61 | √ expression 5.1.1.6.1: FalseValue eq false 62 | √ expression 5.1.1.6.1: IntegerValue lt -128 63 | √ expression 5.1.1.6.1: DecimalValue eq 34.95 64 | √ expression 5.1.1.6.1: StringValue eq 'Say Hello,then go' 65 | √ expression 5.1.1.6.1: DurationValue eq duration'P12DT23H59M59.999999999999S' 66 | √ expression 5.1.1.6.1: DateValue eq 2012-12-03 67 | √ expression 5.1.1.6.1: DateTimeOffsetValue eq 2012-12-03T07:16:23Z 68 | √ expression 5.1.1.6.1: GuidValue eq 01234567-89ab-cdef-0123-456789abcdef 69 | √ expression 5.1.1.6.1: Int64Value eq 0 70 | √ expression 5.1.1.6.1: A eq INF 71 | √ expression 5.1.1.6.1: A eq 0.31415926535897931e1 72 | √ expression 5.1.1.1.2: A ne 1 73 | √ expression 5.1.1.1.3: A gt 2 74 | √ expression 5.1.1.1.4: A ge 3 75 | √ expression 5.1.1.1.5: A lt 2 76 | √ expression 5.1.1.1.6: A le 2 77 | √ expression: A/b eq 1 78 | √ expression 5.1.1.3: (A/b eq 2) or (B/c lt 4) and ((E gt 5) or (E lt -1)) 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "odata-v4-mongodb", 3 | "version": "0.1.12", 4 | "description": "Service OData requests from a MongoDB data store", 5 | "main": "lib/index.js", 6 | "typings": "lib/index", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "build": "tsc src/index.ts --outDir lib -d", 12 | "pretest": "npm run build", 13 | "test": "mocha", 14 | "test:jenkins": "npm run build && mocha test --recursive ./test/**/*.spec.js --reporter mocha-junit-reporter", 15 | "pretdd": "npm run build", 16 | "tdd": "mocha -w", 17 | "prepublish": "npm test" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/jaystack/odata-v4-mongodb.git" 22 | }, 23 | "keywords": [ 24 | "OData", 25 | "server", 26 | "V4", 27 | "parser" 28 | ], 29 | "author": "JayStack", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/jaystack/odata-v4-mongodb/issues" 33 | }, 34 | "homepage": "https://github.com/jaystack/odata-v4-mongodb#readme", 35 | "dependencies": { 36 | "odata-v4-literal": "^0.1.0", 37 | "odata-v4-parser": "^0.1.18" 38 | }, 39 | "devDependencies": { 40 | "chai": "^3.5.0", 41 | "mocha": "^3.2.0", 42 | "mocha-junit-reporter": "^1.13.0", 43 | "typescript": "^2.2.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Visitor } from "./visitor" 2 | import { filter, query } from "odata-v4-parser" 3 | import { Token } from "odata-v4-parser/lib/lexer" 4 | 5 | /** 6 | * Creates MongoDB collection, query, projection, sort, skip and limit from an OData URI string 7 | * @param {string} queryString - An OData query string 8 | * @return {Visitor} Visitor instance object with collection, query, projection, sort, skip and limit 9 | * @example 10 | * const query = createQuery("$filter=Size eq 4&$orderby=Orders&$skip=10&$top=5"); 11 | * collections[query.collection].find(query.query).project(query.projection).sort(query.sort).skip(query.skip).limit(query.limit).toArray(function(err, data){ ... }); 12 | */ 13 | export function createQuery(odataQuery:string); 14 | export function createQuery(odataQuery:Token); 15 | export function createQuery(odataQuery:string | Token){ 16 | let ast:Token = (typeof odataQuery == "string" ? query(odataQuery) : odataQuery); 17 | return new Visitor().Visit(ast); 18 | } 19 | 20 | /** 21 | * Creates a MongoDB query object from an OData filter expression string 22 | * @param {string} odataFilter - A filter expression in OData $filter format 23 | * @return {Object} MongoDB query object 24 | * @example 25 | * const filter = createFilter("Size eq 4 and Age gt 18"); 26 | * collection.find(filter, function(err, data){ ... }); 27 | */ 28 | export function createFilter(odataFilter:string); 29 | export function createFilter(odataFilter:Token); 30 | export function createFilter(odataFilter:string | Token):Object{ 31 | let context = { query: {} }; 32 | let ast:Token = (typeof odataFilter == "string" ? filter(odataFilter) : odataFilter); 33 | new Visitor().Visit(ast, context); 34 | return context.query; 35 | } -------------------------------------------------------------------------------- /src/visitor.ts: -------------------------------------------------------------------------------- 1 | import { Token } from "odata-v4-parser/lib/lexer"; 2 | import { Literal } from "odata-v4-literal"; 3 | 4 | export class Visitor{ 5 | query: any 6 | sort: any 7 | skip: number 8 | limit: number 9 | projection: any 10 | collection: string 11 | navigationProperty: string 12 | includes:Visitor[] 13 | inlinecount: boolean 14 | ast:Token 15 | 16 | constructor(){ 17 | this.query = {}; 18 | this.sort = {}; 19 | this.projection = {}; 20 | this.includes = []; 21 | 22 | let _ast; 23 | Object.defineProperty(this, "ast", { 24 | get: () => { return _ast; }, 25 | set: (v) => { _ast = v; }, 26 | enumerable: false 27 | }); 28 | } 29 | 30 | Visit(node:Token, context?:any){ 31 | this.ast = this.ast || node; 32 | context = context || {}; 33 | 34 | if (node){ 35 | var visitor = this[`Visit${node.type}`]; 36 | if (visitor) visitor.call(this, node, context); 37 | } 38 | 39 | return this; 40 | } 41 | 42 | protected VisitODataUri(node:Token, context:any){ 43 | this.Visit(node.value.resource, context); 44 | this.Visit(node.value.query, context); 45 | } 46 | 47 | protected VisitEntitySetName(node:Token, context:any){ 48 | this.collection = node.value.name; 49 | } 50 | 51 | protected VisitExpand(node: Token, context: any) { 52 | var innerContexts:any = {}; 53 | node.value.items.forEach((item) => { 54 | var expandPath = item.value.path.raw; 55 | var innerVisitor = this.includes.filter(v => v.navigationProperty === expandPath)[0]; 56 | if (!innerVisitor){ 57 | innerVisitor = new Visitor(); 58 | 59 | innerContexts[expandPath] = { 60 | query: {}, 61 | sort: {}, 62 | projection: {}, 63 | options: {} 64 | }; 65 | 66 | this.includes.push(innerVisitor); 67 | } 68 | 69 | let innerContext:any = innerContexts[expandPath] || {}; 70 | innerVisitor.Visit(item, innerContext); 71 | 72 | innerVisitor.query = innerContext.query || innerVisitor.query || {}; 73 | innerVisitor.sort = innerContext.sort || innerVisitor.sort; 74 | innerVisitor.projection = innerContext.projection || innerVisitor.projection; 75 | }); 76 | } 77 | 78 | protected VisitExpandItem(node: Token, context: any) { 79 | this.Visit(node.value.path, context); 80 | node.value.options && node.value.options.forEach((item) => this.Visit(item, context)); 81 | } 82 | 83 | protected VisitExpandPath(node: Token, context: any) { 84 | this.navigationProperty = node.raw; 85 | } 86 | 87 | protected VisitQueryOptions(node:Token, context:any){ 88 | var self = this; 89 | 90 | context.options = {}; 91 | node.value.options.forEach((option) => this.Visit(option, context)); 92 | 93 | this.query = context.query || {}; 94 | delete context.query; 95 | 96 | this.sort = context.sort; 97 | delete context.sort; 98 | } 99 | 100 | protected VisitInlineCount(node:Token, context:any){ 101 | this.inlinecount = Literal.convert(node.value.value, node.value.raw); 102 | } 103 | 104 | protected VisitFilter(node:Token, context:any){ 105 | context.query = {}; 106 | this.Visit(node.value, context); 107 | delete context.identifier; 108 | delete context.literal; 109 | } 110 | 111 | protected VisitOrderBy(node:Token, context:any){ 112 | context.sort = {}; 113 | node.value.items.forEach((item) => this.Visit(item, context)); 114 | } 115 | 116 | protected VisitSkip(node:Token, context:any){ 117 | this.skip = +node.value.raw; 118 | } 119 | 120 | protected VisitTop(node:Token, context:any){ 121 | this.limit = +node.value.raw; 122 | } 123 | 124 | protected VisitOrderByItem(node:Token, context:any){ 125 | this.Visit(node.value.expr, context); 126 | if (context.identifier) context.sort[context.identifier] = node.value.direction; 127 | delete context.identifier; 128 | delete context.literal; 129 | } 130 | 131 | protected VisitSelect(node:Token, context:any){ 132 | context.projection = {}; 133 | node.value.items.forEach((item) => this.Visit(item, context)); 134 | 135 | this.projection = context.projection; 136 | delete context.projection; 137 | } 138 | 139 | protected VisitSelectItem(node:Token, context:any){ 140 | context.projection[node.raw.replace(/\//g, '.')] = 1; 141 | } 142 | 143 | protected VisitAndExpression(node:Token, context:any){ 144 | var query = context.query; 145 | var leftQuery = {}; 146 | context.query = leftQuery; 147 | this.Visit(node.value.left, context); 148 | 149 | var rightQuery = {}; 150 | context.query = rightQuery; 151 | this.Visit(node.value.right, context); 152 | 153 | if (Object.keys(leftQuery).length > 0 && Object.keys(rightQuery).length > 0){ 154 | query.$and = [leftQuery, rightQuery]; 155 | } 156 | context.query = query; 157 | } 158 | 159 | protected VisitOrExpression(node:Token, context:any){ 160 | var query = context.query; 161 | var leftQuery = {}; 162 | context.query = leftQuery; 163 | this.Visit(node.value.left, context); 164 | 165 | var rightQuery = {}; 166 | context.query = rightQuery; 167 | this.Visit(node.value.right, context); 168 | 169 | if (Object.keys(leftQuery).length > 0 && Object.keys(rightQuery).length > 0){ 170 | query.$or = [leftQuery, rightQuery]; 171 | } 172 | context.query = query; 173 | } 174 | 175 | protected VisitBoolParenExpression(node:Token, context:any){ 176 | this.Visit(node.value, context); 177 | } 178 | 179 | protected VisitCommonExpression(node:Token, context:any){ 180 | this.Visit(node.value, context); 181 | } 182 | 183 | protected VisitFirstMemberExpression(node:Token, context:any){ 184 | this.Visit(node.value, context); 185 | } 186 | 187 | protected VisitMemberExpression(node:Token, context:any){ 188 | this.Visit(node.value, context); 189 | } 190 | 191 | protected VisitPropertyPathExpression(node:Token, context:any){ 192 | if (node.value.current && node.value.next){ 193 | this.Visit(node.value.current, context); 194 | if (context.identifier) context.identifier += "."; 195 | this.Visit(node.value.next, context); 196 | }else this.Visit(node.value, context); 197 | } 198 | 199 | protected VisitSingleNavigationExpression(node:Token, context:any){ 200 | if (node.value.current && node.value.next){ 201 | this.Visit(node.value.current, context); 202 | this.Visit(node.value.next, context); 203 | }else this.Visit(node.value, context); 204 | } 205 | 206 | protected VisitODataIdentifier(node:Token, context:any){ 207 | context.identifier = (context.identifier || "") + node.value.name; 208 | } 209 | 210 | protected VisitNotExpression(node:Token, context:any){ 211 | this.Visit(node.value, context); 212 | if (context.query){ 213 | for (var prop in context.query){ 214 | context.query[prop] = { $not: context.query[prop] }; 215 | } 216 | } 217 | } 218 | 219 | protected VisitEqualsExpression(node:Token, context:any){ 220 | this.Visit(node.value.left, context); 221 | this.Visit(node.value.right, context); 222 | 223 | if (context.identifier) context.query[context.identifier] = context.literal; 224 | delete context.identifier; 225 | delete context.literal; 226 | } 227 | 228 | protected VisitNotEqualsExpression(node:Token, context:any){ 229 | var left = this.Visit(node.value.left, context); 230 | var right = this.Visit(node.value.right, context); 231 | 232 | if (context.identifier) context.query[context.identifier] = { $ne: context.literal }; 233 | delete context.identifier; 234 | delete context.literal; 235 | } 236 | 237 | protected VisitLesserThanExpression(node:Token, context:any){ 238 | var left = this.Visit(node.value.left, context); 239 | var right = this.Visit(node.value.right, context); 240 | 241 | if (context.identifier) context.query[context.identifier] = { $lt: context.literal }; 242 | delete context.identifier; 243 | delete context.literal; 244 | } 245 | 246 | protected VisitLesserOrEqualsExpression(node:Token, context:any){ 247 | var left = this.Visit(node.value.left, context); 248 | var right = this.Visit(node.value.right, context); 249 | 250 | if (context.identifier) context.query[context.identifier] = { $lte: context.literal }; 251 | delete context.identifier; 252 | delete context.literal; 253 | } 254 | 255 | protected VisitGreaterThanExpression(node:Token, context:any){ 256 | var left = this.Visit(node.value.left, context); 257 | var right = this.Visit(node.value.right, context); 258 | 259 | if (context.identifier) context.query[context.identifier] = { $gt: context.literal }; 260 | delete context.identifier; 261 | delete context.literal; 262 | } 263 | 264 | protected VisitGreaterOrEqualsExpression(node:Token, context:any){ 265 | var left = this.Visit(node.value.left, context); 266 | var right = this.Visit(node.value.right, context); 267 | 268 | if (context.identifier) context.query[context.identifier] = { $gte: context.literal }; 269 | delete context.identifier; 270 | delete context.literal; 271 | } 272 | 273 | protected VisitLiteral(node:Token, context:any){ 274 | context.literal = Literal.convert(node.value, node.raw); 275 | } 276 | 277 | protected VisitMethodCallExpression(node: Token, context: any) { 278 | var method = node.value.method; 279 | var params = (node.value.parameters || []).forEach(p => this.Visit(p, context)); 280 | if (context.identifier) { 281 | switch (method) { 282 | case "contains": 283 | context.query[context.identifier] = new RegExp(context.literal, "gi"); 284 | break; 285 | case "endswith": 286 | context.query[context.identifier] = new RegExp(context.literal + "$", "gi"); 287 | break; 288 | case "startswith": 289 | context.query[context.identifier] = new RegExp("^" + context.literal, "gi"); 290 | break; 291 | default: 292 | throw new Error("Method call not implemented.") 293 | } 294 | delete context.identifier; 295 | } 296 | } 297 | 298 | } 299 | -------------------------------------------------------------------------------- /test/query.spec.js: -------------------------------------------------------------------------------- 1 | var createQuery = require('../lib').createQuery 2 | var expect = require('chai').expect 3 | 4 | describe("mongodb query", () => { 5 | var f; 6 | beforeEach(function() { 7 | var match; 8 | if (match = this.currentTest.title.match(/expression[^\:]*\: ?(.*)/)) { 9 | f = createQuery(match[1]); 10 | } 11 | }); 12 | 13 | it("expression: $filter=contains(Name,'c')&$orderby=UnitPrice", () => { 14 | expect(f.query).to.deep.equal({ Name: /c/gi }); 15 | expect(f.sort).to.deep.equal({ UnitPrice: 1 }); 16 | }); 17 | 18 | it("expression: $filter=contains(Name,'c')&$orderby=Name", () => { 19 | expect(f.query).to.deep.equal({ Name: /c/gi }); 20 | expect(f.sort).to.deep.equal({ Name: 1 }); 21 | }); 22 | 23 | it("expression: $filter=contains(Description,'c')&$orderby=Name", () => { 24 | expect(f.query).to.deep.equal({ Description: /c/gi }); 25 | expect(f.sort).to.deep.equal({ Name: 1 }); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/visitor.spec.js: -------------------------------------------------------------------------------- 1 | var createFilter = require('../lib').createFilter 2 | var expect = require('chai').expect 3 | 4 | describe("mongodb visitor", () => { 5 | var f; 6 | beforeEach(function() { 7 | var match; 8 | if (match = this.currentTest.title.match(/expression[^\:]*\: ?(.*)/)) { 9 | f = createFilter(match[1]); 10 | } 11 | }); 12 | 13 | //all numbers are referencing this: 14 | //http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part2-url-conventions/odata-v4.0-errata02-os-part2-url-conventions-complete.html#_Toc406398116 15 | 16 | it("expression: 1 eq 1", () => { 17 | expect(f).to.deep.eql({}) 18 | }) 19 | 20 | it("expression: (1 eq 1) or (2 eq 2)", () => { 21 | expect(f).to.deep.eql({}) 22 | }) 23 | 24 | it("expression 5.1.1.6.1: NullValue eq null", () => { 25 | expect(f).to.deep.eql({ NullValue: null }) 26 | }) 27 | 28 | it("expression 5.1.1.6.1: TrueValue eq true", () => { 29 | expect(f).to.deep.eql({ TrueValue: true }) 30 | }) 31 | 32 | it("expression 5.1.1.6.1: FalseValue eq false", () => { 33 | expect(f).to.deep.eql({ FalseValue: false }) 34 | }) 35 | 36 | it("expression 5.1.1.6.1: IntegerValue lt -128", () => { 37 | expect(f).to.deep.eql({ IntegerValue: { $lt: -128 } }) 38 | }) 39 | 40 | it("expression 5.1.1.6.1: DecimalValue eq 34.95", () => { 41 | expect(f).to.deep.eql({ DecimalValue: 34.95 }) 42 | }) 43 | 44 | it("expression 5.1.1.6.1: StringValue eq 'Say Hello,then go'", () => { 45 | expect(f).to.deep.eql({ StringValue: 'Say Hello,then go' }) 46 | }) 47 | 48 | xit("expression 5.1.1.6.1: DurationValue eq duration'P12DT23H59M59.999999999999S'", () => { 49 | expect(f).to.deep.eql({ DurationValue: 1033199000 }) 50 | }) 51 | 52 | it("expression 5.1.1.6.1: DateValue eq 2012-12-03", () => { 53 | expect(f).to.deep.eql({ DateValue: '2012-12-03' }) 54 | }) 55 | 56 | it("expression 5.1.1.6.1: DateTimeOffsetValue eq 2012-12-03T07:16:23Z", () => { 57 | expect(f).to.deep.eql({ DateTimeOffsetValue: new Date('2012-12-03T07:16:23Z') }) 58 | }) 59 | 60 | it("expression 5.1.1.6.1: GuidValue eq 01234567-89ab-cdef-0123-456789abcdef", () => { 61 | expect(f).to.deep.eql({ GuidValue: '01234567-89ab-cdef-0123-456789abcdef' }) 62 | }) 63 | 64 | it("expression 5.1.1.6.1: Int64Value eq 0", () => { 65 | expect(f).to.deep.eql({ Int64Value: 0 }) 66 | }) 67 | 68 | it("expression 5.1.1.6.1: A eq INF", () => { 69 | expect(f).to.deep.eql({ A: Infinity }) 70 | }) 71 | 72 | it("expression 5.1.1.6.1: A eq 0.31415926535897931e1", () => { 73 | expect(f).to.deep.eql({ A: 0.31415926535897931e1 }) 74 | }) 75 | 76 | it("expression 5.1.1.1.2: A ne 1", () => { 77 | expect(f).to.deep.eql({ A: { $ne: 1 } }) 78 | }) 79 | 80 | it("expression 5.1.1.1.3: A gt 2", () => { 81 | expect(f).to.deep.eql({ A: { $gt: 2 } }) 82 | }) 83 | 84 | it("expression 5.1.1.1.4: A ge 3", () => { 85 | expect(f).to.deep.eql({ A: { $gte: 3 } }) 86 | }) 87 | 88 | it("expression 5.1.1.1.5: A lt 2", () => { 89 | expect(f).to.deep.eql({ A: { $lt: 2 } }) 90 | }) 91 | 92 | it("expression 5.1.1.1.6: A le 2", () => { 93 | expect(f).to.deep.eql({ A: { $lte: 2 } }) 94 | }) 95 | 96 | it("expression: A/b eq 1", () => { 97 | expect(f).to.deep.eql({ 'A.b': 1 }) 98 | }) 99 | 100 | it("expression 5.1.1.3: (A/b eq 2) or (B/c lt 4) and ((E gt 5) or (E lt -1))", () => { 101 | expect(f).to.deep.eql({ $or: [{ 'A.b': 2 }, { $and: [{ 'B.c': { $lt: 4 } }, { $or: [{ E: { $gt: 5 } }, { E: { $lt: -1 } }] }] }] }) 102 | }) 103 | 104 | it("expression 5.1.1.4.1: contains(A, 'BC')", () => { 105 | expect(f).to.deep.eql({ A: /BC/gi }); 106 | }) 107 | 108 | it("expression 5.1.1.4.1: contains(A, 'BC') or contains(D, 'EF')", () => { 109 | expect(f).to.deep.eql({ $or: [{ A: /BC/gi }, { D: /EF/gi }]}); 110 | }) 111 | 112 | it("expression 5.1.1.4.2: endswith(A, 'CD')", () => { 113 | expect(f).to.deep.eql({ A: /CD$/gi }); 114 | }) 115 | 116 | it("expression 5.1.1.4.3: startswith(A, 'CD')", () => { 117 | expect(f).to.deep.eql({ A: /^CD/gi }); 118 | }) 119 | 120 | it("expression 5.1.1.1.11: not endswith(Name,'ilk')", () => { 121 | expect(f).to.deep.eql({ Name: { $not: /ilk$/gi } }); 122 | }) 123 | }) 124 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es5", 5 | "declaration": true, 6 | "outDir": "lib" 7 | }, 8 | "exclude": [ 9 | "lib", 10 | "node_modules" 11 | ] 12 | } 13 | --------------------------------------------------------------------------------