├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | dump.rdb 4 | lib-cov 5 | *.seed 6 | *.log 7 | *.out 8 | *.pid 9 | npm-debug.log 10 | src/config/local.js 11 | yarn.lock 12 | *~ 13 | *# 14 | .DS_STORE 15 | .netbeans 16 | nbproject 17 | .idea 18 | .node_history 19 | dist 20 | coverage 21 | .github-todos 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firebase-relationship", 3 | "version": "1.0.1", 4 | "description": "A Firebase relationship manager", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/teamfa/firebase-relationship.git" 12 | }, 13 | "keywords": [ 14 | "firebase", 15 | "javascript", 16 | "relationship", 17 | "firebase join" 18 | ], 19 | "author": "TeamFA", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/teamfa/firebase-relationship/issues" 23 | }, 24 | "homepage": "https://github.com/teamfa/firebase-relationship#readme", 25 | "dependencies": { 26 | "pluralize": "^3.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebase Relationship 2 | [![NPM version][npm-image]][npm-url] 3 | [![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg)](https://www.patreon.com/invertase) 4 | 5 | A promise based helper to manage relationships in your Firebase Realtime Database. 6 | 7 | ## Usage 8 | 9 | ```bash 10 | npm install firebase-relationship --save 11 | ``` 12 | 13 | ```javascript 14 | import Firebase from 'firebase'; 15 | import Relationship from 'firebase-relationship'; 16 | 17 | // Create a new Firebase & Relationship instance 18 | const firebase = Firebase.initializeApp({...}); 19 | const relationship = new Relationship('category_product'); 20 | 21 | // Create a new relationship between a category (id: 123) and a product (id: abc) 22 | relationship.join(firebase, 123, 'abc'); 23 | 24 | // Remove the relationship 25 | relationship.remove(firebase, 123, 'abc'); 26 | ``` 27 | 28 | ## API 29 | 30 | ### - constructor(name, path) 31 | Creates and returns a new relationship. 32 | 33 | - **[name]** Relationship name. Must include only one underscore. 34 | - **[path]** Firebase path to store relationships. Defaults to 'relationship'. Must not contain leading or trailing forward slashes. 35 | 36 | 37 | ### - join(instance, leftId, rightId, value?) : Promise 38 | 39 | Creates a two way relationship between two IDs. The 4th param is an optional value to store with the relationship (defaults to true). 40 | 41 | 42 | ### - remove(instance, leftId, rightId) : Promise 43 | 44 | Removes a two way relationship between two IDs. 45 | 46 | 47 | ## Magic Methods 48 | 49 | When a new relationship instance is created, four magic methods are available for easy relationship querying. For example, with a "category_product" relationship: 50 | 51 | ```javascript 52 | // Get products for a category 53 | relationship.getCategoryProducts(firebase, categoryId).then((snapshot) => { 54 | console.log('Products', snapshot.val()); 55 | }); 56 | 57 | // Get product categories 58 | relationship.getProductCategories(firebase, productId).then((snapshot) => { 59 | console.log('Categories', snapshot.val()); 60 | }); 61 | 62 | // you can also get the internal refs for a join 63 | const refA = relationship.getCategoryProductsRef; 64 | const refB = relationship.getProductCategoriesRef; 65 | // ... do a custom query ? 66 | ``` 67 | 68 | > Each relationship name is pluralized. 69 | 70 | [npm-image]: https://img.shields.io/npm/v/firebase-relationship.svg?style=flat-square 71 | [npm-url]: https://npmjs.org/package/firebase-relationship 72 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const pluralize = require('pluralize'); 2 | const namespace = 'firebase-relationship'; 3 | 4 | function cap(_s) { 5 | const s = _s.toLowerCase(); 6 | return s[0].toUpperCase() + s.slice(1); 7 | } 8 | 9 | class Relationship { 10 | 11 | /** 12 | * Relationship name. 13 | * @param name 14 | * @param path 15 | */ 16 | constructor(name, path = 'relationship') { 17 | if (path.startsWith('/') || path.endsWith('/')) { 18 | throw new Error(`[${namespace}] Invalid relationship path. Should not start or end with a / (forward slash).`); 19 | } 20 | 21 | const parts = name.split('_'); 22 | 23 | if (parts.length !== 2) { 24 | throw new Error(`[${namespace}] Invalid relationship name. Must contain a single underscore e.g. "category_product"`); 25 | } 26 | 27 | const a = [parts[0], pluralize(parts[1])].join('_'); 28 | const b = [parts[1], pluralize(parts[0])].join('_'); 29 | 30 | this._relLeft = [cap(parts[0]), cap(pluralize(parts[1]))].join(''); 31 | this._relRight = [cap(parts[1]), cap(pluralize(parts[0]))].join(''); 32 | 33 | this._leftRef = `/${path}/${a}`; 34 | this._rightRef = `/${path}/${b}`; 35 | 36 | // create stub methods i.e getUserMatches & getMatchUsers 37 | this[`get${this._relLeft}`] = this._getLeftChildren.bind(this); 38 | this[`get${this._relRight}`] = this._getRightChildren.bind(this); 39 | 40 | // create stub get ref methods i.e getUserMatchesRef & getMatchUsersRef 41 | this[`get${this._relLeft}Ref`] = this._getLeftChildrenRef.bind(this); 42 | this[`get${this._relRight}Ref`] = this._getRightChildrenRef.bind(this); 43 | } 44 | 45 | /** 46 | * 47 | * @param firebase 48 | * @param aId 49 | * @param bId 50 | * @param value 51 | */ 52 | join(firebase, aId, bId, value = true) { 53 | const db = firebase.database(); 54 | const _value = value || Date.now(); 55 | return Promise.all([ 56 | db.ref(`${this._leftRef}/${aId}/${bId}`).set(_value), 57 | db.ref(`${this._rightRef}/${bId}/${aId}`).set(_value) 58 | ]); 59 | } 60 | 61 | /** 62 | * 63 | * @param firebase 64 | * @param aId 65 | * @param bId 66 | */ 67 | remove(firebase, aId, bId) { 68 | const db = firebase.database(); 69 | return Promise.all([ 70 | db.ref(`${this._leftRef}/${aId}/${bId}`).remove(), 71 | db.ref(`${this._rightRef}/${bId}/${aId}`).remove() 72 | ]); 73 | } 74 | 75 | /** 76 | * 77 | * @param firebase 78 | * @param aId 79 | * @returns {Promise} 80 | * @private 81 | */ 82 | _getLeftChildren(firebase, aId) { 83 | return this._getLeftChildrenRef(firebase, aId).once('value'); 84 | } 85 | 86 | /** 87 | * 88 | * @param firebase 89 | * @param bId 90 | * @returns {Promise} 91 | * @private 92 | */ 93 | _getRightChildren(firebase, bId) { 94 | return this._getRightChildrenRef(firebase, bId).once('value'); 95 | } 96 | 97 | /** 98 | * 99 | * @param firebase 100 | * @param aId 101 | * @returns {Promise} 102 | * @private 103 | */ 104 | _getLeftChildrenRef(firebase, aId) { 105 | return firebase.database().ref(`${this._leftRef}/${aId}`); 106 | } 107 | 108 | /** 109 | * 110 | * @param firebase 111 | * @param bId 112 | * @returns {Promise} 113 | * @private 114 | */ 115 | _getRightChildrenRef(firebase, bId) { 116 | return firebase.database().ref(`${this._rightRef}/${bId}`); 117 | } 118 | } 119 | 120 | module.exports = Relationship; 121 | --------------------------------------------------------------------------------