├── .babelrc ├── .gitignore ├── dist ├── index.js └── index.js.map ├── index.d.ts ├── index.js ├── package.json └── readme.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["transform-object-rest-spread"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linq2js/linq2fire/7340e73e86067b309f3d65992133eeb4890f0c0c/.gitignore -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 8 | 9 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 10 | 11 | exports.default = create; 12 | 13 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 14 | 15 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 16 | 17 | var keyRegex = /^\s*([^^<>=\s]+)\s*(<>|<|>|<=|>=|==|=|\^=|array-contains|has)?\s*$/; 18 | var specialFields = { 19 | "@id": "__name__" 20 | }; 21 | var arrayMethods = "slice reduce filter some every".split(/\s+/); 22 | var copy = "__copy__"; 23 | var dbWrapper = function dbWrapper(db) { 24 | return { 25 | from: function from(collection, callback) { 26 | var col = create(db.collection(collection)); 27 | if (arguments.length < 2) { 28 | return col; 29 | } 30 | callback(col); 31 | return this; 32 | } 33 | }; 34 | }; 35 | var deepClone = function deepClone(obj) { 36 | var clone = Object.assign({}, obj); 37 | Object.keys(clone).forEach(function (key) { 38 | return clone[key] = _typeof(obj[key]) === "object" ? deepClone(obj[key]) : obj[key]; 39 | }); 40 | return Array.isArray(obj) ? (clone.length = obj.length) && Array.from(clone) : clone; 41 | }; 42 | var translateField = function translateField(field) { 43 | return specialFields[field] || field; 44 | }; 45 | var translateValue = function translateValue(field, value) { 46 | return field === "@id" ? String(value) : value; 47 | }; 48 | var cloneNode = function cloneNode(node) { 49 | return Object.assign({}, node, { 50 | children: node.children ? node.children.map(cloneNode) : undefined 51 | }); 52 | }; 53 | /** 54 | * algorithm: 55 | * collect all or node, then put them into the list 56 | * each or node contains childIndex (from 0 - number of child) 57 | * we perform infinite loop until no child index can be increased 58 | * for sample: 59 | * A (2) B (3) are nodes and its chid number (number insde parentheses) 60 | * 0 0 are values/child indexes 61 | * for each child index, if we can incease it by 1, we reset prev indexes to 0, 62 | * unless we try to increase next child index, 63 | * if no child index can be increased the loop is end 64 | * A B 65 | * 0 0 66 | * 1 0 67 | * 0 1 68 | * 1 1 69 | * 0 2 70 | * 1 2 71 | * totally 6 possible generated 72 | */ 73 | var findAllPossibles = function findAllPossibles(root) { 74 | root = cloneNode(root); 75 | function traverse(node, callback, parent, index) { 76 | if (callback(node, parent, index)) return true; 77 | if (node.children && node.children.length) { 78 | node.children.some(function (child, childIndex) { 79 | return traverse(child, callback, node, childIndex); 80 | }); 81 | } 82 | } 83 | 84 | var orNodes = []; 85 | 86 | // create indexes 87 | traverse(root, function (node, parent, index) { 88 | node.parent = function () { 89 | return parent; 90 | }; 91 | if (node.type === "or") { 92 | node.id = orNodes.length; 93 | node.__children = node.children; 94 | node.childIndex = 0; 95 | orNodes.push(node); 96 | } 97 | }); 98 | var result = []; 99 | var posible = void 0; 100 | while (true) { 101 | traverse(root, function (node) { 102 | if (node.type === "or") { 103 | node.children = [node.__children[node.childIndex]]; 104 | } 105 | if (node.type !== "or" && node.type !== "and") { 106 | if (!posible) { 107 | posible = []; 108 | result.push(posible); 109 | } 110 | posible.push(node); 111 | } 112 | }); 113 | posible = null; 114 | var increased = false; 115 | // increase possible number 116 | for (var i = 0; i < orNodes.length; i++) { 117 | // can increase 118 | var node = orNodes[i]; 119 | if (node.childIndex + 1 < node.__children.length) { 120 | node.childIndex++; 121 | // reset prev nodes 122 | orNodes.slice(0, i).forEach(function (node) { 123 | return node.childIndex = 0; 124 | }); 125 | increased = true; 126 | break; 127 | } 128 | } 129 | if (!increased) break; 130 | } 131 | 132 | return result; 133 | }; 134 | 135 | var parseCondition = function parseCondition(condition) { 136 | var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 137 | 138 | var result = []; 139 | Object.keys(condition).forEach(function (key) { 140 | var value = condition[key]; 141 | if (key === "or") { 142 | var children = []; 143 | if (Array.isArray(value)) { 144 | children.push.apply(children, _toConsumableArray(value)); 145 | } else { 146 | Object.keys(value).forEach(function (field) { 147 | children.push(_defineProperty({}, field, value[field])); 148 | }); 149 | } 150 | 151 | children.length && result.push({ 152 | type: "or", 153 | children: children.map(function (child) { 154 | return { 155 | type: "and", 156 | children: parseCondition(child, context) 157 | }; 158 | }) 159 | }); 160 | } else { 161 | // parse normal criteria 162 | var _ref = keyRegex.exec(key) || [], 163 | _ref2 = _slicedToArray(_ref, 3), 164 | field = _ref2[1], 165 | _ref2$ = _ref2[2], 166 | op = _ref2$ === undefined ? "==" : _ref2$; 167 | 168 | if (!field) { 169 | throw new Error("Invalid criteria " + key); 170 | } 171 | if (op === "has") { 172 | op = "array-contains"; 173 | } 174 | if (op === "=" || op === "===") { 175 | op = "=="; 176 | } 177 | if (op === "<>" || op === "!==") { 178 | op = "!="; 179 | } 180 | if (Array.isArray(value)) { 181 | if (op === "!=") { 182 | // not in 183 | // convert to multiple != operator 184 | result.push({ 185 | type: "and", 186 | children: value.map(function (value) { 187 | return { 188 | type: "or", 189 | children: [{ field: field, type: ">", value: value }, { field: field, type: "<", value: value }] 190 | }; 191 | }) 192 | }); 193 | } else if (op === "==") { 194 | // in 195 | result.push({ 196 | type: "or", 197 | children: value.map(function (value) { 198 | return { field: field, type: op, value: value }; 199 | }) 200 | }); 201 | } else if (op === "array-contains") { 202 | result.push({ 203 | type: "or", 204 | children: value.map(function (value) { 205 | return { 206 | type: "array-contains", 207 | field: field, 208 | value: value 209 | }; 210 | }) 211 | }); 212 | context.postFilters.push(function (doc) { 213 | var fieldValue = doc.data()[field]; 214 | return value.every(function (value) { 215 | return fieldValue.includes(value); 216 | }); 217 | }); 218 | } else { 219 | throw new Error("Unsupported " + op + " for Array"); 220 | } 221 | } else { 222 | if (op === "!=") { 223 | result.push({ 224 | type: "or", 225 | children: [{ field: field, type: ">", value: value }, { field: field, type: "<", value: value }] 226 | }); 227 | } 228 | // process startsWith operator 229 | else if (op === "^=") { 230 | value = String(value); 231 | var length = value.length; 232 | var frontCode = value.slice(0, length - 1); 233 | var endChar = value.slice(length - 1, value.length); 234 | var endcode = frontCode + String.fromCharCode(endChar.charCodeAt(0) + 1); 235 | result.push({ field: field, type: ">=", value: value }, { field: field, type: "<", value: endcode }); 236 | } else { 237 | result.push({ field: field, type: op, value: value }); 238 | } 239 | } 240 | } 241 | }); 242 | return result; 243 | }; 244 | 245 | function create(queryable, collection) { 246 | var _query; 247 | 248 | if (queryable.collection) { 249 | if (collection) { 250 | queryable = queryable.collection(collection); 251 | } else { 252 | return dbWrapper(queryable); 253 | } 254 | } 255 | var unsubscribes = []; 256 | var limit = 0; 257 | var startAt = void 0; 258 | var _orderBy = void 0; 259 | var _where = []; 260 | var lastGet = void 0, 261 | lastDocs = void 0; 262 | var compiledQueries = void 0; 263 | var select = []; 264 | var _pipe = []; 265 | var _map = []; 266 | var postFilters = []; 267 | 268 | function processResults(results) { 269 | var docs = {}; 270 | var count = 0; 271 | lastDocs = results.map(function (result) { 272 | return result ? result.docs[result.docs.length - 1] : undefined; 273 | }); 274 | results.some(function (result) { 275 | if (!result) return; 276 | result.forEach(function (doc) { 277 | if (limit && count >= limit) return; 278 | if (postFilters.length && postFilters.some(function (filter) { 279 | return !filter(doc); 280 | })) return; 281 | if (!(doc.id in docs)) { 282 | count++; 283 | } 284 | docs[doc.id] = doc; 285 | }); 286 | return limit && count >= limit; 287 | }); 288 | 289 | var result = Object.values(docs); 290 | 291 | if (select.length) { 292 | result = result.map(function (doc) { 293 | return select.reduce(function (mappedObj, selector) { 294 | return selector(mappedObj, doc.data(), doc); 295 | }, {}); 296 | }); 297 | } 298 | 299 | if (_map.length) { 300 | result = _map.reduce(function (result, mapper) { 301 | return result.map(function (item, index) { 302 | return typeof mapper === "function" ? mapper(item, index) : item[mapper](); 303 | }); 304 | }, result); 305 | } 306 | 307 | if (_pipe.length) { 308 | result = _pipe.reduce(function (result, f) { 309 | return f(result); 310 | }, result); 311 | } 312 | 313 | return result; 314 | } 315 | 316 | function modify(docs, callback) { 317 | return Promise.resolve(docs).then(function (docs) { 318 | var batch = queryable.firestore.batch(); 319 | var _iteratorNormalCompletion = true; 320 | var _didIteratorError = false; 321 | var _iteratorError = undefined; 322 | 323 | try { 324 | for (var _iterator = docs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 325 | var doc = _step.value; 326 | 327 | callback(batch, doc); 328 | } 329 | } catch (err) { 330 | _didIteratorError = true; 331 | _iteratorError = err; 332 | } finally { 333 | try { 334 | if (!_iteratorNormalCompletion && _iterator.return) { 335 | _iterator.return(); 336 | } 337 | } finally { 338 | if (_didIteratorError) { 339 | throw _iteratorError; 340 | } 341 | } 342 | } 343 | 344 | return batch.commit(); 345 | }); 346 | } 347 | 348 | function createOrderedQuery(q) { 349 | if (!_orderBy) return q; 350 | var pairs = Object.entries(_orderBy); 351 | return pairs.reduce(function (q, order) { 352 | return q.orderBy.apply(q, _toConsumableArray(order)); 353 | }, q); 354 | } 355 | 356 | function buildQueries(noCache) { 357 | if (!noCache && compiledQueries) return compiledQueries; 358 | 359 | if (!_where.length) { 360 | var q = queryable; 361 | if (limit) { 362 | q = q.limit(limit); 363 | } 364 | if (startAt !== undefined) { 365 | q = q.startAt(startAt); 366 | } 367 | 368 | return [createOrderedQuery(q)]; 369 | } 370 | 371 | // should copy where before process 372 | var posible = findAllPossibles({ 373 | type: "and", 374 | children: _where 375 | }); 376 | 377 | return compiledQueries = posible.map(function (p) { 378 | var q = p.reduce(function (q, node) { 379 | return q.where(translateField(node.field), node.type, translateValue(node.field, node.value)); 380 | }, queryable); 381 | 382 | if (limit) { 383 | q = q.limit(limit); 384 | } 385 | if (startAt !== undefined) { 386 | q = q.startAt(startAt); 387 | } 388 | 389 | return createOrderedQuery(q); 390 | }); 391 | } 392 | 393 | function clone(overwriteData) { 394 | return create(queryable)[copy](Object.assign({ 395 | limit: limit, 396 | where: _where, 397 | orderBy: _orderBy, 398 | startAt: startAt, 399 | select: select, 400 | pipe: _pipe, 401 | map: _map, 402 | postFilters: postFilters 403 | }, overwriteData)); 404 | } 405 | 406 | var query = (_query = {}, _defineProperty(_query, copy, function (data) { 407 | limit = data.limit; 408 | _where = data.where; 409 | _orderBy = data.orderBy; 410 | startAt = data.startAt; 411 | select = data.select; 412 | _pipe = data.pipe; 413 | _map = data.map; 414 | postFilters = data.postFilters; 415 | return this; 416 | }), _defineProperty(_query, "pipe", function pipe() { 417 | for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) { 418 | funcs[_key] = arguments[_key]; 419 | } 420 | 421 | return clone({ 422 | pipe: _pipe.slice().concat(funcs) 423 | }); 424 | }), _defineProperty(_query, "map", function map() { 425 | for (var _len2 = arguments.length, mappers = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 426 | mappers[_key2] = arguments[_key2]; 427 | } 428 | 429 | return clone({ 430 | map: _map.slice().concat(mappers) 431 | }); 432 | }), _defineProperty(_query, "subscribe", function subscribe(options, callback) { 433 | if (typeof options === "function") { 434 | callback = options; 435 | options = {}; 436 | } 437 | var currentUnsubscribes = buildQueries().map(function (queryable) { 438 | return queryable.onSnapshot(options, callback); 439 | }); 440 | unsubscribes.push.apply(unsubscribes, _toConsumableArray(currentUnsubscribes)); 441 | return function () { 442 | currentUnsubscribes.forEach(function (unsubscribe) { 443 | unsubscribe(); 444 | var index = unsubscribes.indexOf(unsubscribe); 445 | if (index !== -1) { 446 | unsubscribes.splice(index, 1); 447 | } 448 | }); 449 | }; 450 | }), _defineProperty(_query, "unsubscribeAll", function unsubscribeAll() { 451 | var copyOfUnsubscribes = unsubscribes.slice(); 452 | unsubscribes.length = 0; 453 | copyOfUnsubscribes.forEach(function (unsubscribe) { 454 | return unsubscribe(); 455 | }); 456 | return this; 457 | }), _defineProperty(_query, "select", function select() { 458 | var selector = void 0; 459 | // single field value selector 460 | 461 | for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 462 | args[_key3] = arguments[_key3]; 463 | } 464 | 465 | if (args[0] === true) { 466 | var field = args[1]; 467 | selector = function selector(mappedObj, data, doc) { 468 | return field === "@id" ? doc.id : data[field]; 469 | }; 470 | } else if (typeof args[0] === "function") { 471 | var customSelector = args[0]; 472 | selector = function selector(mappedObj, data, doc) { 473 | return customSelector(data, doc); 474 | }; 475 | } else if (typeof args[0] === "string") { 476 | var fields = args; 477 | selector = function selector(mappedObj, data, doc) { 478 | fields.forEach(function (field) { 479 | return mappedObj[field] = field === "@id" ? doc.id : data[field]; 480 | }); 481 | return mappedObj; 482 | }; 483 | } else { 484 | var pairs = Object.entries(args[0]); 485 | selector = function selector(mappedObj, data, doc) { 486 | pairs.forEach(function (pair) { 487 | return mappedObj[pair[1]] = pair[0] === "@id" ? doc.id : data[pair[0]]; 488 | }); 489 | return mappedObj; 490 | }; 491 | } 492 | return clone({ 493 | select: [selector] 494 | }); 495 | }), _defineProperty(_query, "limit", function limit(count) { 496 | return clone({ limit: count }); 497 | }), _defineProperty(_query, "first", function first() { 498 | return this.limit(1).get().then(function (results) { 499 | return results[0]; 500 | }); 501 | }), _defineProperty(_query, "where", function where() { 502 | var newWhere = _where.slice(); 503 | var context = { 504 | postFilters: postFilters.slice() 505 | }; 506 | 507 | for (var _len4 = arguments.length, conditions = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 508 | conditions[_key4] = arguments[_key4]; 509 | } 510 | 511 | conditions.forEach(function (condition) { 512 | return newWhere.push.apply(newWhere, _toConsumableArray(parseCondition(condition, context))); 513 | }); 514 | return clone({ 515 | where: newWhere, 516 | postFilters: context.postFilters 517 | }); 518 | }), _defineProperty(_query, "orderBy", function orderBy(fields) { 519 | return clone({ 520 | orderBy: Object.assign({}, _orderBy, fields) 521 | }); 522 | }), _defineProperty(_query, "get", function get() { 523 | var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 524 | source = _ref3.source; 525 | 526 | var promises = buildQueries().map(function (queryable) { 527 | return queryable.get(source); 528 | }); 529 | return lastGet = Promise.all(promises).then(processResults); 530 | }), _defineProperty(_query, "data", function data(options) { 531 | return this.get(options).then(function (results) { 532 | return results.map(function (x) { 533 | return x.data(); 534 | }); 535 | }); 536 | }), _defineProperty(_query, "next", function next() { 537 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 538 | var source = options.source; 539 | 540 | if (lastGet) { 541 | return lastGet = lastGet.then(function (docs) { 542 | if (!docs.length) return []; 543 | var queries = buildQueries(); 544 | var promises = queries.map(function (queryable, index) { 545 | if (!lastDocs[index]) return undefined; 546 | return queryable.startAfter(lastDocs[index]).get(source); 547 | }); 548 | return Promise.all(promises).then(processResults); 549 | }); 550 | } 551 | return this.get(options); 552 | }), _defineProperty(_query, "set", function set(docsOrData, applyToResultSet) { 553 | if (applyToResultSet) { 554 | return modify(this.get(), function (batch, doc) { 555 | return batch.set(doc.ref, docsOrData); 556 | }); 557 | } 558 | return modify(Object.keys(docsOrData).map(function (id) { 559 | return queryable.doc(String(id)); 560 | }), function (batch, doc) { 561 | return batch.set(doc, docsOrData[doc.id]); 562 | }); 563 | }), _defineProperty(_query, "update", function update(docsOrData, applyToResultSet) { 564 | if (applyToResultSet) { 565 | return modify(this.get(), function (batch, doc) { 566 | return batch.update(doc.ref, docsOrData); 567 | }); 568 | } 569 | return modify(Object.keys(docsOrData).map(function (id) { 570 | return queryable.doc(String(id)); 571 | }), function (batch, doc) { 572 | return batch.update(doc, docsOrData[doc.id]); 573 | }); 574 | }), _defineProperty(_query, "remove", function remove() { 575 | return modify(this.get(), function (batch, doc) { 576 | return batch.delete(doc.ref); 577 | }); 578 | }), _query); 579 | 580 | arrayMethods.forEach(function (method) { 581 | query[method] = function () { 582 | for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 583 | args[_key5] = arguments[_key5]; 584 | } 585 | 586 | return query.get().then(function (results) { 587 | return results[method].apply(results, args); 588 | }); 589 | }; 590 | }); 591 | 592 | return query; 593 | } 594 | 595 | Object.assign(create, { 596 | fields: function fields(newSpecialFields) { 597 | Object.assign(specialFields, newSpecialFields); 598 | return this; 599 | } 600 | }); 601 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../index.js"],"names":["create","keyRegex","specialFields","arrayMethods","split","copy","dbWrapper","from","collection","callback","col","db","arguments","length","deepClone","clone","Object","assign","obj","keys","forEach","key","Array","isArray","translateField","field","translateValue","value","String","cloneNode","node","children","map","undefined","findAllPossibles","root","traverse","parent","index","some","child","childIndex","orNodes","type","id","__children","push","result","posible","increased","i","slice","parseCondition","condition","context","exec","op","Error","postFilters","fieldValue","doc","data","every","includes","frontCode","endChar","endcode","fromCharCode","charCodeAt","queryable","unsubscribes","limit","startAt","orderBy","where","lastGet","lastDocs","compiledQueries","select","pipe","processResults","results","docs","count","filter","values","reduce","mappedObj","selector","mapper","item","f","modify","Promise","resolve","then","batch","firestore","commit","createOrderedQuery","q","pairs","entries","order","buildQueries","noCache","p","overwriteData","query","funcs","concat","mappers","options","currentUnsubscribes","onSnapshot","unsubscribe","indexOf","splice","copyOfUnsubscribes","args","customSelector","fields","pair","get","newWhere","conditions","source","promises","all","x","queries","startAfter","docsOrData","applyToResultSet","set","ref","update","delete","method","newSpecialFields"],"mappings":"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","file":"index.js","sourcesContent":["const keyRegex = /^\\s*([^^<>=\\s]+)\\s*(<>|<|>|<=|>=|==|=|\\^=|array-contains|has)?\\s*$/;\r\nconst specialFields = {\r\n \"@id\": \"__name__\"\r\n};\r\nconst arrayMethods = \"slice reduce filter some every\".split(/\\s+/);\r\nconst copy = \"__copy__\";\r\nconst dbWrapper = db => {\r\n return {\r\n from(collection, callback) {\r\n const col = create(db.collection(collection));\r\n if (arguments.length < 2) {\r\n return col;\r\n }\r\n callback(col);\r\n return this;\r\n }\r\n };\r\n};\r\nconst deepClone = obj => {\r\n let clone = Object.assign({}, obj);\r\n Object.keys(clone).forEach(\r\n key =>\r\n (clone[key] =\r\n typeof obj[key] === \"object\" ? deepClone(obj[key]) : obj[key])\r\n );\r\n return Array.isArray(obj)\r\n ? (clone.length = obj.length) && Array.from(clone)\r\n : clone;\r\n};\r\nconst translateField = field => specialFields[field] || field;\r\nconst translateValue = (field, value) =>\r\n field === \"@id\" ? String(value) : value;\r\nconst cloneNode = node => {\r\n return Object.assign({}, node, {\r\n children: node.children ? node.children.map(cloneNode) : undefined\r\n });\r\n};\r\n/**\r\n * algorithm:\r\n * collect all or node, then put them into the list\r\n * each or node contains childIndex (from 0 - number of child)\r\n * we perform infinite loop until no child index can be increased\r\n * for sample:\r\n * A (2) B (3) are nodes and its chid number (number insde parentheses)\r\n * 0 0 are values/child indexes\r\n * for each child index, if we can incease it by 1, we reset prev indexes to 0,\r\n * unless we try to increase next child index,\r\n * if no child index can be increased the loop is end\r\n * A B\r\n * 0 0\r\n * 1 0\r\n * 0 1\r\n * 1 1\r\n * 0 2\r\n * 1 2\r\n * totally 6 possible generated\r\n */\r\nconst findAllPossibles = root => {\r\n root = cloneNode(root);\r\n function traverse(node, callback, parent, index) {\r\n if (callback(node, parent, index)) return true;\r\n if (node.children && node.children.length) {\r\n node.children.some((child, childIndex) =>\r\n traverse(child, callback, node, childIndex)\r\n );\r\n }\r\n }\r\n\r\n const orNodes = [];\r\n\r\n // create indexes\r\n traverse(root, (node, parent, index) => {\r\n node.parent = () => parent;\r\n if (node.type === \"or\") {\r\n node.id = orNodes.length;\r\n node.__children = node.children;\r\n node.childIndex = 0;\r\n orNodes.push(node);\r\n }\r\n });\r\n const result = [];\r\n let posible;\r\n while (true) {\r\n traverse(root, node => {\r\n if (node.type === \"or\") {\r\n node.children = [node.__children[node.childIndex]];\r\n }\r\n if (node.type !== \"or\" && node.type !== \"and\") {\r\n if (!posible) {\r\n posible = [];\r\n result.push(posible);\r\n }\r\n posible.push(node);\r\n }\r\n });\r\n posible = null;\r\n let increased = false;\r\n // increase possible number\r\n for (let i = 0; i < orNodes.length; i++) {\r\n // can increase\r\n const node = orNodes[i];\r\n if (node.childIndex + 1 < node.__children.length) {\r\n node.childIndex++;\r\n // reset prev nodes\r\n orNodes.slice(0, i).forEach(node => (node.childIndex = 0));\r\n increased = true;\r\n break;\r\n }\r\n }\r\n if (!increased) break;\r\n }\r\n\r\n return result;\r\n};\r\n\r\nconst parseCondition = (condition, context = {}) => {\r\n const result = [];\r\n Object.keys(condition).forEach(key => {\r\n let value = condition[key];\r\n if (key === \"or\") {\r\n const children = [];\r\n if (Array.isArray(value)) {\r\n children.push(...value);\r\n } else {\r\n Object.keys(value).forEach(field => {\r\n children.push({ [field]: value[field] });\r\n });\r\n }\r\n\r\n children.length &&\r\n result.push({\r\n type: \"or\",\r\n children: children.map(child => ({\r\n type: \"and\",\r\n children: parseCondition(child, context)\r\n }))\r\n });\r\n } else {\r\n // parse normal criteria\r\n let [, field, op = \"==\"] = keyRegex.exec(key) || [];\r\n if (!field) {\r\n throw new Error(\"Invalid criteria \" + key);\r\n }\r\n if (op === \"has\") {\r\n op = \"array-contains\";\r\n }\r\n if (op === \"=\" || op === \"===\") {\r\n op = \"==\";\r\n }\r\n if (op === \"<>\" || op === \"!==\") {\r\n op = \"!=\";\r\n }\r\n if (Array.isArray(value)) {\r\n if (op === \"!=\") {\r\n // not in\r\n // convert to multiple != operator\r\n result.push({\r\n type: \"and\",\r\n children: value.map(value => ({\r\n type: \"or\",\r\n children: [\r\n { field, type: \">\", value },\r\n { field, type: \"<\", value }\r\n ]\r\n }))\r\n });\r\n } else if (op === \"==\") {\r\n // in\r\n result.push({\r\n type: \"or\",\r\n children: value.map(value => ({ field, type: op, value }))\r\n });\r\n } else if (op === \"array-contains\") {\r\n result.push({\r\n type: \"or\",\r\n children: value.map(value => ({\r\n type: \"array-contains\",\r\n field,\r\n value\r\n }))\r\n });\r\n context.postFilters.push(doc => {\r\n const fieldValue = doc.data()[field];\r\n return value.every(value => fieldValue.includes(value));\r\n });\r\n } else {\r\n throw new Error(\"Unsupported \" + op + \" for Array\");\r\n }\r\n } else {\r\n if (op === \"!=\") {\r\n result.push({\r\n type: \"or\",\r\n children: [{ field, type: \">\", value }, { field, type: \"<\", value }]\r\n });\r\n }\r\n // process startsWith operator\r\n else if (op === \"^=\") {\r\n value = String(value);\r\n const length = value.length;\r\n const frontCode = value.slice(0, length - 1);\r\n const endChar = value.slice(length - 1, value.length);\r\n const endcode =\r\n frontCode + String.fromCharCode(endChar.charCodeAt(0) + 1);\r\n result.push(\r\n { field, type: \">=\", value },\r\n { field, type: \"<\", value: endcode }\r\n );\r\n } else {\r\n result.push({ field, type: op, value });\r\n }\r\n }\r\n }\r\n });\r\n return result;\r\n};\r\n\r\nexport default function create(queryable, collection) {\r\n if (queryable.collection) {\r\n if (collection) {\r\n queryable = queryable.collection(collection);\r\n } else {\r\n return dbWrapper(queryable);\r\n }\r\n }\r\n const unsubscribes = [];\r\n let limit = 0;\r\n let startAt;\r\n let orderBy;\r\n let where = [];\r\n let lastGet, lastDocs;\r\n let compiledQueries;\r\n let select = [];\r\n let pipe = [];\r\n let map = [];\r\n let postFilters = [];\r\n\r\n function processResults(results) {\r\n const docs = {};\r\n let count = 0;\r\n lastDocs = results.map(result =>\r\n result ? result.docs[result.docs.length - 1] : undefined\r\n );\r\n results.some(result => {\r\n if (!result) return;\r\n result.forEach(doc => {\r\n if (limit && count >= limit) return;\r\n if (postFilters.length && postFilters.some(filter => !filter(doc)))\r\n return;\r\n if (!(doc.id in docs)) {\r\n count++;\r\n }\r\n docs[doc.id] = doc;\r\n });\r\n return limit && count >= limit;\r\n });\r\n\r\n let result = Object.values(docs);\r\n\r\n if (select.length) {\r\n result = result.map(doc => {\r\n return select.reduce(\r\n (mappedObj, selector) => selector(mappedObj, doc.data(), doc),\r\n {}\r\n );\r\n });\r\n }\r\n\r\n if (map.length) {\r\n result = map.reduce(\r\n (result, mapper) =>\r\n result.map((item, index) =>\r\n typeof mapper === \"function\" ? mapper(item, index) : item[mapper]()\r\n ),\r\n result\r\n );\r\n }\r\n\r\n if (pipe.length) {\r\n result = pipe.reduce((result, f) => f(result), result);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n function modify(docs, callback) {\r\n return Promise.resolve(docs).then(docs => {\r\n const batch = queryable.firestore.batch();\r\n for (let doc of docs) {\r\n callback(batch, doc);\r\n }\r\n return batch.commit();\r\n });\r\n }\r\n\r\n function createOrderedQuery(q) {\r\n if (!orderBy) return q;\r\n const pairs = Object.entries(orderBy);\r\n return pairs.reduce((q, order) => q.orderBy(...order), q);\r\n }\r\n\r\n function buildQueries(noCache) {\r\n if (!noCache && compiledQueries) return compiledQueries;\r\n\r\n if (!where.length) {\r\n let q = queryable;\r\n if (limit) {\r\n q = q.limit(limit);\r\n }\r\n if (startAt !== undefined) {\r\n q = q.startAt(startAt);\r\n }\r\n\r\n return [createOrderedQuery(q)];\r\n }\r\n\r\n // should copy where before process\r\n const posible = findAllPossibles({\r\n type: \"and\",\r\n children: where\r\n });\r\n\r\n return (compiledQueries = posible.map(p => {\r\n let q = p.reduce((q, node) => {\r\n return q.where(\r\n translateField(node.field),\r\n node.type,\r\n translateValue(node.field, node.value)\r\n );\r\n }, queryable);\r\n\r\n if (limit) {\r\n q = q.limit(limit);\r\n }\r\n if (startAt !== undefined) {\r\n q = q.startAt(startAt);\r\n }\r\n\r\n return createOrderedQuery(q);\r\n }));\r\n }\r\n\r\n function clone(overwriteData) {\r\n return create(queryable)[copy](\r\n Object.assign(\r\n {\r\n limit,\r\n where,\r\n orderBy,\r\n startAt,\r\n select,\r\n pipe,\r\n map,\r\n postFilters\r\n },\r\n overwriteData\r\n )\r\n );\r\n }\r\n\r\n const query = {\r\n [copy](data) {\r\n limit = data.limit;\r\n where = data.where;\r\n orderBy = data.orderBy;\r\n startAt = data.startAt;\r\n select = data.select;\r\n pipe = data.pipe;\r\n map = data.map;\r\n postFilters = data.postFilters;\r\n return this;\r\n },\r\n pipe(...funcs) {\r\n return clone({\r\n pipe: pipe.slice().concat(funcs)\r\n });\r\n },\r\n map(...mappers) {\r\n return clone({\r\n map: map.slice().concat(mappers)\r\n });\r\n },\r\n subscribe(options, callback) {\r\n if (typeof options === \"function\") {\r\n callback = options;\r\n options = {};\r\n }\r\n const currentUnsubscribes = buildQueries().map(queryable =>\r\n queryable.onSnapshot(options, callback)\r\n );\r\n unsubscribes.push(...currentUnsubscribes);\r\n return function() {\r\n currentUnsubscribes.forEach(unsubscribe => {\r\n unsubscribe();\r\n const index = unsubscribes.indexOf(unsubscribe);\r\n if (index !== -1) {\r\n unsubscribes.splice(index, 1);\r\n }\r\n });\r\n };\r\n },\r\n unsubscribeAll() {\r\n const copyOfUnsubscribes = unsubscribes.slice();\r\n unsubscribes.length = 0;\r\n copyOfUnsubscribes.forEach(unsubscribe => unsubscribe());\r\n return this;\r\n },\r\n /**\r\n * supports:\r\n * single field value selector: select(true, 'field') => fieldValue\r\n * multiple fields selector: select('field1', 'field2', ...) => { field1: field1Value, field2: field2Value }\r\n * obj map selector: select({ field: 'newFieldName' }) => { newFieldName: fieldValue }\r\n * custom selector: select(Function)\r\n */\r\n select(...args) {\r\n let selector;\r\n // single field value selector\r\n if (args[0] === true) {\r\n const field = args[1];\r\n selector = (mappedObj, data, doc) =>\r\n field === \"@id\" ? doc.id : data[field];\r\n } else if (typeof args[0] === \"function\") {\r\n const customSelector = args[0];\r\n selector = (mappedObj, data, doc) => customSelector(data, doc);\r\n } else if (typeof args[0] === \"string\") {\r\n const fields = args;\r\n selector = (mappedObj, data, doc) => {\r\n fields.forEach(\r\n field => (mappedObj[field] = field === \"@id\" ? doc.id : data[field])\r\n );\r\n return mappedObj;\r\n };\r\n } else {\r\n const pairs = Object.entries(args[0]);\r\n selector = (mappedObj, data, doc) => {\r\n pairs.forEach(\r\n pair =>\r\n (mappedObj[pair[1]] = pair[0] === \"@id\" ? doc.id : data[pair[0]])\r\n );\r\n return mappedObj;\r\n };\r\n }\r\n return clone({\r\n select: [selector]\r\n });\r\n },\r\n limit(count) {\r\n return clone({ limit: count });\r\n },\r\n first() {\r\n return this.limit(1)\r\n .get()\r\n .then(results => {\r\n return results[0];\r\n });\r\n },\r\n where(...conditions) {\r\n const newWhere = where.slice();\r\n const context = {\r\n postFilters: postFilters.slice()\r\n };\r\n conditions.forEach(condition =>\r\n newWhere.push(...parseCondition(condition, context))\r\n );\r\n return clone({\r\n where: newWhere,\r\n postFilters: context.postFilters\r\n });\r\n },\r\n orderBy(fields) {\r\n return clone({\r\n orderBy: Object.assign({}, orderBy, fields)\r\n });\r\n },\r\n get: function get({ source } = {}) {\r\n const promises = buildQueries().map(queryable => queryable.get(source));\r\n return (lastGet = Promise.all(promises).then(processResults));\r\n },\r\n data(options) {\r\n return this.get(options).then(results => results.map(x => x.data()));\r\n },\r\n next(options = {}) {\r\n const { source } = options;\r\n if (lastGet) {\r\n return (lastGet = lastGet.then(docs => {\r\n if (!docs.length) return [];\r\n const queries = buildQueries();\r\n const promises = queries.map((queryable, index) => {\r\n if (!lastDocs[index]) return undefined;\r\n return queryable.startAfter(lastDocs[index]).get(source);\r\n });\r\n return Promise.all(promises).then(processResults);\r\n }));\r\n }\r\n return this.get(options);\r\n },\r\n set(docsOrData, applyToResultSet) {\r\n if (applyToResultSet) {\r\n return modify(this.get(), (batch, doc) =>\r\n batch.set(doc.ref, docsOrData)\r\n );\r\n }\r\n return modify(\r\n Object.keys(docsOrData).map(id => queryable.doc(String(id))),\r\n (batch, doc) => batch.set(doc, docsOrData[doc.id])\r\n );\r\n },\r\n update(docsOrData, applyToResultSet) {\r\n if (applyToResultSet) {\r\n return modify(this.get(), (batch, doc) =>\r\n batch.update(doc.ref, docsOrData)\r\n );\r\n }\r\n return modify(\r\n Object.keys(docsOrData).map(id => queryable.doc(String(id))),\r\n (batch, doc) => batch.update(doc, docsOrData[doc.id])\r\n );\r\n },\r\n remove() {\r\n return modify(this.get(), (batch, doc) => batch.delete(doc.ref));\r\n }\r\n };\r\n\r\n arrayMethods.forEach(method => {\r\n query[method] = (...args) =>\r\n query.get().then(results => results[method](...args));\r\n });\r\n\r\n return query;\r\n}\r\n\r\nObject.assign(create, {\r\n fields(newSpecialFields) {\r\n Object.assign(specialFields, newSpecialFields);\r\n return this;\r\n }\r\n});\r\n"]} -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | interface IQueryOption { 2 | source: string; 3 | } 4 | 5 | interface IQuery { 6 | /** 7 | * subscribe query onSnapshot with options and return unsubscribe function 8 | * @param options 9 | * @param callback 10 | */ 11 | subscribe(options, callback: Function): Function; 12 | 13 | /** 14 | * subscribe query onSnapshot and return unsubscribe function 15 | * @param callback 16 | */ 17 | subscribe(callback: Function): Function; 18 | 19 | /** 20 | * remove all subscriptions 21 | */ 22 | unsubscribeAll(): IQuery; 23 | 24 | /** 25 | * query will return set of result of specific selector 26 | * using '@id' to select document id 27 | * .select(function(documentData, originalDocumentObject) { 28 | * return { 29 | * 30 | * }; 31 | * }) 32 | * @param selector 33 | */ 34 | select(selector: (data: T, document?: any) => U): IQuery; 35 | 36 | /** 37 | * query will return set of object which has selected fields 38 | * using '@id' to select document id 39 | * .select('field1', 'field2', 'field3', ...) 40 | * @param fields 41 | */ 42 | select(...fields: string[]): IQuery; 43 | 44 | /** 45 | * query will return set of specified field value 46 | * using '@id' to select document id 47 | * @param isSelectSingleField must be true 48 | * @param field 49 | */ 50 | select(isSelectSingleField: boolean, field: string): IQuery; 51 | 52 | /** 53 | * .select({ 54 | * field1: 'newField1Name', 55 | * field2: 'newField2Name' 56 | * }) 57 | * using '@id' to select document id 58 | * @param fieldMapper 59 | */ 60 | select(fieldMapper: object): IQuery; 61 | 62 | /** 63 | * limit query results 64 | * @param count 65 | */ 66 | limit(count: number): IQuery; 67 | 68 | /** 69 | * return first query result 70 | */ 71 | first(): Promise; 72 | 73 | /** 74 | * filter query by one or many conditions 75 | * .where({ 76 | * // equivalent to field = value 77 | * field: value, 78 | * // equivalent to field > value 79 | * 'field>': value, 80 | * // equivalent to field = value 81 | * 'field==': value, 82 | * // equivalent to field = value 83 | * 'field===': value, 84 | * // equivalent to field != value 85 | * 'field<>': value, 86 | * // equivalent to field != value 87 | * 'field!=': value, 88 | * // equivalent to field != value 89 | * 'field!==': value, 90 | * // equivalent to field startsWith value 91 | * 'field^=': value 92 | * // equivalent to field IN arrayOfValue 93 | * field: arrayOfValue, 94 | * 95 | * or: [ 96 | * condition1, 97 | * condition2 98 | * ] 99 | * }) 100 | * @param conditions 101 | */ 102 | where(...conditions: object[]): IQuery; 103 | 104 | /** 105 | * order result set by specified fields 106 | * .orderBy({ 107 | * field1: 'asc', 108 | * field2: 'desc' 109 | * }) 110 | */ 111 | orderBy(fields: object): IQuery; 112 | 113 | /** 114 | * start query and return promise of result set 115 | * @param options 116 | */ 117 | get(options?: IQueryOption): Promise; 118 | 119 | /** 120 | * return next result set for pagination 121 | */ 122 | next(options?: IQueryOption): Promise; 123 | 124 | /** 125 | * modify or create multiple documents by their ids. queryable object must be collection 126 | * .set({ 127 | * id1: data1, 128 | * id2: data2 129 | * }); 130 | * 131 | * modify documents which is satisfied query condition 132 | * .set(data, true); 133 | * 134 | * @param documentListOrData 135 | * @param applyToResultSet 136 | */ 137 | set(documentListOrData: object, applyToResultSet?: boolean): IQuery; 138 | 139 | /** 140 | * update multiple documents by their ids. queryable object must be collection 141 | * .set({ 142 | * id1: data1, 143 | * id2: data2 144 | * }); 145 | * 146 | * update documents which is satisfied query condition 147 | * .set(data, true); 148 | * @param documentListOrData 149 | * @param applyToResultSet 150 | */ 151 | update(documentListOrData: object, applyToResultSet?: boolean): IQuery; 152 | 153 | /** 154 | * return result of original data, no selector applied 155 | * @param options 156 | */ 157 | data(options?: IQueryOption): Promise; 158 | 159 | /** 160 | * remove all documents which is satisfied query condition 161 | */ 162 | remove(): IQuery; 163 | 164 | /** 165 | * create new query from collection. queryable must be firestore object 166 | */ 167 | from(collection: string): IQuery; 168 | } 169 | 170 | interface IQueryCreator { 171 | (queryable: T): IQuery; 172 | 173 | (firestore: any, collectionName: string): IQuery; 174 | } 175 | 176 | declare let queryCreator: IQueryCreator; 177 | 178 | export default queryCreator; 179 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const keyRegex = /^\s*([^^<>=\s]+)\s*(<>|<|>|<=|>=|==|=|\^=|array-contains|has)?\s*$/; 2 | const specialFields = { 3 | "@id": "__name__" 4 | }; 5 | const arrayMethods = "slice reduce filter some every".split(/\s+/); 6 | const copy = "__copy__"; 7 | const dbWrapper = db => { 8 | return { 9 | from(collection, callback) { 10 | const col = create(db.collection(collection)); 11 | if (arguments.length < 2) { 12 | return col; 13 | } 14 | callback(col); 15 | return this; 16 | } 17 | }; 18 | }; 19 | const deepClone = obj => { 20 | let clone = Object.assign({}, obj); 21 | Object.keys(clone).forEach( 22 | key => 23 | (clone[key] = 24 | typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key]) 25 | ); 26 | return Array.isArray(obj) 27 | ? (clone.length = obj.length) && Array.from(clone) 28 | : clone; 29 | }; 30 | const translateField = field => specialFields[field] || field; 31 | const translateValue = (field, value) => 32 | field === "@id" ? String(value) : value; 33 | const cloneNode = node => { 34 | return Object.assign({}, node, { 35 | children: node.children ? node.children.map(cloneNode) : undefined 36 | }); 37 | }; 38 | /** 39 | * algorithm: 40 | * collect all or node, then put them into the list 41 | * each or node contains childIndex (from 0 - number of child) 42 | * we perform infinite loop until no child index can be increased 43 | * for sample: 44 | * A (2) B (3) are nodes and its chid number (number insde parentheses) 45 | * 0 0 are values/child indexes 46 | * for each child index, if we can incease it by 1, we reset prev indexes to 0, 47 | * unless we try to increase next child index, 48 | * if no child index can be increased the loop is end 49 | * A B 50 | * 0 0 51 | * 1 0 52 | * 0 1 53 | * 1 1 54 | * 0 2 55 | * 1 2 56 | * totally 6 possible generated 57 | */ 58 | const findAllPossibles = root => { 59 | root = cloneNode(root); 60 | function traverse(node, callback, parent, index) { 61 | if (callback(node, parent, index)) return true; 62 | if (node.children && node.children.length) { 63 | node.children.some((child, childIndex) => 64 | traverse(child, callback, node, childIndex) 65 | ); 66 | } 67 | } 68 | 69 | const orNodes = []; 70 | 71 | // create indexes 72 | traverse(root, (node, parent, index) => { 73 | node.parent = () => parent; 74 | if (node.type === "or") { 75 | node.id = orNodes.length; 76 | node.__children = node.children; 77 | node.childIndex = 0; 78 | orNodes.push(node); 79 | } 80 | }); 81 | const result = []; 82 | let posible; 83 | while (true) { 84 | traverse(root, node => { 85 | if (node.type === "or") { 86 | node.children = [node.__children[node.childIndex]]; 87 | } 88 | if (node.type !== "or" && node.type !== "and") { 89 | if (!posible) { 90 | posible = []; 91 | result.push(posible); 92 | } 93 | posible.push(node); 94 | } 95 | }); 96 | posible = null; 97 | let increased = false; 98 | // increase possible number 99 | for (let i = 0; i < orNodes.length; i++) { 100 | // can increase 101 | const node = orNodes[i]; 102 | if (node.childIndex + 1 < node.__children.length) { 103 | node.childIndex++; 104 | // reset prev nodes 105 | orNodes.slice(0, i).forEach(node => (node.childIndex = 0)); 106 | increased = true; 107 | break; 108 | } 109 | } 110 | if (!increased) break; 111 | } 112 | 113 | return result; 114 | }; 115 | 116 | const parseCondition = (condition, context = {}) => { 117 | const result = []; 118 | Object.keys(condition).forEach(key => { 119 | let value = condition[key]; 120 | if (key === "or") { 121 | const children = []; 122 | if (Array.isArray(value)) { 123 | children.push(...value); 124 | } else { 125 | Object.keys(value).forEach(field => { 126 | children.push({ [field]: value[field] }); 127 | }); 128 | } 129 | 130 | children.length && 131 | result.push({ 132 | type: "or", 133 | children: children.map(child => ({ 134 | type: "and", 135 | children: parseCondition(child, context) 136 | })) 137 | }); 138 | } else { 139 | // parse normal criteria 140 | let [, field, op = "=="] = keyRegex.exec(key) || []; 141 | if (!field) { 142 | throw new Error("Invalid criteria " + key); 143 | } 144 | if (op === "has") { 145 | op = "array-contains"; 146 | } 147 | if (op === "=" || op === "===") { 148 | op = "=="; 149 | } 150 | if (op === "<>" || op === "!==") { 151 | op = "!="; 152 | } 153 | if (Array.isArray(value)) { 154 | if (op === "!=") { 155 | // not in 156 | // convert to multiple != operator 157 | result.push({ 158 | type: "and", 159 | children: value.map(value => ({ 160 | type: "or", 161 | children: [ 162 | { field, type: ">", value }, 163 | { field, type: "<", value } 164 | ] 165 | })) 166 | }); 167 | } else if (op === "==") { 168 | // in 169 | result.push({ 170 | type: "or", 171 | children: value.map(value => ({ field, type: op, value })) 172 | }); 173 | } else if (op === "array-contains") { 174 | result.push({ 175 | type: "or", 176 | children: value.map(value => ({ 177 | type: "array-contains", 178 | field, 179 | value 180 | })) 181 | }); 182 | context.postFilters.push(doc => { 183 | const fieldValue = doc.data()[field]; 184 | return value.every(value => fieldValue.includes(value)); 185 | }); 186 | } else { 187 | throw new Error("Unsupported " + op + " for Array"); 188 | } 189 | } else { 190 | if (op === "!=") { 191 | result.push({ 192 | type: "or", 193 | children: [{ field, type: ">", value }, { field, type: "<", value }] 194 | }); 195 | } 196 | // process startsWith operator 197 | else if (op === "^=") { 198 | value = String(value); 199 | const length = value.length; 200 | const frontCode = value.slice(0, length - 1); 201 | const endChar = value.slice(length - 1, value.length); 202 | const endcode = 203 | frontCode + String.fromCharCode(endChar.charCodeAt(0) + 1); 204 | result.push( 205 | { field, type: ">=", value }, 206 | { field, type: "<", value: endcode } 207 | ); 208 | } else { 209 | result.push({ field, type: op, value }); 210 | } 211 | } 212 | } 213 | }); 214 | return result; 215 | }; 216 | 217 | export default function create(queryable, collection) { 218 | if (queryable.collection) { 219 | if (collection) { 220 | queryable = queryable.collection(collection); 221 | } else { 222 | return dbWrapper(queryable); 223 | } 224 | } 225 | const unsubscribes = []; 226 | let limit = 0; 227 | let startAt; 228 | let orderBy; 229 | let where = []; 230 | let lastGet, lastDocs; 231 | let compiledQueries; 232 | let select = []; 233 | let pipe = []; 234 | let map = []; 235 | let postFilters = []; 236 | 237 | function processResults(results) { 238 | const docs = {}; 239 | let count = 0; 240 | lastDocs = results.map(result => 241 | result ? result.docs[result.docs.length - 1] : undefined 242 | ); 243 | results.some(result => { 244 | if (!result) return; 245 | result.forEach(doc => { 246 | if (limit && count >= limit) return; 247 | if (postFilters.length && postFilters.some(filter => !filter(doc))) 248 | return; 249 | if (!(doc.id in docs)) { 250 | count++; 251 | } 252 | docs[doc.id] = doc; 253 | }); 254 | return limit && count >= limit; 255 | }); 256 | 257 | let result = Object.values(docs); 258 | 259 | if (select.length) { 260 | result = result.map(doc => { 261 | return select.reduce( 262 | (mappedObj, selector) => selector(mappedObj, doc.data(), doc), 263 | {} 264 | ); 265 | }); 266 | } 267 | 268 | if (map.length) { 269 | result = map.reduce( 270 | (result, mapper) => 271 | result.map((item, index) => 272 | typeof mapper === "function" ? mapper(item, index) : item[mapper]() 273 | ), 274 | result 275 | ); 276 | } 277 | 278 | if (pipe.length) { 279 | result = pipe.reduce((result, f) => f(result), result); 280 | } 281 | 282 | return result; 283 | } 284 | 285 | function modify(docs, callback) { 286 | return Promise.resolve(docs).then(docs => { 287 | const batch = queryable.firestore.batch(); 288 | for (let doc of docs) { 289 | callback(batch, doc); 290 | } 291 | return batch.commit(); 292 | }); 293 | } 294 | 295 | function createOrderedQuery(q) { 296 | if (!orderBy) return q; 297 | const pairs = Object.entries(orderBy); 298 | return pairs.reduce((q, order) => q.orderBy(...order), q); 299 | } 300 | 301 | function buildQueries(noCache) { 302 | if (!noCache && compiledQueries) return compiledQueries; 303 | 304 | if (!where.length) { 305 | let q = queryable; 306 | if (limit) { 307 | q = q.limit(limit); 308 | } 309 | if (startAt !== undefined) { 310 | q = q.startAt(startAt); 311 | } 312 | 313 | return [createOrderedQuery(q)]; 314 | } 315 | 316 | // should copy where before process 317 | const posible = findAllPossibles({ 318 | type: "and", 319 | children: where 320 | }); 321 | 322 | return (compiledQueries = posible.map(p => { 323 | let q = p.reduce((q, node) => { 324 | return q.where( 325 | translateField(node.field), 326 | node.type, 327 | translateValue(node.field, node.value) 328 | ); 329 | }, queryable); 330 | 331 | if (limit) { 332 | q = q.limit(limit); 333 | } 334 | if (startAt !== undefined) { 335 | q = q.startAt(startAt); 336 | } 337 | 338 | return createOrderedQuery(q); 339 | })); 340 | } 341 | 342 | function clone(overwriteData) { 343 | return create(queryable)[copy]( 344 | Object.assign( 345 | { 346 | limit, 347 | where, 348 | orderBy, 349 | startAt, 350 | select, 351 | pipe, 352 | map, 353 | postFilters 354 | }, 355 | overwriteData 356 | ) 357 | ); 358 | } 359 | 360 | const query = { 361 | [copy](data) { 362 | limit = data.limit; 363 | where = data.where; 364 | orderBy = data.orderBy; 365 | startAt = data.startAt; 366 | select = data.select; 367 | pipe = data.pipe; 368 | map = data.map; 369 | postFilters = data.postFilters; 370 | return this; 371 | }, 372 | pipe(...funcs) { 373 | return clone({ 374 | pipe: pipe.slice().concat(funcs) 375 | }); 376 | }, 377 | map(...mappers) { 378 | return clone({ 379 | map: map.slice().concat(mappers) 380 | }); 381 | }, 382 | subscribe(options, callback) { 383 | if (typeof options === "function") { 384 | callback = options; 385 | options = {}; 386 | } 387 | const currentUnsubscribes = buildQueries().map(queryable => 388 | queryable.onSnapshot(options, callback) 389 | ); 390 | unsubscribes.push(...currentUnsubscribes); 391 | return function() { 392 | currentUnsubscribes.forEach(unsubscribe => { 393 | unsubscribe(); 394 | const index = unsubscribes.indexOf(unsubscribe); 395 | if (index !== -1) { 396 | unsubscribes.splice(index, 1); 397 | } 398 | }); 399 | }; 400 | }, 401 | unsubscribeAll() { 402 | const copyOfUnsubscribes = unsubscribes.slice(); 403 | unsubscribes.length = 0; 404 | copyOfUnsubscribes.forEach(unsubscribe => unsubscribe()); 405 | return this; 406 | }, 407 | /** 408 | * supports: 409 | * single field value selector: select(true, 'field') => fieldValue 410 | * multiple fields selector: select('field1', 'field2', ...) => { field1: field1Value, field2: field2Value } 411 | * obj map selector: select({ field: 'newFieldName' }) => { newFieldName: fieldValue } 412 | * custom selector: select(Function) 413 | */ 414 | select(...args) { 415 | let selector; 416 | // single field value selector 417 | if (args[0] === true) { 418 | const field = args[1]; 419 | selector = (mappedObj, data, doc) => 420 | field === "@id" ? doc.id : data[field]; 421 | } else if (typeof args[0] === "function") { 422 | const customSelector = args[0]; 423 | selector = (mappedObj, data, doc) => customSelector(data, doc); 424 | } else if (typeof args[0] === "string") { 425 | const fields = args; 426 | selector = (mappedObj, data, doc) => { 427 | fields.forEach( 428 | field => (mappedObj[field] = field === "@id" ? doc.id : data[field]) 429 | ); 430 | return mappedObj; 431 | }; 432 | } else { 433 | const pairs = Object.entries(args[0]); 434 | selector = (mappedObj, data, doc) => { 435 | pairs.forEach( 436 | pair => 437 | (mappedObj[pair[1]] = pair[0] === "@id" ? doc.id : data[pair[0]]) 438 | ); 439 | return mappedObj; 440 | }; 441 | } 442 | return clone({ 443 | select: [selector] 444 | }); 445 | }, 446 | limit(count) { 447 | return clone({ limit: count }); 448 | }, 449 | first() { 450 | return this.limit(1) 451 | .get() 452 | .then(results => { 453 | return results[0]; 454 | }); 455 | }, 456 | where(...conditions) { 457 | const newWhere = where.slice(); 458 | const context = { 459 | postFilters: postFilters.slice() 460 | }; 461 | conditions.forEach(condition => 462 | newWhere.push(...parseCondition(condition, context)) 463 | ); 464 | return clone({ 465 | where: newWhere, 466 | postFilters: context.postFilters 467 | }); 468 | }, 469 | orderBy(fields) { 470 | return clone({ 471 | orderBy: Object.assign({}, orderBy, fields) 472 | }); 473 | }, 474 | get: function get({ source } = {}) { 475 | const promises = buildQueries().map(queryable => queryable.get(source)); 476 | return (lastGet = Promise.all(promises).then(processResults)); 477 | }, 478 | data(options) { 479 | return this.get(options).then(results => results.map(x => x.data())); 480 | }, 481 | next(options = {}) { 482 | const { source } = options; 483 | if (lastGet) { 484 | return (lastGet = lastGet.then(docs => { 485 | if (!docs.length) return []; 486 | const queries = buildQueries(); 487 | const promises = queries.map((queryable, index) => { 488 | if (!lastDocs[index]) return undefined; 489 | return queryable.startAfter(lastDocs[index]).get(source); 490 | }); 491 | return Promise.all(promises).then(processResults); 492 | })); 493 | } 494 | return this.get(options); 495 | }, 496 | set(docsOrData, applyToResultSet) { 497 | if (applyToResultSet) { 498 | return modify(this.get(), (batch, doc) => 499 | batch.set(doc.ref, docsOrData) 500 | ); 501 | } 502 | return modify( 503 | Object.keys(docsOrData).map(id => queryable.doc(String(id))), 504 | (batch, doc) => batch.set(doc, docsOrData[doc.id]) 505 | ); 506 | }, 507 | update(docsOrData, applyToResultSet) { 508 | if (applyToResultSet) { 509 | return modify(this.get(), (batch, doc) => 510 | batch.update(doc.ref, docsOrData) 511 | ); 512 | } 513 | return modify( 514 | Object.keys(docsOrData).map(id => queryable.doc(String(id))), 515 | (batch, doc) => batch.update(doc, docsOrData[doc.id]) 516 | ); 517 | }, 518 | remove() { 519 | return modify(this.get(), (batch, doc) => batch.delete(doc.ref)); 520 | } 521 | }; 522 | 523 | arrayMethods.forEach(method => { 524 | query[method] = (...args) => 525 | query.get().then(results => results[method](...args)); 526 | }); 527 | 528 | return query; 529 | } 530 | 531 | Object.assign(create, { 532 | fields(newSpecialFields) { 533 | Object.assign(specialFields, newSpecialFields); 534 | return this; 535 | } 536 | }); 537 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linq2fire", 3 | "version": "1.0.22", 4 | "description": "Zero dependency. Supports special operators: IN, OR, !=, startsWith (^=), array-contains and many more", 5 | "main": "./dist/index.js", 6 | "scripts": { 7 | "test": "npm test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/linq2js/linq2fire.git" 12 | }, 13 | "keywords": [ 14 | "functional", 15 | "firebase", 16 | "firestore", 17 | "documentdb", 18 | "nosql" 19 | ], 20 | "author": "linq2js", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/linq2js/linq2fire/issues" 24 | }, 25 | "homepage": "https://github.com/linq2js/linq2fire#readme", 26 | "dependencies": {}, 27 | "devDependencies": { 28 | "babel-cli": "^6.26.0", 29 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 30 | "babel-preset-env": "^1.7.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Linq2Fire 2 | 3 | Supports special operators: IN, NOT IN, OR, !=, startsWith (^=), array-contains (has) and many more 4 | 5 | ```js 6 | import linq from "linq2fire"; 7 | const db = firebase.firestore(); 8 | 9 | const printDocs = heading => docs => { 10 | console.log("**********", heading.toUpperCase(), "**********"); 11 | docs.forEach(doc => console.log(doc.id, doc.data())); 12 | console.log(); 13 | }; 14 | 15 | const test = async () => { 16 | const $todos = linq(db).from("todos"); 17 | 18 | await $todos 19 | .orderBy({ text: "desc" }) 20 | .orderBy({ text: "desc" }) 21 | .where({ category: "A" }) 22 | .get() 23 | .then(printDocs("Duplicate order by")); 24 | 25 | await $todos.remove(); 26 | 27 | // add single doc 28 | await $todos.set(1, { 29 | text: "Task 1" 30 | }); 31 | // add multiple docs 32 | await $todos.set({ 33 | 1: { 34 | text: "Task 1", 35 | category: "A" 36 | }, 37 | 2: { 38 | text: "Task 2", 39 | category: "B", 40 | categories: ["A", "B"] 41 | }, 42 | 3: { 43 | text: "Task 3", 44 | categories: ["A"] 45 | }, 46 | 4: { 47 | text: "Task 4", 48 | category: "B", 49 | categories: ["B"] 50 | }, 51 | 5: { 52 | text: "Task 5", 53 | category: "A" 54 | }, 55 | 6: { 56 | text: "Other task", 57 | category: "C" 58 | } 59 | }); 60 | 61 | await $todos 62 | .where({ 63 | // not in operator 64 | "text<>": ["Task 1", "Task 2"] 65 | }) 66 | .get() 67 | .then(printDocs("Find tasks which is not in [1, 2]")); 68 | 69 | await $todos 70 | .where({ 71 | "text ^=": "Task" 72 | }) 73 | .get() 74 | .then(printDocs("Find all tasks which starts with Task")); 75 | 76 | await $todos 77 | .orderBy({ text: "desc" }) 78 | .first() 79 | .then(first => { 80 | console.log("Get first task ", first && first.data()); 81 | }); 82 | 83 | await $todos 84 | .where({ 85 | // in operator 86 | text: ["Task 1", "Task 2"] 87 | }) 88 | .get() 89 | .then(printDocs("Find tasks: 1, 2, 3")); 90 | 91 | await $todos 92 | .where({ 93 | "text <": "Task 2" 94 | }) 95 | .get() 96 | .then(printDocs("Find all tasks which has text less than Task 2")); 97 | 98 | await $todos 99 | .where({ 100 | // not equal 101 | "text <>": "Task 1" 102 | }) 103 | .get() 104 | .then(printDocs("Find all tasks which has text not equal Task 1")); 105 | 106 | await $todos 107 | .where({ 108 | // find by id 109 | "@id": 1 110 | }) 111 | .get() 112 | .then(printDocs("Find task by id")); 113 | 114 | await $todos 115 | .where({ 116 | // multiple IN operators 117 | text: ["Task 1", "Task 2", "Task 3"], 118 | category: ["A", "B"] 119 | }) 120 | .get() 121 | .then(printDocs("Find task with multiple IN operators")); 122 | 123 | await $todos 124 | .where({ 125 | text: ["Task 1", "Task 2", "Task 3"], 126 | or: [{ category: "A" }, { category: "B" }] 127 | }) 128 | .get() 129 | .then(printDocs("Find task with OR operator ")); 130 | 131 | // await $todos 132 | // .where({ 133 | // 'category array_contains': 'A' 134 | // }) 135 | // .get() 136 | // .then(printDocs('Finding task using array_contains operator')); 137 | 138 | // get task names 139 | await $todos 140 | .select({ text: "name" }) 141 | .get() 142 | .then(console.log); 143 | 144 | // join all items using pipe 145 | await $todos 146 | .select(true, "text") 147 | .pipe(String) 148 | .get() 149 | .then(console.log); 150 | 151 | // convert task names to uppercase 152 | await $todos 153 | .select(true, "text") 154 | .map("toUpperCase") 155 | .get() 156 | .then(console.log); 157 | 158 | await $todos 159 | .select(true, "text") 160 | .map(x => x.toUpperCase()) 161 | .get() 162 | .then(console.log); 163 | 164 | await $todos 165 | .select(true, "text") 166 | .get() 167 | .then(console.log); 168 | 169 | await $todos 170 | .where({ 171 | or: { 172 | category: "A", 173 | text: "Task 3" 174 | } 175 | }) 176 | .get() 177 | .then(printDocs("Find task with OR operator ")); 178 | 179 | await $todos 180 | .where({ 181 | "categories has": "A" 182 | }) 183 | .get() 184 | .then(printDocs("Find task using has operator")); 185 | 186 | await $todos 187 | .where({ 188 | "categories array-contains": "A" 189 | }) 190 | .get() 191 | .then(printDocs("Find task using array-contains operator")); 192 | 193 | // support pagination 194 | const pagination = $todos 195 | .limit(1) 196 | .orderBy({ 197 | text: "asc" 198 | }) 199 | .where({ 200 | "text <>": "Task 1" 201 | }); 202 | 203 | await pagination 204 | .get() 205 | .then(printDocs("Find all tasks which has text not equal Task 1. Page 1")); 206 | 207 | await pagination 208 | .next() 209 | .then(printDocs("Find all tasks which has text not equal Task 1. Page 2")); 210 | await pagination 211 | .next() 212 | .then(printDocs("Find all tasks which has text not equal Task 1. Page 3")); 213 | }; 214 | 215 | test(); 216 | ``` 217 | 218 | ## References: 219 | 220 | ### linq2fire(db):LinqDb 221 | 222 | Create a linq object to wrap db 223 | 224 | ### LinqDb.from(collectionName): LinqCollection 225 | 226 | Create a linq object to wrap collection 227 | 228 | ### LinqDb.from(collection, callback): LinqDb 229 | 230 | Create a linq object to wrap collection, then pass it to callback. This method is useful for chaining calls 231 | 232 | ### linq2fire(collection): LinqCollection 233 | 234 | Create a linq object to wrap collection 235 | 236 | ### LinqCollection.select(fieldName1: String, fieldName2: String, ...): LinqCollection 237 | 238 | Projects each item of a result set into a new object with specific fields. 239 | 240 | ### LinqCollection.select(fieldMap: Object): LinqCollection 241 | 242 | Projects each item of a result set into a new object with specific fields. 243 | 244 | ### LinqCollection.select(valueOnly: Boolean, field: String): LinqCollection 245 | 246 | Transform result set into a array of field value 247 | 248 | ### LinqCollection.select(customSelector: Function(data: Object, doc: DocumentSnapshot)): LinqCollection 249 | 250 | Projects each item of a result set by using custom selector. 251 | 252 | ### LinqCollection.limit(count): LinqCollection 253 | 254 | Limit result set 255 | 256 | ### LinqCollection.where(conditions): LinqCollection 257 | 258 | Filter result set by multiple conditions { text: 'abc', 'age >': 100, fieldValueMustBeIn: [1, 2, 3, 4, 5], 'field <>': 0 } 259 | Support operators: >, <, >=, <=, =, ==, ===, <>, !=, !==, has, array-contains. 260 | 'has' is shorthand of 'array-contains' 261 | 262 | ### LinqCollection.orderBy(fields): LinqCollection 263 | 264 | Sort result set by specified fields { field1: 'asc', field2: 'desc' } 265 | 266 | ### LinqCollection.get(options): Promise 267 | 268 | Get all documents which is satisfied query condition 269 | 270 | ### LinqCollection.next(options): Promise 271 | 272 | Get next result set which starts after last result set 273 | 274 | ### LinqCollection.set(docsOrData, applyToResultSet): Promise 275 | 276 | ### LinqCollection.update(docsOrData, applyToResultSet): Promise 277 | 278 | ### LinqCollection.remove(): Promise 279 | 280 | Remove all documents which is satisfied query condition 281 | --------------------------------------------------------------------------------