├── babel.config.js ├── index.js ├── test ├── integration │ ├── echo-engine.js │ ├── prepared-query.js │ ├── queue-event.js │ ├── realtime.js │ ├── filestore.html │ └── index.js └── index.js ├── lib ├── realtime │ ├── subscription.js │ ├── realtime.js │ ├── helper.js │ ├── README.md │ └── liveQuery.js ├── db │ ├── batch.js │ ├── prepared-query.js │ ├── delete.js │ ├── aggregate.js │ ├── insert.js │ ├── update.js │ ├── get.js │ ├── db.js │ └── README.md ├── filestore │ ├── README.md │ └── filestore.js ├── websocket │ └── websocket-client.js ├── utils.js ├── api.js └── README.md ├── .gitignore ├── package.json ├── README.md └── LICENSE /babel.config.js: -------------------------------------------------------------------------------- 1 | const presets = ["@babel/env"]; 2 | 3 | module.exports = { presets }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const utils = require('./lib/utils'); 2 | const API = require('./lib/api'); 3 | 4 | exports.API = API; 5 | exports.and = utils.and; 6 | exports.or = utils.or; 7 | exports.cond = utils.cond; -------------------------------------------------------------------------------- /test/integration/echo-engine.js: -------------------------------------------------------------------------------- 1 | var { API, cond, and, or } = require("../../index") 2 | 3 | const api = new API("test", "http://localhost:4122") 4 | const service = api.Service("echo-engine") 5 | service.registerFunc("echo", (params, auth, cb) => { 6 | cb("response", { message: params }) 7 | }) 8 | 9 | service.start() -------------------------------------------------------------------------------- /test/integration/prepared-query.js: -------------------------------------------------------------------------------- 1 | var { API } = require("../../index") 2 | 3 | const api = new API("test", "http://localhost:4122") 4 | const db = api.DB("postgres"); 5 | db.preparedQuery("preparedQuery1").args({ id: "1" }).apply().then((res => { 6 | if (res.status !== 200) { 7 | throw new Error(`Request failed - ${res}`) 8 | } 9 | })).catch(ex => console.log("Exception", ex)) -------------------------------------------------------------------------------- /test/integration/queue-event.js: -------------------------------------------------------------------------------- 1 | var { API } = require("../../index") 2 | 3 | let api = new API("myproject", "http://localhost:4122") 4 | 5 | const queueEvent = async () => { 6 | try { 7 | const res = await api.queueEvent("my_type", { "key1": "value1" }).apply() 8 | console.log("Event Queue Response", res) 9 | } catch (error) { 10 | console.log("Error", error) 11 | } 12 | } 13 | 14 | queueEvent() 15 | -------------------------------------------------------------------------------- /lib/realtime/subscription.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class representing the LiveQuerySubscription Interface. 3 | * 4 | */ 5 | 6 | class LiveQuerySubscription { 7 | /** 8 | * The function to unsubscribe the subscription 9 | * @name Unsubscribe 10 | * @function 11 | */ 12 | 13 | /** 14 | * Create an instance of the LiveQuerySubscription Interface. 15 | * @param {Unsubscribe} unsubscribeFunc The function to unsubscribe the liveQuery 16 | * @param {Array} snapshot The initial snapshot 17 | */ 18 | 19 | constructor(unsubscribeFunc, snapshot) { 20 | this.unsubscribeFunc = unsubscribeFunc 21 | this.snapshot = snapshot 22 | } 23 | 24 | /** 25 | * Gets the snapshot 26 | * @returns {Array} snapshot - The current snapshot 27 | */ 28 | getSnapshot() { 29 | return this.snapshot 30 | } 31 | 32 | /** 33 | * Unsubscribes from the liveQuery 34 | */ 35 | unsubscribe() { 36 | this.unsubscribeFunc() 37 | } 38 | } 39 | 40 | module.exports = LiveQuerySubscription -------------------------------------------------------------------------------- /lib/realtime/realtime.js: -------------------------------------------------------------------------------- 1 | const LiveQuery = require('./liveQuery') 2 | const snapshotCallback = require('./helper').snapshotCallback 3 | 4 | class Realtime { 5 | constructor(appId, client) { 6 | this.appId = appId 7 | this.client = client 8 | this.store = {} 9 | this.client.registerOnReconnectCallback(() => { 10 | Object.keys(this.store).forEach(db => { 11 | Object.keys(this.store[db]).forEach(col => { 12 | Object.keys(this.store[db][col]).forEach(id => { 13 | const obj = this.store[db][col][id] 14 | var q = this.liveQuery(db, col) 15 | q.find = obj.find 16 | q.opts = obj.opts 17 | q.subscribeRaw(id, obj.subscription.onSnapshot, obj.subscription.onError); 18 | }) 19 | }) 20 | }) 21 | }) 22 | this.client.registerCallback('realtime-feed', (data) => { snapshotCallback(this.store, [data]) }) 23 | } 24 | 25 | liveQuery(db, collection) { 26 | return new LiveQuery(this.appId, db, collection, this.client, this.store) 27 | } 28 | } 29 | 30 | module.exports = Realtime -------------------------------------------------------------------------------- /lib/db/batch.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | const utils = require('../utils'), 3 | fetchAsync = utils.fetchAsync; 4 | 5 | class Batch { 6 | constructor(appId, url, options, db) { 7 | this.appId = appId; 8 | this.url = url; 9 | this.db = db; 10 | this.options = Object.assign({}, options, { method: 'POST' }); 11 | this.params = { reqs: [] }; 12 | } 13 | 14 | /** 15 | * Adds a request to the batch. 16 | * @param {Object} req - The request to batch. 17 | */ 18 | add(req) { 19 | this.params.reqs.push(Object.assign({}, req.params, { col: req.collection, type: req.type })) 20 | return this; 21 | } 22 | 23 | /** 24 | * Executes the batch operations. 25 | * @returns {Promise} Returns a promise containing response from server. 26 | * @example 27 | * const batch = db.beginBatch() 28 | * batch.add(db.insert("todos").doc({_id: "1", todo: "Star Space Cloud"})) 29 | * batch.add(db.insert("todos").doc({_id: "2", todo: "Fork Space Cloud"})) 30 | * batch.apply().then(res => console.log("Response", res)) 31 | */ 32 | apply() { 33 | this.options.body = JSON.stringify(this.params); 34 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', 'batch'); 35 | return fetchAsync(url, this.options); 36 | } 37 | } 38 | 39 | module.exports = Batch; 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # build files 64 | dist 65 | 66 | # standalone file for browser 67 | bundle 68 | 69 | # misc test files 70 | test/misc 71 | 72 | test.js 73 | 74 | publish.sh -------------------------------------------------------------------------------- /test/integration/realtime.js: -------------------------------------------------------------------------------- 1 | var { API, cond, and, or } = require("../../index") 2 | const generateId = require('../../lib/utils').generateId; 3 | 4 | const api = new API('todo_app', 'http://localhost:4122/') 5 | const db = api.DB("db") 6 | 7 | const userId = 'user1' 8 | const otherUserId = 'user2' 9 | 10 | // db.liveQuery("todos") 11 | // .where(cond("user_id", "==", userId)) 12 | // .subscribe( 13 | // (docs, type, find, changedDoc) => { 14 | // console.log('Snapshot:', docs, '\nType: ', type, '\nFind: ', find, '\nChangedDoc', changedDoc, '\n\n') 15 | // }, 16 | // (err) => console.log('Operation failed:', err) 17 | // ) 18 | 19 | db.liveQuery("todos") 20 | .where(cond("userId", "==", userId)) 21 | .options({ changesOnly: false }) 22 | .subscribe( 23 | (docs, type, find, changedDoc) => { 24 | // console.log('Snapshot:', docs, '\nType: ', type, '\nFind: ', find, '\nChangedDoc', changedDoc, '\n\n') 25 | }, 26 | (err) => console.log('Operation failed:', err) 27 | ) 28 | 29 | const id1 = generateId() 30 | 31 | setTimeout(() => { 32 | db.insert('todos').doc({ _id: id1, todo: 'some todo 1', user_id: userId }).apply() 33 | db.insert('todos').doc({ _id: generateId(), todo: 'some todo 2', user_id: otherUserId }).apply() 34 | db.insert('todos').doc({ _id: generateId(), todo: 'some todo 3', user_id: userId }).apply() 35 | }, 1000) 36 | 37 | setTimeout(() => { 38 | db.update('todos').where(cond('_id', '==', id1)).set({ todo: 'some todo111' }).apply() 39 | }, 2000) 40 | 41 | setTimeout(() => { 42 | db.delete('todos').where(cond('_id', '==', id1)).apply() 43 | }, 3000) 44 | 45 | setTimeout(() => { 46 | db.delete('todos').apply() 47 | }, 4000) 48 | -------------------------------------------------------------------------------- /lib/db/prepared-query.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | const utils = require('../utils'), 3 | fetchAsync = utils.fetchAsync; 4 | 5 | /** 6 | * Class representing the Prepared Query Interface. 7 | * @example 8 | * import { API } from 'space-api'; 9 | * 10 | * const api = new API('my-project', 'localhost:4122'); 11 | * const db = api.DB("mongo"); 12 | * 13 | * db.preparedQuery('myPreparedQuery').args({ "foo": "bar" }).apply().then(res => { 14 | * if (res.status === 200) { 15 | * // The prepared query was executed successfully 16 | * console.log("Data", res.data) 17 | * return; 18 | * } 19 | * }).catch(ex => { 20 | * // Exception occured while processing request 21 | * }); 22 | */ 23 | class PreparedQuery { 24 | /** 25 | * Create an instance of the Prepared Query Interface. 26 | * @param {string} appId 27 | * @param {string} id 28 | * @param {string} url 29 | * @param {Object} options 30 | * @param {string} db 31 | */ 32 | constructor(appId, id, url, options, db) { 33 | this.appId = appId; 34 | this.id = id; 35 | this.url = url; 36 | this.db = db; 37 | this.options = Object.assign({}, options, { method: 'POST' }); 38 | this.params = { params: {} }; 39 | } 40 | 41 | /** 42 | * Prepares the args object 43 | * @param {Object} args - The args object. 44 | */ 45 | args(argsObject) { 46 | this.params.params = argsObject; 47 | return this; 48 | } 49 | 50 | /** 51 | * Executes the prepared query. 52 | * @returns {Promise} Returns a promise containing response from server. 53 | * @example 54 | * db.preparedQuery('myPreparedQuery').args({ "foo": "bar" }).apply().then(res => ...) 55 | */ 56 | apply() { 57 | this.options.body = JSON.stringify(this.params); 58 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `prepared-queries/${this.id}`); 59 | return fetchAsync(url, this.options); 60 | } 61 | } 62 | 63 | module.exports = PreparedQuery; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "space-api", 3 | "version": "0.19.3", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "npm run dev", 8 | "dev": "npm test -- -w", 9 | "test": "./node_modules/nyc/bin/nyc.js ./node_modules/mocha/bin/mocha", 10 | "init": "mkdir dist", 11 | "clean": "rm -rf dist", 12 | "prebuild": "npm run clean && npm run init", 13 | "build": "babel ./index.js -d ./dist --ignore test.js && browserify ./index.js -s Space -o ./bundle/bundle.js && babel ./bundle -d ./bundle --ignore test.js && uglifyjs bundle/bundle.js -o bundle/space-api.js && rm ./bundle/bundle.js && babel ./lib -d ./dist/lib --ignore test.js && npm run docs", 14 | "pretest": "npm run build", 15 | "pub": "npm run build && npm publish", 16 | "docs": "jsdoc2md -f lib/api.js > lib/README.md && jsdoc2md -f lib/db/db.js -f lib/db/insert.js -f lib/db/get.js -f lib/db/update.js -f lib/db/delete.js -f lib/db/aggregate.js > lib/db/README.md && jsdoc2md -f lib/realtime/liveQuery.js > lib/realtime/README.md && jsdoc2md -f lib/filestore/filestore.js > lib/filestore/README.md" 17 | }, 18 | "author": "Noorain Panjwani", 19 | "license": "Apache-2.0", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/spaceuptech/space-api-js.git" 23 | }, 24 | "devDependencies": { 25 | "@babel/cli": "^7.2.3", 26 | "@babel/core": "^7.3.4", 27 | "@babel/preset-env": "^7.3.4", 28 | "babel-preset-env": "^1.7.0", 29 | "babel-preset-es2015": "^6.24.1", 30 | "browserify": "^16.2.3", 31 | "chai": "^4.1.2", 32 | "jsdoc-to-markdown": "^5.0.3", 33 | "mocha": "^5.1.1", 34 | "nyc": "^14.1.1", 35 | "uglify-js": "^3.4.9" 36 | }, 37 | "files": [ 38 | "dist" 39 | ], 40 | "dependencies": { 41 | "@babel/polyfill": "^7.2.5", 42 | "form-data": "^2.3.3", 43 | "isomorphic-ws": "^4.0.1", 44 | "node-fetch": "^2.3.0", 45 | "url": "^0.11.0", 46 | "websocket": "^1.0.28", 47 | "ws": "^6.1.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/realtime/helper.js: -------------------------------------------------------------------------------- 1 | exports.snapshotCallback = (store, rows) => { 2 | if (!rows || rows.length === 0) return 3 | var obj = {} 4 | var opts = {} 5 | rows.forEach(data => { 6 | obj = store[data.dbType][data.group][data.id] 7 | if (obj) { 8 | opts = obj.opts 9 | if (data.type === 'initial') { 10 | obj.snapshot.push({ find: data.find, time: data.time, payload: data.payload, isDeleted: false }) 11 | } else if (data.type === 'insert' || data.type === 'update') { 12 | let isExisting = false 13 | obj.snapshot = obj.snapshot.map(row => { 14 | if (matchFindClause(row.payload, data.find)) { 15 | isExisting = true 16 | if (row.time <= data.time) 17 | return Object.assign(row, { time: data.time, payload: data.payload, isDeleted: false }) 18 | } 19 | return row 20 | }) 21 | if (!isExisting) obj.snapshot.push({ find: data.find, time: data.time, payload: data.payload, isDeleted: false }) 22 | } else if (data.type === 'delete') { 23 | obj.snapshot = obj.snapshot.map(row => { 24 | if (matchFindClause(row.payload, data.find) && row.time <= data.time) 25 | return Object.assign(row, { time: data.time, payload: {}, isDeleted: true }) 26 | return row 27 | }) 28 | } 29 | const changeType = rows[0].type 30 | if (changeType === 'initial') { 31 | if (!opts.skipInitial) { 32 | obj.subscriptionObject.snapshot = obj.snapshot.filter(row => (!row.isDeleted)).map(row => row.payload) 33 | obj.subscription.onSnapshot(obj.subscriptionObject.snapshot, changeType) 34 | } 35 | } else { 36 | if (changeType !== 'delete') { 37 | obj.subscriptionObject.snapshot = obj.snapshot.filter(row => (!row.isDeleted)).map(row => row.payload) 38 | obj.subscription.onSnapshot(obj.subscriptionObject.snapshot, changeType, rows[0].find, rows[0].payload) 39 | } else { 40 | obj.subscriptionObject.snapshot = obj.snapshot.filter(row => (!row.isDeleted)).map(row => row.payload) 41 | obj.subscription.onSnapshot(obj.subscriptionObject.snapshot, changeType, rows[0].find) 42 | } 43 | } 44 | } 45 | }); 46 | } 47 | 48 | const matchFindClause = (obj, find) => { 49 | return Object.entries(find).every(([key, value]) => { 50 | return obj[key] == value 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var API = require('../index').API; 2 | var assert = require('chai').assert; 3 | 4 | describe('Client API', function () { 5 | describe('#constructor', function () { 6 | it('should set appId and url when both are provided', function () { 7 | let api = new API('app', '/url'); 8 | assert.equal(api.appId, 'app', 'api.appId equal `app`'); 9 | assert.equal(api.url, '/url', 'api.url equal `/url`'); 10 | }); 11 | 12 | it('should set take default url when not provided', function () { 13 | let api = new API('app'); 14 | assert.equal(api.url, '/', 'api.url equal `/`'); 15 | }); 16 | 17 | it('should set the defualt options', function () { 18 | let options = { 19 | credentials: 'include', 20 | headers: { 'Content-Type': 'application/json' } 21 | }; 22 | 23 | let api = new API('app'); 24 | assert.equal(api.options.credentials, 'include', 'credentials equal `include`'); 25 | assert.equal(api.options.headers['Content-Type'], 'application/json', 'Content-Type equal `application/json`'); 26 | }); 27 | 28 | it('should have a default token value', function () { 29 | let api = new API('app'); 30 | assert.equal(api.options.headers.Authorization, undefined, 'token equal `undefined`'); 31 | }); 32 | }); 33 | 34 | describe('#setToken', function () { 35 | it('should set the token value', function () { 36 | let api = new API('app'); 37 | api.setToken('token'); 38 | assert.equal(api.options.headers.Authorization, 'Bearer token', 'api.token equal `token`'); 39 | }); 40 | }); 41 | 42 | describe('#setAppId', function () { 43 | it('should set the new app id', function () { 44 | let api = new API('app1'); 45 | api.setAppId('app2'); 46 | assert.equal(api.appId, 'app2', 'api.appId equal `app2`'); 47 | }); 48 | }); 49 | 50 | describe('#get', function () { 51 | it('should get a Get object instance', function () { 52 | let api = new API('app'); 53 | let get = api.Mongo().get('col'); 54 | assert.equal(get.constructor.name, 'Get', 'object is of type Get'); 55 | }); 56 | }); 57 | 58 | describe('#insert', function () { 59 | it('should get an Insert object instance', function () { 60 | let api = new API('app'); 61 | let insert = api.Mongo().insert('col'); 62 | assert.equal(insert.constructor.name, 'Insert', 'object is of type Insert'); 63 | }); 64 | }); 65 | 66 | describe('#update', function () { 67 | it('should get an Update object instance', function () { 68 | let api = new API('app'); 69 | let update = api.Mongo().update('col'); 70 | assert.equal(update.constructor.name, 'Update', 'object is of type Update'); 71 | }); 72 | }); 73 | 74 | describe('#delete', function () { 75 | it('should get a Delete object instance', function () { 76 | let api = new API('app'); 77 | let del = api.Mongo().delete('col'); 78 | assert.equal(del.constructor.name, 'Delete', 'object is of type Delete'); 79 | }); 80 | }); 81 | }); -------------------------------------------------------------------------------- /lib/db/delete.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | const utils = require('../utils'), 3 | fetchAsync = utils.fetchAsync, 4 | and = utils.and; 5 | 6 | /** 7 | * Class representing the DB Delete Interface. 8 | * @example 9 | * import { API, cond, or, and } from 'space-api'; 10 | * 11 | * const api = new API('my-project', 'localhost:4122'); 12 | * const db = api.DB("mongo"); 13 | * 14 | * db.delete('posts').where(and(cond('title', '==', 'Title1'))).all().then(res => { 15 | * if (res.status === 200) { 16 | * // The documents were deleted successfully 17 | * return; 18 | * } 19 | * }).catch(ex => { 20 | * // Exception occured while processing request 21 | * }); 22 | */ 23 | class Delete { 24 | /** 25 | * Create an instance of the DB Delete Interface. 26 | * @param {string} appId 27 | * @param {string} collection 28 | * @param {string} url 29 | * @param {Object} options 30 | * @param {string} db 31 | * @param {string} op 32 | */ 33 | constructor(appId, collection, url, options, db, op) { 34 | this.appId = appId; 35 | this.collection = collection; 36 | this.url = url; 37 | this.db = db; 38 | this.options = Object.assign({}, options, { method: 'POST' }); 39 | this.params = { find: {} }; 40 | this.params.op = op; 41 | this.type = 'delete'; 42 | } 43 | 44 | /** 45 | * Prepares the find query 46 | * @param {...Object} conditions - The condition logic. 47 | */ 48 | where(...conditions) { 49 | this.params.find = db.generateFind(and(...conditions)); 50 | return this; 51 | } 52 | 53 | /** 54 | * Makes the query to delete a single document which matches first. 55 | * @returns {Promise} Returns a promise containing response from server. 56 | * @example 57 | * db.delete('posts').one().then(res => ...) 58 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 59 | */ 60 | one() { 61 | this.params.op = 'one'; 62 | this.options.body = JSON.stringify(this.params); 63 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/delete`); 64 | return fetchAsync(url, this.options); 65 | } 66 | 67 | /** 68 | * Makes the query to delete all the documents which match. 69 | * @returns {Promise} Returns a promise containing response from server. 70 | * @example 71 | * db.delete('posts').all().then(res => ...) 72 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 73 | */ 74 | all() { 75 | this.params.op = 'all'; 76 | this.options.body = JSON.stringify(this.params); 77 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/delete`); 78 | return fetchAsync(url, this.options); 79 | } 80 | 81 | /** 82 | * Makes the query to delete all the documents which match. 83 | * @returns {Promise} Returns a promise containing response from server. 84 | * @example 85 | * db.delete('posts').apply().then(res => ...) 86 | */ 87 | apply() { 88 | this.options.body = JSON.stringify(this.params); 89 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/delete`); 90 | return fetchAsync(url, this.options); 91 | } 92 | 93 | } 94 | 95 | module.exports = Delete; -------------------------------------------------------------------------------- /lib/db/aggregate.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | const utils = require('../utils'), 3 | fetchAsync = utils.fetchAsync; 4 | 5 | /** 6 | * Class representing the DB Aggregate Interface. 7 | * @example 8 | * import { API, cond, or, and } from 'space-api'; 9 | * 10 | * const api = new API('my-project', 'http://localhost:4122'); 11 | * const db = api.DB("mongo"); 12 | * 13 | * const pipe = [ 14 | * { $match: { status: 'A' } }, 15 | * { $group: { _id: '$cust_id', total: { $sum: '$amount' } } } 16 | * ] 17 | * 18 | * db.aggr('posts').pipe(pipe).apply().then(res => { 19 | * if (res.status === 200) { 20 | * // res.data contains the documents returned by the database 21 | * console.log('Response:', res.data); 22 | * return 23 | * } 24 | * }).catch(ex => { 25 | * // Exception occured while processing request 26 | * }); 27 | */ 28 | class Aggregate { 29 | /** 30 | * Create an instance of the DB Aggregate Interface. 31 | * @param {string} appId 32 | * @param {string} collection 33 | * @param {string} url 34 | * @param {Object} options 35 | * @param {string} db 36 | * @param {string} op 37 | */ 38 | constructor(appId, collection, url, options, db, op) { 39 | this.appId = appId; 40 | this.collection = collection; 41 | this.url = url; 42 | this.options = Object.assign({}, options, { method: 'POST' }); 43 | this.db = db; 44 | this.params = {}; 45 | this.params.op = op; 46 | } 47 | 48 | /** 49 | * Prepares the Pipe query 50 | * @param {Object[]} pipeObj - The pipeline object. 51 | */ 52 | pipe(pipeObj) { 53 | this.params.pipe = pipeObj; 54 | return this; 55 | } 56 | 57 | /** 58 | * Makes the query to return single object. 59 | * @returns {Promise} Returns a promise containing response from server. 60 | * @example 61 | * db.aggr('posts').pipe([...]).one().then(res => ...) 62 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 63 | */ 64 | one() { 65 | this.params.op = 'one'; 66 | this.options.body = JSON.stringify(this.params); 67 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/aggr`); 68 | return fetchAsync(url, this.options); 69 | } 70 | 71 | /** 72 | * Makes the query to return all objects. 73 | * @returns {Promise} Returns a promise containing response from server. 74 | * @example 75 | * db.aggr('posts').pipe([...]).all().then(res => ...) 76 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 77 | */ 78 | all() { 79 | this.params.op = 'all'; 80 | this.options.body = JSON.stringify(this.params); 81 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/aggr`); 82 | return fetchAsync(url, this.options); 83 | } 84 | 85 | /** 86 | * Makes the query to return all objects. 87 | * @returns {Promise} Returns a promise containing response from server. 88 | * @example 89 | * db.aggr('posts').pipe([...]).apply().then(res => ...) 90 | */ 91 | apply(){ 92 | this.options.body = JSON.stringify(this.params); 93 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/aggr`); 94 | return fetchAsync(url, this.options); 95 | } 96 | } 97 | 98 | module.exports = Aggregate; -------------------------------------------------------------------------------- /lib/filestore/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## FileStore 4 | **Kind**: global class 5 | 6 | * [FileStore](#FileStore) 7 | * [new FileStore(appId, url, options)](#new_FileStore_new) 8 | * [.uploadFile(path, file, name)](#FileStore+uploadFile) ⇒ Promise 9 | * [.createFolder(path, name)](#FileStore+createFolder) ⇒ Promise 10 | * [.listFiles(path)](#FileStore+listFiles) ⇒ Promise 11 | * [.delete(path)](#FileStore+delete) ⇒ Promise 12 | 13 | 14 | 15 | ### new FileStore(appId, url, options) 16 | Create an instance of the FileStore Interface. 17 | 18 | 19 | | Param | Type | 20 | | --- | --- | 21 | | appId | string | 22 | | url | string | 23 | | options | Object | 24 | 25 | 26 | 27 | ### fileStore.uploadFile(path, file, name) ⇒ Promise 28 | Uploads the given file at given path. 29 | 30 | **Kind**: instance method of [FileStore](#FileStore) 31 | **Returns**: Promise - Returns a promise containing response from server. 32 | 33 | | Param | Type | 34 | | --- | --- | 35 | | path | string | 36 | | file | file | 37 | | name | string | 38 | 39 | **Example** 40 | ```js 41 | const fileInput = document.querySelector('#your-file-input'); 42 | api.FileStore().uploadFile("/some-path", fileInput.files[0]).then(res => { 43 | if (res.status === 200) { 44 | console.log("File uploaded successfully"); 45 | } 46 | }) 47 | ``` 48 | 49 | 50 | ### fileStore.createFolder(path, name) ⇒ Promise 51 | Creates a folder with given name at given path. 52 | 53 | **Kind**: instance method of [FileStore](#FileStore) 54 | **Returns**: Promise - Returns a promise containing response from server. 55 | 56 | | Param | Type | 57 | | --- | --- | 58 | | path | string | 59 | | name | string | 60 | 61 | **Example** 62 | ```js 63 | api.FileStore().createFolder("/some-path", "my-folder").then(res => { 64 | if (res.status === 200) { 65 | console.log("Folder created successfully"); 66 | } 67 | }) 68 | ``` 69 | 70 | 71 | ### fileStore.listFiles(path) ⇒ Promise 72 | Returns list of files within a directory. 73 | 74 | **Kind**: instance method of [FileStore](#FileStore) 75 | **Returns**: Promise - Returns a promise containing response from server. 76 | 77 | | Param | Type | 78 | | --- | --- | 79 | | path | string | 80 | 81 | **Example** 82 | ```js 83 | api.FileStore().listFiles("/some-path").then(res => ...) 84 | ``` 85 | 86 | 87 | ### fileStore.delete(path) ⇒ Promise 88 | Deletes a file / folder at given path. 89 | 90 | **Kind**: instance method of [FileStore](#FileStore) 91 | **Returns**: Promise - Returns a promise containing response from server. 92 | 93 | | Param | Type | 94 | | --- | --- | 95 | | path | string | 96 | 97 | **Example** 98 | ```js 99 | api.FileStore().delete("/some-path").then(res => { 100 | if (res.status === 200) { 101 | console.log("Deleted successfully"); 102 | } 103 | }) 104 | ``` 105 | -------------------------------------------------------------------------------- /test/integration/filestore.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Upload File:
11 | 12 |
13 | 14 |

15 | 16 | 17 | Create Folder:
18 |
19 |
20 | 21 |

22 | 23 | 24 | List Directory:
25 |
26 | 27 |

28 | 29 | 30 | Delete File/Folder:
31 |
32 | 33 |

34 | 35 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /lib/websocket/websocket-client.js: -------------------------------------------------------------------------------- 1 | const generateId = require('../utils').generateId 2 | const WebSocket = require('isomorphic-ws') 3 | 4 | class WebSocketClient { 5 | constructor(url, options) { 6 | if (!url.startsWith('http')) { 7 | url = window.location.href 8 | var arr = url.split("/"); 9 | url = arr[0] + "//" + arr[2] + '/' 10 | } 11 | 12 | if (url.startsWith('http')) { 13 | url = url.replace('http', 'ws') 14 | } 15 | 16 | url = url + `v1/api/${options.projectId}/socket/json` 17 | this.url = url 18 | this.options = options 19 | this.connected = false 20 | this.isConnecting = false 21 | this.connectedOnce = false 22 | this.callbacks = {} 23 | this.onReconnectCallbacks = [] 24 | this.pendingRequests = [] 25 | } 26 | 27 | registerCallback(type, cb) { 28 | // TODO: check if cb is a function 29 | this.callbacks[type] = cb 30 | } 31 | 32 | unregisterCallback(type) { 33 | delete this.callbacks[type] 34 | } 35 | 36 | registerOnReconnectCallback(cb) { 37 | this.onReconnectCallbacks.push(cb) 38 | } 39 | 40 | connect() { 41 | this.isConnecting = true 42 | let socket = new WebSocket(this.url) 43 | socket.onopen = () => { 44 | this.isConnecting = false 45 | this.socket = socket 46 | this.connected = true 47 | 48 | // perform onconnect callbacks 49 | if (this.connectedOnce) { 50 | this.onReconnectCallbacks.forEach(cb => { 51 | cb() 52 | }); 53 | } 54 | 55 | this.connectedOnce = true 56 | 57 | // clear pending requests 58 | if (this.connected) { 59 | this.pendingRequests.forEach(req => { 60 | this.socket.send(JSON.stringify(req)) 61 | }) 62 | this.pendingRequests = [] 63 | } 64 | } 65 | socket.onmessage = e => { 66 | let res = JSON.parse(e.data) 67 | 68 | // one time callback if id exists 69 | if (res.id) { 70 | let cb = this.callbacks[res.id] 71 | if (cb) { 72 | cb(res.data) 73 | this.unregisterCallback(res.id) 74 | return 75 | } 76 | } 77 | 78 | // Normal callback procedure 79 | let cb = this.callbacks[res.type] 80 | if (cb) { 81 | cb(res.data) 82 | } 83 | } 84 | socket.onclose = () => { 85 | this.connected = false 86 | this.isConnecting = false 87 | setTimeout(() => { 88 | this.connect() 89 | }, 5000) 90 | } 91 | socket.onerror = e => { 92 | this.isConnecting = false 93 | console.log('Websocket error: ', e) 94 | } 95 | } 96 | 97 | send(type, data) { 98 | const id = generateId() 99 | 100 | const payload = { id: id, type: type, data: Object.assign({}, data, { token: this.options.token }) } 101 | // if not connected, then push the message to pending requests and try to connect 102 | if (!this.connected) { 103 | this.pendingRequests.push(payload) 104 | if (!this.isConnecting) this.connect(); 105 | return id 106 | } 107 | 108 | // send the message 109 | this.socket.send(JSON.stringify(payload)) 110 | return id 111 | } 112 | 113 | request(type, data) { 114 | const p = new Promise((resolve, reject) => { 115 | var isResolved = false 116 | const id = this.send(type, data) 117 | 118 | const timer = setTimeout(() => { 119 | reject('Websocket request has timed out') 120 | }, 10000) 121 | 122 | this.registerCallback(id, (data) => { 123 | if (!isResolved) { 124 | clearTimeout(timer) 125 | resolve(data) 126 | } 127 | }) 128 | }) 129 | return p 130 | } 131 | } 132 | 133 | module.exports = WebSocketClient -------------------------------------------------------------------------------- /lib/filestore/filestore.js: -------------------------------------------------------------------------------- 1 | const fetchAsync = require("../utils").fetchAsync; 2 | var FormData = require("form-data"); 3 | 4 | class FileStore { 5 | /** 6 | * Create an instance of the FileStore Interface. 7 | * @param {string} appId 8 | * @param {string} url 9 | * @param {Object} options 10 | */ 11 | 12 | constructor(appId, url, options) { 13 | this.appId = appId; 14 | this.url = url; 15 | this.options = options; 16 | delete this.options.headers["Content-Type"]; 17 | } 18 | 19 | /** 20 | * Uploads the given file at given path. 21 | * @param {string} path 22 | * @param {file} file 23 | * @param {string} name 24 | * @returns {Promise} Returns a promise containing response from server. 25 | * @example 26 | * const fileInput = document.querySelector('#your-file-input'); 27 | * api.FileStore().uploadFile("/some-path", fileInput.files[0]).then(res => { 28 | * if (res.status === 200) { 29 | * console.log("File uploaded successfully"); 30 | * } 31 | * }) 32 | */ 33 | uploadFile(path, file, name) { 34 | let formData = new FormData(); 35 | formData.append("file", file); 36 | formData.append("fileType", "file"); 37 | formData.append("path", path); 38 | formData.append("makeAll", "true"); 39 | if (name) { 40 | formData.append("fileName", name); 41 | } 42 | const url = `${this.url}v1/api/${this.appId}/files`; 43 | return fetchAsync( 44 | url, 45 | Object.assign({}, this.options, { method: "POST", body: formData }) 46 | ); 47 | } 48 | 49 | /** 50 | * Creates a folder with given name at given path. 51 | * @param {string} path 52 | * @param {string} name 53 | * @returns {Promise} Returns a promise containing response from server. 54 | * @example 55 | * api.FileStore().createFolder("/some-path", "my-folder").then(res => { 56 | * if (res.status === 200) { 57 | * console.log("Folder created successfully"); 58 | * } 59 | * }) 60 | */ 61 | createFolder(path, name) { 62 | let formData = new FormData(); 63 | formData.append("fileType", "folder"); 64 | formData.append("path", path); 65 | formData.append("name", name); 66 | formData.append("makeAll", "true"); 67 | const url = `${this.url}v1/api/${this.appId}/files`; 68 | return fetchAsync( 69 | url, 70 | Object.assign({}, this.options, { method: "POST", body: formData }) 71 | ); 72 | } 73 | 74 | /** 75 | * Returns list of files within a directory. 76 | * @param {string} path 77 | * @returns {Promise} Returns a promise containing response from server. 78 | * @example 79 | * api.FileStore().listFiles("/some-path").then(res => ...) 80 | */ 81 | listFiles(path) { 82 | const url = `${this.url}v1/api/${ 83 | this.appId 84 | }/files/${path}?op=list&mode=all`; 85 | return fetchAsync(url, Object.assign({}, this.options, { method: "GET" })); 86 | } 87 | 88 | /** 89 | * Deletes a file / folder at given path. 90 | * @param {string} path 91 | * @returns {Promise} Returns a promise containing response from server. 92 | * @example 93 | * api.FileStore().delete("/some-path").then(res => { 94 | * if (res.status === 200) { 95 | * console.log("Deleted successfully"); 96 | * } 97 | * }) 98 | */ 99 | delete(path) { 100 | const url = `${this.url}v1/api/${this.appId}/files/${path}?fileType=file`; 101 | return fetchAsync( 102 | url, 103 | Object.assign({}, this.options, { method: "DELETE" }) 104 | ); 105 | } 106 | 107 | deleteFolder(path) { 108 | const url = `${this.url}v1/api/${this.appId}/files/${path}?fileType=dir`; 109 | return fetchAsync( 110 | url, 111 | Object.assign({}, this.options, { method: "DELETE" }) 112 | ); 113 | } 114 | } 115 | 116 | module.exports = FileStore; 117 | -------------------------------------------------------------------------------- /lib/db/insert.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | const utils = require('../utils'), 3 | fetchAsync = utils.fetchAsync; 4 | 5 | /** 6 | * Class representing the DB Insert Interface. 7 | * @example 8 | * import { API, cond, or, and } from 'space-api'; 9 | * 10 | * const api = new API('my-project', 'http://localhost:4122'); 11 | * const db = api.DB("mongo"); 12 | * 13 | * const doc = { author: 'John', title: 'Title1', _id: 1 }; 14 | * db.insert('posts').one(doc).then(res => { 15 | * if (res.status === 200) { 16 | * // Document was inserted successfully 17 | * return; 18 | * } 19 | * }).catch(ex => { 20 | * // Exception occured while processing request 21 | * }); 22 | */ 23 | class Insert { 24 | 25 | /** 26 | * Create an instance of the DB Insert Interface. 27 | * @param {string} appId 28 | * @param {string} collection 29 | * @param {string} url 30 | * @param {Object} options 31 | * @param {string} db 32 | */ 33 | constructor(appId, collection, url, options, db) { 34 | this.appId = appId; 35 | this.collection = collection; 36 | this.url = url; 37 | this.db = db; 38 | this.options = Object.assign({}, options, { method: 'POST' }); 39 | this.params = {}; 40 | this.type = 'create'; 41 | } 42 | 43 | /** 44 | * Makes the query to insert a single document. 45 | * @param {Object} doc - The document to be inserted. 46 | * @returns {Promise} Returns a promise containing response from server. 47 | * @example 48 | * const doc = { author: 'John', title: 'Title1', _id: 1 }; 49 | * db.insert('posts').one(doc).then(res => ...) 50 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use doc and apply instead. 51 | */ 52 | one(doc) { 53 | this.params.doc = doc; 54 | this.params.op = 'one'; 55 | this.options.body = JSON.stringify(this.params); 56 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/create`); 57 | return fetchAsync(url, this.options); 58 | } 59 | 60 | /** 61 | * Makes the query to insert multiple documents. 62 | * @param {Object[]} docs - The documents to be inserted. 63 | * @returns {Promise} Returns a promise containing response from server. 64 | * @example 65 | * const docs = [{ author: 'John', title: 'Title1', _id: 1 }]; 66 | * db.insert('posts').all(docs).then(res => ...) 67 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use docs and apply instead. 68 | */ 69 | all(docs) { 70 | this.params.doc = docs; 71 | this.params.op = 'all'; 72 | this.options.body = JSON.stringify(this.params); 73 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/create`); 74 | return fetchAsync(url, this.options); 75 | } 76 | 77 | /** 78 | * Makes the query to insert multiple documents. 79 | * @param {Object[]} docs - The documents to be inserted. 80 | * @returns {Promise} Returns a promise containing response from server. 81 | * @example 82 | * const docs = [{ author: 'John', title: 'Title1', _id: 1 }]; 83 | * db.insert('posts').docs(docs).apply().then(res => ...) 84 | */ 85 | apply(){ 86 | this.options.body = JSON.stringify(this.params); 87 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/create`); 88 | return fetchAsync(url, this.options); 89 | } 90 | 91 | /** 92 | * Accepts the documents to be inserted. 93 | * @param {Object[]} docs - The documents to be inserted. 94 | */ 95 | docs(docs){ 96 | this.params.op = 'all'; 97 | this.params.doc = docs; 98 | return this; 99 | } 100 | 101 | /** 102 | * Accepts the document to be inserted. 103 | * @param {Object[]} doc - The document to be inserted. 104 | */ 105 | doc(doc){ 106 | this.params.op = 'one'; 107 | this.params.doc = doc; 108 | return this; 109 | } 110 | } 111 | 112 | module.exports = Insert; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | 3 | // Fetch asynchronuosly 4 | exports.fetchAsync = (url, options) => { 5 | return new Promise((resolve, reject) => { 6 | fetch(url, options) 7 | .then(response => { 8 | const status = response.status; 9 | response 10 | .json() 11 | .then(data => { 12 | resolve({ status: status, data: data }); 13 | }) 14 | .catch(error => { 15 | reject(error); 16 | }); 17 | }) 18 | .catch(error => { 19 | reject(error); 20 | }); 21 | }); 22 | }; 23 | 24 | exports.cond = (f1, op, f2) => { 25 | return { type: "cond", f1: f1, op: op, f2: f2 }; 26 | }; 27 | 28 | exports.and = (...conditions) => { 29 | return { type: "and", clauses: conditions }; 30 | }; 31 | 32 | exports.or = (...conditions) => { 33 | return { type: "or", clauses: conditions }; 34 | }; 35 | 36 | exports.generateId = () => { 37 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { 38 | var r = (Math.random() * 16) | 0, 39 | v = c == "x" ? r : (r & 0x3) | 0x8; 40 | return v.toString(16); 41 | }); 42 | }; 43 | 44 | const validate = (find, obj) => { 45 | const keys = Object.keys(find); 46 | const len = keys.length; 47 | for (let i = 0; i < len; i++) { 48 | const key = keys[i]; 49 | if (key === "$or") { 50 | const objs = Object.keys(find[key]); 51 | const len1 = objs.length; 52 | for (let j = 0; j < len1; j++) { 53 | // If any one condition gets satisfied return true 54 | if (validate(objs[j], obj)) return true; 55 | } 56 | // Return false if no condition is matched 57 | return false; 58 | } 59 | // Return false if the obj does not contain the required field 60 | if (!loadValue(obj, key).found) return false; 61 | 62 | if (typeof find[key] !== "object") 63 | return loadValue(find, key).value === loadValue(obj, key).value; 64 | const subKeys = Object.keys(find[key]); 65 | const len1 = subKeys.length; 66 | for (let j = 0; j < len1; j++) { 67 | const subKey = subKeys[j]; 68 | const val1 = loadValue(obj, key); 69 | const val2 = find[key][subKey]; 70 | switch (subKey) { 71 | case "$eq": 72 | if (val1 !== val2) return false; 73 | break; 74 | case "$neq": 75 | if (val1 === val2) return false; 76 | break; 77 | case "$gt": 78 | if (val1 <= val2) return false; 79 | break; 80 | case "$gte": 81 | if (val1 < val2) return false; 82 | break; 83 | case "$lt": 84 | if (val1 >= val2) return false; 85 | break; 86 | case "$lte": 87 | if (val1 > val2) return false; 88 | break; 89 | case "default": 90 | return false; 91 | } 92 | } 93 | } 94 | return true; 95 | }; 96 | 97 | const loadValue = (state, key) => { 98 | const keys = key.split("."); 99 | let result = { 100 | found: false, 101 | value: undefined 102 | }; 103 | let len = keys.length; 104 | for (let i = 0; i < len; i++) { 105 | if ( 106 | state === undefined || 107 | state === null || 108 | !isObject(state) || 109 | !state.hasOwnProperty(keys[i]) 110 | ) { 111 | break; 112 | } 113 | if (i === len - 1) 114 | result = { 115 | found: true, 116 | value: state[keys[i]] 117 | }; 118 | else state = state[keys[i]]; 119 | } 120 | return result; 121 | }; 122 | 123 | const storeValue = (state, key, value) => { 124 | const keys = key.split("."); 125 | let ok = true; 126 | let len = keys.length; 127 | for (let i = 0; i < len; i++) { 128 | if (!state.hasOwnProperty(keys[i])) { 129 | ok = false; 130 | break; 131 | } 132 | if (i === len - 1) state[keys[i]] = value; 133 | else state = state[keys[i]]; 134 | } 135 | return ok; 136 | }; 137 | 138 | const isObject = value => { 139 | return value && typeof value === "object" && value.constructor === Object; 140 | }; 141 | 142 | exports.loadValue = loadValue; 143 | exports.storeValue = storeValue; 144 | exports.validate = validate; 145 | -------------------------------------------------------------------------------- /lib/realtime/README.md: -------------------------------------------------------------------------------- 1 | ## Classes 2 | 3 |
4 |
LiveQuery
5 |

Class representing the LiveQuery Interface.

6 |
7 |
8 | 9 | ## Functions 10 | 11 |
12 |
OnSnapshot(docs, type, find, changedDoc)
13 |

Callback for realtime updates to the subscribed data

14 |
15 |
OnError(err)
16 |

Callback for error while subscribing

17 |
18 |
Unsubscribe()
19 |

The function to unsubscribe the subscription

20 |
21 |
22 | 23 | 24 | 25 | ## LiveQuery 26 | Class representing the LiveQuery Interface. 27 | 28 | **Kind**: global class 29 | 30 | * [LiveQuery](#LiveQuery) 31 | * [new LiveQuery(appId, db, collection, client, store)](#new_LiveQuery_new) 32 | * [.where(...conditions)](#LiveQuery+where) 33 | * [.options(opts)](#LiveQuery+options) 34 | * [.subscribe(onSnapshot, onError)](#LiveQuery+subscribe) ⇒ [Unsubscribe](#Unsubscribe) 35 | 36 | 37 | 38 | ### new LiveQuery(appId, db, collection, client, store) 39 | Create an instance of the LiveQuery Interface. 40 | 41 | 42 | | Param | Type | 43 | | --- | --- | 44 | | appId | string | 45 | | db | string | 46 | | collection | string | 47 | | client | WebSocketClient | 48 | | store | Object | 49 | 50 | **Example** 51 | ```js 52 | import { API, cond, or, and } from 'space-api'; 53 | const api = new API('my-project'); 54 | 55 | // Create database instance 56 | const db = api.DB("mongo"); 57 | 58 | const onSnapshot = (docs, type, find, changedDoc) => { 59 | console.log(docs, type, find, changedDoc) 60 | } 61 | 62 | const onError = (err) => { 63 | console.log('Live query error', err) 64 | } 65 | 66 | let subscription = db.liveQuery('posts').where({}).subscribe(onSnapshot, onError) 67 | 68 | subscription.unsubscribe() 69 | ``` 70 | 71 | 72 | ### liveQuery.where(...conditions) 73 | Prepares the find query 74 | 75 | **Kind**: instance method of [LiveQuery](#LiveQuery) 76 | 77 | | Param | Type | Description | 78 | | --- | --- | --- | 79 | | ...conditions | Object | The condition logic. | 80 | 81 | 82 | 83 | ### liveQuery.options(opts) 84 | Sets the options for the live query 85 | 86 | **Kind**: instance method of [LiveQuery](#LiveQuery) 87 | 88 | | Param | Type | Description | 89 | | --- | --- | --- | 90 | | opts | Object | The options. (Of the form { changesOnly: true|false }) | 91 | 92 | 93 | 94 | ### liveQuery.subscribe(onSnapshot, onError) ⇒ [Unsubscribe](#Unsubscribe) 95 | Subscribes for real time updates 96 | 97 | **Kind**: instance method of [LiveQuery](#LiveQuery) 98 | **Returns**: [Unsubscribe](#Unsubscribe) - Returns a unsubscribe function 99 | 100 | | Param | Type | Description | 101 | | --- | --- | --- | 102 | | onSnapshot | [OnSnapshot](#OnSnapshot) | OnSnapshot callback | 103 | | onError | [OnError](#OnError) | OnError callback | 104 | 105 | 106 | 107 | ## OnSnapshot(docs, type, find, changedDoc) 108 | Callback for realtime updates to the subscribed data 109 | 110 | **Kind**: global function 111 | 112 | | Param | Type | Description | 113 | | --- | --- | --- | 114 | | docs | Array | The updated docs | 115 | | type | string | The type of operation performed | 116 | | find | Object | The object containing those fields of the concerned doc that form it's unique identity (i.e the primary field or the fields in a unique index) | 117 | | changedDoc | Object | The doc that changed | 118 | 119 | 120 | 121 | ## OnError(err) 122 | Callback for error while subscribing 123 | 124 | **Kind**: global function 125 | 126 | | Param | Type | Description | 127 | | --- | --- | --- | 128 | | err | string | Error during the liveSubscribe | 129 | 130 | 131 | 132 | ## Unsubscribe() 133 | The function to unsubscribe the subscription 134 | 135 | **Kind**: global function 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Client API for Space Cloud 2 | 3 | ## Installation 4 | Install via. npm 5 | ```bash 6 | $ npm install space-api --save 7 | ``` 8 | or import as a stand alone library 9 | ```html 10 | 11 | ``` 12 | 13 | ## Documentation 14 | The complete documentation can be found [here](https://github.com/spaceuptech/space-api-js/wiki). 15 | 16 | Documentation for specific databases is given below: 17 | - [MongoDB](https://github.com/spaceuptech/space-api-js/wiki/Mongo) 18 | - [SQL Databases](https://github.com/spaceuptech/space-api-js/wiki/SQL) 19 | 20 | Documentation for real time feature is given below: 21 | - [Realtime Feature](https://github.com/spaceuptech/space-api-js/wiki/Realtime) 22 | 23 | ## Quick Start 24 | 25 | ### Create Client Instance 26 | 27 | ```js 28 | import { API, and, or, cond } from 'space-api'; 29 | 30 | const api = new API('demo-project', 'http://localhost:4122'); 31 | const db = api.DB("mongo") // Put your database alias name here to create DB instance 32 | ``` 33 | **Note: Multiple databases may be used simultaneously.** 34 | 35 | ### Insert a document into the database 36 | ```js 37 | const doc = {_id: 1, title: "Title 1", content: "My first record"}; 38 | db.insert('COLLECTION_NAME').doc(doc).apply().then(res => { 39 | if (res.status === 200) { 40 | // Document inserted successfully 41 | return; 42 | } 43 | }).catch(ex => { 44 | // Exception occured while processing request 45 | }); 46 | ``` 47 | **Note: Always insert documents with a id in order to use the realtime feature.** 48 | 49 | ### Query documents in database 50 | ```js 51 | const find = and(cond('title', '==', 'Title1'), cond('author', '==', 'Jon')); 52 | db.get('COLLECTION_NAME').where(find) 53 | .skip(10).limit(10) 54 | .apply() 55 | .then(res => { 56 | if (res.status === 200) { 57 | // res.data contains the documents returned by the database 58 | console.log('Response:', res.data); 59 | return; 60 | } 61 | }).catch(ex => { 62 | // Exception occured while processing request 63 | }); 64 | ``` 65 | 66 | ### Update documents in database 67 | ```js 68 | const find = and(cond('author', '==', 'Jon')); 69 | db.update('COLLECTION_NAME').where(find) 70 | .set({ author: 'John' }) 71 | .apply() 72 | .then(res => { 73 | if (res.status === 200) { 74 | // Document updated successfully 75 | return; 76 | } 77 | }).catch(ex => { 78 | // Exception occured while processing request 79 | }); 80 | ``` 81 | 82 | ### Delete documents in database 83 | ```js 84 | const find = and(cond('author', '==', 'John')); 85 | db.delete('COLLECTION_NAME').where(find) 86 | .apply() 87 | .then(res => { 88 | if (res.status === 200) { 89 | // Document deleted successfully 90 | return; 91 | } 92 | }).catch(ex => { 93 | // Exception occured while processing request 94 | }); 95 | ``` 96 | 97 | ### Get real time updates 98 | ```js 99 | const onSnapshot = (docs, type, find, changedDoc) => { 100 | if (type === 'initial') { 101 | console.log('Initial docs ', docs) 102 | return 103 | } 104 | console.log(docs, type, find, changedDoc) 105 | } 106 | 107 | const onError = (err) => { 108 | console.log('Monitor error', err) 109 | } 110 | 111 | let subscription = db.liveQuery('posts').where().subscribe(onSnapshot, onError) 112 | 113 | subscription.unsubscribe() 114 | ``` 115 | 116 | ### Upload file 117 | ```js 118 | const fileInput = document.querySelector('#your-file-input'); 119 | api.FileStore().uploadFile("/some-path", fileInput.files[0]) 120 | .then(res => { 121 | if (res.status === 200) { 122 | console.log("File uploaded successfully"); 123 | } 124 | }).catch(ex => { 125 | // Exception occured while uploading file 126 | }) 127 | ``` 128 | 129 | ### Call functions directly (Function as a Service) 130 | ```js 131 | api.call('my-engine', 'my-func', { msg: 'Function as a Service is awesome!' }, 1000) 132 | .then(res => { 133 | if (res.status === 200) { 134 | console.log('Response: ', res.data) 135 | } 136 | }).catch(ex => { 137 | // Exception occured while processing request 138 | }) 139 | ``` 140 | 141 | ## License 142 | 143 | Copyright 2018 Noorain Panjwani 144 | 145 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 146 | 147 | http://www.apache.org/licenses/LICENSE-2.0 148 | 149 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 150 | -------------------------------------------------------------------------------- /lib/realtime/liveQuery.js: -------------------------------------------------------------------------------- 1 | const db = require('../db/db') 2 | const and = require('../utils').and 3 | const WebSocketClient = require('../websocket/websocket-client').WebSocketClient 4 | const generateId = require('../utils').generateId; 5 | const snapshotCallback = require('./helper').snapshotCallback 6 | const LiveQuerySubscription = require('./subscription') 7 | 8 | /** 9 | * Class representing the LiveQuery Interface. 10 | * @example 11 | * import { API, cond, or, and } from 'space-api'; 12 | * const api = new API('my-project'); 13 | * 14 | * // Create database instance 15 | * const db = api.DB("mongo"); 16 | * 17 | * const onSnapshot = (docs, type, find, changedDoc) => { 18 | * console.log(docs, type, find, changedDoc) 19 | * } 20 | * 21 | * const onError = (err) => { 22 | * console.log('Live query error', err) 23 | * } 24 | * 25 | * let subscription = db.liveQuery('posts').where({}).subscribe(onSnapshot, onError) 26 | * 27 | * subscription.unsubscribe() 28 | */ 29 | 30 | class LiveQuery { 31 | /** 32 | * Create an instance of the LiveQuery Interface. 33 | * @param {string} appId 34 | * @param {string} db 35 | * @param {string} collection 36 | * @param {WebSocketClient} client 37 | * @param {Object} store 38 | */ 39 | 40 | constructor(appId, db, collection, client, store) { 41 | this.appId = appId 42 | this.db = db 43 | this.collection = collection 44 | this.client = client 45 | this.store = store 46 | this.params = { find: {} } 47 | this.opts = { skipInitial: false } 48 | } 49 | 50 | /** 51 | * Prepares the find query 52 | * @param {...Object} conditions - The condition logic. 53 | */ 54 | where(...conditions) { 55 | this.find = db.generateFind(and(...conditions)); 56 | return this; 57 | } 58 | 59 | /** 60 | * Sets the options for the live query 61 | * @param {Object} opts - The options. (Of the form { changesOnly: true|false }) 62 | */ 63 | options(opts) { 64 | if (opts != {}) { 65 | this.opts = opts; 66 | } 67 | return this; 68 | } 69 | 70 | skipInitial() { 71 | this.opts.skipInitial = true 72 | } 73 | 74 | addSubscription(id, onSnapshot, onError) { 75 | const subscription = { onSnapshot: onSnapshot, onError: onError } 76 | this.store[this.db][this.collection][id].subscription = subscription 77 | return () => { 78 | // TODO: Handle the errors for realtime-unsubscribe 79 | this.client.request('realtime-unsubscribe', { group: this.collection, dbType: this.db, id: id, options: this.opts }) 80 | delete this.store[this.db][this.collection][id] 81 | } 82 | } 83 | 84 | /** 85 | * Callback for realtime updates to the subscribed data 86 | * @name OnSnapshot 87 | * @function 88 | * @param {Array} docs The updated docs 89 | * @param {string} type The type of operation performed 90 | * @param {Object} find The object containing those fields of the concerned doc that form it's unique identity (i.e the primary field or the fields in a unique index) 91 | * @param {Object} changedDoc The doc that changed 92 | */ 93 | 94 | /** 95 | * Callback for error while subscribing 96 | * @name OnError 97 | * @function 98 | * @param {string} err - Error during the liveSubscribe 99 | */ 100 | 101 | /** 102 | * The function to unsubscribe the subscription 103 | * @name Unsubscribe 104 | * @function 105 | */ 106 | 107 | /** 108 | * Subscribes for real time updates 109 | * @param {OnSnapshot} onSnapshot - OnSnapshot callback 110 | * @param {OnError} onError - OnError callback 111 | * @returns {Unsubscribe} Returns a unsubscribe function 112 | */ 113 | subscribe(onSnapshot, onError) { 114 | const id = generateId() 115 | return this.subscribeRaw(id, onSnapshot, onError) 116 | } 117 | 118 | subscribeRaw(id, onSnapshot, onError) { 119 | const req = { id: id, group: this.collection, where: this.find, dbType: this.db, project: this.appId, options: this.opts } 120 | 121 | if (!this.store[this.db]) { 122 | this.store[this.db] = {} 123 | } 124 | if (!this.store[this.db][this.collection]) { 125 | this.store[this.db][this.collection] = {} 126 | } 127 | this.store[this.db][this.collection][id] = { snapshot: [], subscription: {}, find: this.find, opts: this.opts } 128 | 129 | // Add subscription to store 130 | const unsubscribe = this.addSubscription(id, onSnapshot, onError) 131 | 132 | // Send subscribe request to server 133 | this.client.request('realtime-subscribe', req).then(data => { 134 | // Unsubscribe if ack is false 135 | if (!data.ack) { 136 | onError(data.error) 137 | unsubscribe() 138 | return 139 | } 140 | snapshotCallback(this.store, data.docs) 141 | }).catch(e => { 142 | // Unsubscribe on error 143 | onError(e) 144 | unsubscribe() 145 | }) 146 | 147 | this.store[this.db][this.collection][id].subscriptionObject = new LiveQuerySubscription(unsubscribe, []) 148 | return this.store[this.db][this.collection][id].subscriptionObject 149 | } 150 | } 151 | 152 | module.exports = LiveQuery -------------------------------------------------------------------------------- /lib/db/update.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | const utils = require('../utils'), 3 | fetchAsync = utils.fetchAsync, 4 | and = utils.and; 5 | 6 | /** 7 | * Class representing the DB Update Interface. 8 | * @example 9 | * import { API, cond, or, and } from 'space-api'; 10 | * 11 | * const api = new API('my-project', 'http://localhost:4122'); 12 | * const db = api.DB("mongo"); 13 | * 14 | * db.update('posts').where(and(cond('title', '==', 'Title1'))).set({ title: 'Title2' }).all().then(res => { 15 | * if (res.status === 200) { 16 | * // The documents were updated successfully 17 | * return; 18 | * } 19 | * }).catch(ex => { 20 | * // Exception occured while processing request 21 | * }); 22 | */ 23 | class Update { 24 | /** 25 | * Create an instance of the DB Update Interface. 26 | * @param {string} appId 27 | * @param {string} collection 28 | * @param {string} url 29 | * @param {Object} options 30 | * @param {string} db 31 | * @param {string} op 32 | */ 33 | constructor(appId, collection, url, options, db, op) { 34 | this.appId = appId; 35 | this.collection = collection; 36 | this.url = url; 37 | this.db = db; 38 | this.options = Object.assign({}, options, { method: 'POST' }); 39 | this.params = { find: {}, update: { } }; 40 | this.params.op = op; 41 | this.type = 'update'; 42 | 43 | } 44 | 45 | /** 46 | * Prepares the find query 47 | * @param {...Object} conditions - The condition logic. 48 | */ 49 | where(...conditions) { 50 | this.params.find = db.generateFind(and(...conditions)); 51 | return this; 52 | } 53 | 54 | /** 55 | * Sets the value of a field in a document. 56 | * @param {Object} obj - The Object containing fields to set. 57 | * @example 58 | * db.update('posts').set({ author: 'Drake' }).all().then(res => ...) 59 | */ 60 | set(obj) { 61 | this.params.update.$set = obj; 62 | return this; 63 | } 64 | 65 | /** 66 | * Adds an item to an array. 67 | * @param {Object} obj - The Object containing fields to set. 68 | * @example 69 | * db.update('posts').push({ author: 'Drake' }).all().then(res => ...) 70 | */ 71 | push(obj) { 72 | this.params.update.$push = obj; 73 | return this; 74 | } 75 | 76 | /** 77 | * Removes the specified field from a document. 78 | * @param {...string} fields - The fields to remove. 79 | * @example 80 | * db.update('posts').remove('age', 'likes').all().then(res => ...) 81 | */ 82 | remove(...fields) { 83 | this.params.update.$unset = fields.reduce((prev, curr) => Object.assign(prev, { [curr]: '' }), {}); 84 | return this; 85 | } 86 | 87 | /** 88 | * Renames the specified field. 89 | * @param {Object} obj - The object containing fields to rename. 90 | * @example 91 | * db.update('posts').rename({ mobile: 'contact' }).all().then(res => ...) 92 | */ 93 | rename(obj) { 94 | this.params.update.$rename = obj; 95 | return this; 96 | } 97 | 98 | /** 99 | * Increments the value of the field by the specified amount. 100 | * @param {Object} obj - The object containing fields to increment along with the value. 101 | * @example 102 | * // The value of added with 1 103 | * db.update('posts').inc({ views: 1 }).all().then(res => ...) 104 | */ 105 | inc(obj) { 106 | this.params.update.$inc = obj; 107 | return this; 108 | } 109 | 110 | /** 111 | * Multiplies the value of the field by the specified amount. 112 | * @param {Object} obj - The object containing fields to multiply along with the value. 113 | * @example 114 | * // The value of amount will be multiplied by 4 115 | * db.update('posts').mul({ amount: 4 }).all().then(res => ...) 116 | */ 117 | mul(obj) { 118 | this.params.update.$mul = obj; 119 | return this; 120 | } 121 | 122 | /** 123 | * Only updates the field if the specified value is greater than the existing field value. 124 | * @param {Object} obj - The object containing fields to set. 125 | * @example 126 | * db.update('posts').max({ highScore: 1200 }).all().then(res => ...) 127 | */ 128 | max(obj) { 129 | this.params.update.$max = obj; 130 | return this; 131 | } 132 | 133 | /** 134 | * Only updates the field if the specified value is lesser than the existing field value. 135 | * @param {Object} obj - The object containing fields to set. 136 | * @example 137 | * db.update('posts').min({ lowestScore: 300 }).all().then(res => ...) 138 | */ 139 | min(obj) { 140 | this.params.update.$min = obj; 141 | return this; 142 | } 143 | 144 | /** 145 | * Sets the value of a field to current timestamp. 146 | * @param {...string} values - The fields to set. 147 | * @example 148 | * db.update('posts').currentTimestamp('lastModified').all().then(res => ...) 149 | */ 150 | currentTimestamp(...values) { 151 | this.params.update.$currentDate = Object.assign({}, this.params.update.$currentDate, values.reduce((prev, curr) => { 152 | return Object.assign(prev, { [curr]: { $type: 'timestamp' } }); 153 | }, {})); 154 | return this; 155 | } 156 | 157 | /** 158 | * Sets the value of a field to current date. 159 | * @param {...string} values - The fields to set. 160 | * @example 161 | * db.update('posts').currentDate('lastModified').all().then(res => ...) 162 | */ 163 | currentDate(...values) { 164 | this.params.update.$currentDate = Object.assign({}, this.params.update.$currentDate, values.reduce((prev, curr) => { 165 | return Object.assign(prev, { [curr]: { $type: 'date' } }); 166 | }, {})); 167 | return this; 168 | } 169 | 170 | /** 171 | * Makes the query to update a single document which matches first. 172 | * @returns {Promise} Returns a promise containing response from server 173 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 174 | */ 175 | one() { 176 | this.params.op = 'one'; 177 | this.options.body = JSON.stringify(this.params); 178 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/update`); 179 | return fetchAsync(url, this.options); 180 | } 181 | 182 | /** 183 | * Makes the query to update all documents which matches. 184 | * @returns {Promise} Returns a promise containing response from server 185 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 186 | */ 187 | all() { 188 | this.params.op = 'all'; 189 | this.options.body = JSON.stringify(this.params); 190 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/update`); 191 | return fetchAsync(url, this.options); 192 | } 193 | 194 | /** 195 | * Makes the query to update all documents which matches. 196 | * @returns {Promise} Returns a promise containing response from server 197 | */ 198 | apply(){ 199 | this.options.body = JSON.stringify(this.params); 200 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/update`); 201 | return fetchAsync(url, this.options); 202 | } 203 | 204 | /** 205 | * Makes the query to update all, else insert a document. 206 | * @returns {Promise} Returns a promise containing response from server 207 | */ 208 | upsert() { 209 | this.params.op = 'upsert'; 210 | this.options.body = JSON.stringify(this.params); 211 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/update`); 212 | return fetchAsync(url, this.options); 213 | } 214 | 215 | } 216 | 217 | module.exports = Update; 218 | -------------------------------------------------------------------------------- /lib/api.js: -------------------------------------------------------------------------------- 1 | const DB = require("./db/db").DB; 2 | const Client = require("./websocket/websocket-client"); 3 | const Realtime = require("./realtime/realtime"); 4 | const FileStore = require("./filestore/filestore"); 5 | const fetchAsync = require("./utils").fetchAsync; 6 | 7 | /** 8 | * The DB Client Interface. 9 | * @external DB 10 | * @see {@link https://github.com/spaceuptech/space-api-js/wiki/DB} 11 | */ 12 | 13 | /** 14 | * The Realtime Client Interface. 15 | * @external Realtime 16 | * @see {@link https://github.com/spaceuptech/space-api-js/wiki/Realtime} 17 | */ 18 | 19 | /** 20 | * The FileStore Client Interface. 21 | * @external FileStore 22 | * @see {@link https://github.com/spaceuptech/space-api-js/wiki/FileStore} 23 | */ 24 | 25 | /** 26 | * Class representing the client api. 27 | * @example 28 | * import { API } from 'space-api'; 29 | * 30 | * const api = new API('my-project', 'http://localhost:4122'); 31 | */ 32 | class Api { 33 | /** 34 | * Create an instance of the Client API. 35 | * @param {string} projectId - The Project Id. 36 | * @param {string} url - Base url of space-cloud server. 37 | */ 38 | constructor(projectId, url = "/") { 39 | if (!url.endsWith("/")) { 40 | url = url + "/"; 41 | } 42 | this.appId = projectId; 43 | this.url = url; 44 | this.options = { 45 | credentials: "include", 46 | headers: { 47 | "Content-Type": "application/json" 48 | } 49 | }; 50 | this.webSocketOptions = { projectId: projectId }; 51 | this.serviceOptions = { project: projectId }; 52 | let client = new Client(url, this.webSocketOptions); 53 | this.client = client; 54 | this.realTime = new Realtime(projectId, client); 55 | } 56 | 57 | /** 58 | * Initialse the Client Api with the JWT token 59 | * @param {string} token - The signed JWT token received from the server on successful authentication. 60 | */ 61 | setToken(token) { 62 | this.serviceOptions.token = token; 63 | this.webSocketOptions.token = token; 64 | this.options.headers.Authorization = "Bearer " + token; 65 | } 66 | 67 | /** 68 | * Set the new Project Id 69 | * @param {string} projectId - The Project Id. 70 | */ 71 | setProjectId(projectId) { 72 | this.appId = projectId; 73 | this.serviceOptions.project = projectId; 74 | } 75 | 76 | /** 77 | * Returns a Mongo DB client instance 78 | * @returns {external:DB} DB client instance 79 | * @deprecated since version v0.15.0. Will be deleted in version 1.0.0. Use DB instead. 80 | */ 81 | Mongo() { 82 | return new DB(this.appId, this.url, this.options, "mongo", this.realTime); 83 | } 84 | 85 | /** 86 | * Returns a Postgres DB client instance 87 | * @returns {external:DB} DB client instance 88 | * @deprecated since version v0.15.0. Will be deleted in version 1.0.0. Use DB instead. 89 | */ 90 | Postgres() { 91 | return new DB(this.appId, this.url, this.options, "sql-postgres", this.realTime); 92 | } 93 | 94 | /** 95 | * Returns a MySQL Db client instance 96 | * @returns {external:DB} Db client instance 97 | * @deprecated since version v0.15.0. Will be deleted in version 1.0.0. Use DB instead. 98 | */ 99 | MySQL() { 100 | return new DB(this.appId, this.url, this.options, "sql-mysql", this.realTime); 101 | } 102 | 103 | /** 104 | * Returns a DB client instance 105 | * @param {string} db - The alias name of the database 106 | * @returns {external:DB} DB client instance 107 | */ 108 | DB(db) { 109 | return new DB(this.appId, this.url, this.options, db, this.realTime); 110 | } 111 | 112 | /** 113 | * Calls an endpoint from the remote service 114 | * @param {string} service - The name of service 115 | * @param {string} endpoint - The name of endpoint to be called 116 | * @param {Object} params - The params to be sent to the remote service 117 | * @param {string} [timeout = 5000] - Timeout in milliseconds 118 | * @returns {Promise} Returns a promise 119 | * @example 120 | * api.call('my_service', 'my_endpoint', { msg: 'Remote services are awesome!' }, 1000) 121 | * .then(res => { 122 | * if (res.status === 200) { 123 | * // res.data contains the response given by the function 124 | * console.log('Response:', res.data); 125 | * return; 126 | * } 127 | * }).catch(ex => { 128 | * // Exception occured while processing request 129 | * }); 130 | */ 131 | 132 | call(service, endpoint, params, timeout) { 133 | if (timeout === undefined) timeout = 5000; 134 | const url = `${this.url}v1/api/${this.appId}/services/${service}/${endpoint}`; 135 | const options = Object.assign({}, this.options, { 136 | method: "POST", 137 | body: JSON.stringify({ timeout: timeout, params: params }) 138 | }); 139 | return fetchAsync(url, options); 140 | } 141 | 142 | /** 143 | * Queues an event 144 | * @param {string} type - The type of event 145 | * @param {string} payload - Event payload 146 | * @returns {Promise} Returns a promise 147 | * @example 148 | * const res = await api.queueEvent("event-type", {"foo": "bar"}) 149 | * .delay(0) 150 | * .date(new Date("2025-06-25")) 151 | * .options({}) 152 | * .apply() 153 | */ 154 | queueEvent(type, payload) { 155 | return new QueueEvent(this.appId, this.url, this.options, type, payload) 156 | } 157 | 158 | /** 159 | * Returns a FileStore client instance 160 | * @returns {external:FileStore} FileStore client instance 161 | */ 162 | FileStore() { 163 | return new FileStore(this.appId, this.url, this.options); 164 | } 165 | } 166 | 167 | /** 168 | * Class representing the Queue Event Interface. 169 | * @example 170 | * import { API, cond, or, and } from 'space-api'; 171 | * 172 | * const api = new API('my-project', 'http://localhost:4122'); 173 | * const res = await api.queueEvent("event-type", {"foo": "bar"}) 174 | * .delay(0) 175 | * .date(new Date("2025-06-25")) 176 | * .options({}) 177 | * .apply() 178 | */ 179 | class QueueEvent { 180 | /** 181 | * Create an instance of the DB Get Interface. 182 | * @param {string} appId 183 | * @param {string} url 184 | * @param {Object} options 185 | * @param {string} eventType 186 | * @param {Object} eventPayload 187 | */ 188 | constructor(appId, url, options, eventType, eventPayload) { 189 | this.appId = appId; 190 | this.url = url; 191 | this.options = Object.assign({}, options, { method: 'POST' }); 192 | this.params = { 193 | type: eventType, 194 | delay: 0, 195 | payload: eventPayload, 196 | options: {}, 197 | isSynchronous: false 198 | } 199 | } 200 | 201 | /** 202 | * Queues the event synchronously 203 | */ 204 | synchronous() { 205 | this.params.isSynchronous = true; 206 | return this; 207 | } 208 | 209 | /** 210 | * Seconds to delay the webhook trigger by 211 | * @param {Number} delay - delay in seconds. 212 | */ 213 | delay(delay) { 214 | this.params.delay = delay; 215 | return this; 216 | } 217 | 218 | /** 219 | * Date to schedule the event for 220 | * @param {Object} date - Date object. 221 | */ 222 | date(date) { 223 | this.params.timestamp = date; 224 | return this; 225 | } 226 | 227 | /** 228 | * Makes the query to queue the event. In case of synchronous events it will resolve to the response object from the webhook. 229 | * @returns {Promise} Returns a promise containing response from server. 230 | */ 231 | apply() { 232 | if (!this.params.timestamp) { 233 | this.params.timestamp = new Date() 234 | } 235 | 236 | this.options.body = JSON.stringify(this.params); 237 | const url = `${this.url}v1/api/${this.appId}/eventing/queue` 238 | return fetchAsync(url, this.options); 239 | } 240 | } 241 | 242 | module.exports = Api; 243 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | ## Classes 2 | 3 |
4 |
Api
5 |

Class representing the client api.

6 |
7 |
QueueEvent
8 |

Class representing the Queue Event Interface.

9 |
10 |
11 | 12 | ## External 13 | 14 |
15 |
DB
16 |

The DB Client Interface.

17 |
18 |
Realtime
19 |

The Realtime Client Interface.

20 |
21 |
FileStore
22 |

The FileStore Client Interface.

23 |
24 |
25 | 26 | 27 | 28 | ## Api 29 | Class representing the client api. 30 | 31 | **Kind**: global class 32 | 33 | * [Api](#Api) 34 | * [new Api(projectId, url)](#new_Api_new) 35 | * [.setToken(token)](#Api+setToken) 36 | * [.setProjectId(projectId)](#Api+setProjectId) 37 | * ~~[.Mongo()](#Api+Mongo) ⇒ [DB](#external_DB)~~ 38 | * ~~[.Postgres()](#Api+Postgres) ⇒ [DB](#external_DB)~~ 39 | * ~~[.MySQL()](#Api+MySQL) ⇒ [DB](#external_DB)~~ 40 | * [.DB(db)](#Api+DB) ⇒ [DB](#external_DB) 41 | * [.call(service, endpoint, params, [timeout])](#Api+call) ⇒ Promise 42 | * [.queueEvent(type, payload)](#Api+queueEvent) ⇒ Promise 43 | * [.FileStore()](#Api+FileStore) ⇒ [FileStore](#external_FileStore) 44 | 45 | 46 | 47 | ### new Api(projectId, url) 48 | Create an instance of the Client API. 49 | 50 | 51 | | Param | Type | Default | Description | 52 | | --- | --- | --- | --- | 53 | | projectId | string | | The Project Id. | 54 | | url | string | "/" | Base url of space-cloud server. | 55 | 56 | **Example** 57 | ```js 58 | import { API } from 'space-api'; 59 | 60 | const api = new API('my-project', 'http://localhost:4122'); 61 | ``` 62 | 63 | 64 | ### api.setToken(token) 65 | Initialse the Client Api with the JWT token 66 | 67 | **Kind**: instance method of [Api](#Api) 68 | 69 | | Param | Type | Description | 70 | | --- | --- | --- | 71 | | token | string | The signed JWT token received from the server on successful authentication. | 72 | 73 | 74 | 75 | ### api.setProjectId(projectId) 76 | Set the new Project Id 77 | 78 | **Kind**: instance method of [Api](#Api) 79 | 80 | | Param | Type | Description | 81 | | --- | --- | --- | 82 | | projectId | string | The Project Id. | 83 | 84 | 85 | 86 | ### ~~api.Mongo() ⇒ [DB](#external_DB)~~ 87 | ***Deprecated*** 88 | 89 | Returns a Mongo DB client instance 90 | 91 | **Kind**: instance method of [Api](#Api) 92 | **Returns**: [DB](#external_DB) - DB client instance 93 | 94 | 95 | ### ~~api.Postgres() ⇒ [DB](#external_DB)~~ 96 | ***Deprecated*** 97 | 98 | Returns a Postgres DB client instance 99 | 100 | **Kind**: instance method of [Api](#Api) 101 | **Returns**: [DB](#external_DB) - DB client instance 102 | 103 | 104 | ### ~~api.MySQL() ⇒ [DB](#external_DB)~~ 105 | ***Deprecated*** 106 | 107 | Returns a MySQL Db client instance 108 | 109 | **Kind**: instance method of [Api](#Api) 110 | **Returns**: [DB](#external_DB) - Db client instance 111 | 112 | 113 | ### api.DB(db) ⇒ [DB](#external_DB) 114 | Returns a DB client instance 115 | 116 | **Kind**: instance method of [Api](#Api) 117 | **Returns**: [DB](#external_DB) - DB client instance 118 | 119 | | Param | Type | Description | 120 | | --- | --- | --- | 121 | | db | string | The alias name of the database | 122 | 123 | 124 | 125 | ### api.call(service, endpoint, params, [timeout]) ⇒ Promise 126 | Calls an endpoint from the remote service 127 | 128 | **Kind**: instance method of [Api](#Api) 129 | **Returns**: Promise - Returns a promise 130 | 131 | | Param | Type | Default | Description | 132 | | --- | --- | --- | --- | 133 | | service | string | | The name of service | 134 | | endpoint | string | | The name of endpoint to be called | 135 | | params | Object | | The params to be sent to the remote service | 136 | | [timeout] | string | 5000 | Timeout in milliseconds | 137 | 138 | **Example** 139 | ```js 140 | api.call('my_service', 'my_endpoint', { msg: 'Remote services are awesome!' }, 1000) 141 | .then(res => { 142 | if (res.status === 200) { 143 | // res.data contains the response given by the function 144 | console.log('Response:', res.data); 145 | return; 146 | } 147 | }).catch(ex => { 148 | // Exception occured while processing request 149 | }); 150 | ``` 151 | 152 | 153 | ### api.queueEvent(type, payload) ⇒ Promise 154 | Queues an event 155 | 156 | **Kind**: instance method of [Api](#Api) 157 | **Returns**: Promise - Returns a promise 158 | 159 | | Param | Type | Description | 160 | | --- | --- | --- | 161 | | type | string | The type of event | 162 | | payload | string | Event payload | 163 | 164 | **Example** 165 | ```js 166 | const res = await api.queueEvent("event-type", {"foo": "bar"}) 167 | .delay(0) 168 | .date(new Date("2025-06-25")) 169 | .options({}) 170 | .apply() 171 | ``` 172 | 173 | 174 | ### api.FileStore() ⇒ [FileStore](#external_FileStore) 175 | Returns a FileStore client instance 176 | 177 | **Kind**: instance method of [Api](#Api) 178 | **Returns**: [FileStore](#external_FileStore) - FileStore client instance 179 | 180 | 181 | ## QueueEvent 182 | Class representing the Queue Event Interface. 183 | 184 | **Kind**: global class 185 | 186 | * [QueueEvent](#QueueEvent) 187 | * [new QueueEvent(appId, url, options, eventType, eventPayload)](#new_QueueEvent_new) 188 | * [.synchronous()](#QueueEvent+synchronous) 189 | * [.delay(delay)](#QueueEvent+delay) 190 | * [.date(date)](#QueueEvent+date) 191 | * [.apply()](#QueueEvent+apply) ⇒ Promise 192 | 193 | 194 | 195 | ### new QueueEvent(appId, url, options, eventType, eventPayload) 196 | Create an instance of the DB Get Interface. 197 | 198 | 199 | | Param | Type | 200 | | --- | --- | 201 | | appId | string | 202 | | url | string | 203 | | options | Object | 204 | | eventType | string | 205 | | eventPayload | Object | 206 | 207 | **Example** 208 | ```js 209 | import { API, cond, or, and } from 'space-api'; 210 | 211 | const api = new API('my-project', 'http://localhost:4122'); 212 | const res = await api.queueEvent("event-type", {"foo": "bar"}) 213 | .delay(0) 214 | .date(new Date("2025-06-25")) 215 | .options({}) 216 | .apply() 217 | ``` 218 | 219 | 220 | ### queueEvent.synchronous() 221 | Queues the event synchronously 222 | 223 | **Kind**: instance method of [QueueEvent](#QueueEvent) 224 | 225 | 226 | ### queueEvent.delay(delay) 227 | Seconds to delay the webhook trigger by 228 | 229 | **Kind**: instance method of [QueueEvent](#QueueEvent) 230 | 231 | | Param | Type | Description | 232 | | --- | --- | --- | 233 | | delay | Number | delay in seconds. | 234 | 235 | 236 | 237 | ### queueEvent.date(date) 238 | Date to schedule the event for 239 | 240 | **Kind**: instance method of [QueueEvent](#QueueEvent) 241 | 242 | | Param | Type | Description | 243 | | --- | --- | --- | 244 | | date | Object | Date object. | 245 | 246 | 247 | 248 | ### queueEvent.apply() ⇒ Promise 249 | Makes the query to queue the event. In case of synchronous events it will resolve to the response object from the webhook. 250 | 251 | **Kind**: instance method of [QueueEvent](#QueueEvent) 252 | **Returns**: Promise - Returns a promise containing response from server. 253 | 254 | 255 | ## DB 256 | The DB Client Interface. 257 | 258 | **Kind**: global external 259 | **See**: [https://github.com/spaceuptech/space-api-js/wiki/DB](https://github.com/spaceuptech/space-api-js/wiki/DB) 260 | 261 | 262 | ## Realtime 263 | The Realtime Client Interface. 264 | 265 | **Kind**: global external 266 | **See**: [https://github.com/spaceuptech/space-api-js/wiki/Realtime](https://github.com/spaceuptech/space-api-js/wiki/Realtime) 267 | 268 | 269 | ## FileStore 270 | The FileStore Client Interface. 271 | 272 | **Kind**: global external 273 | **See**: [https://github.com/spaceuptech/space-api-js/wiki/FileStore](https://github.com/spaceuptech/space-api-js/wiki/FileStore) 274 | -------------------------------------------------------------------------------- /lib/db/get.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | const utils = require('../utils'), 3 | fetchAsync = utils.fetchAsync, 4 | and = utils.and; 5 | 6 | /** 7 | * Class representing the DB Get Interface. 8 | * @example 9 | * import { API, cond, or, and } from 'space-api'; 10 | * 11 | * const api = new API('my-project', 'http://localhost:4122'); 12 | * const db = api.DB("mongo"); 13 | * 14 | * db.get('posts').where(and(cond('title', '==', 'Title1'))).all().then(res => { 15 | * if (res.status === 200) { 16 | * // res.data contains the documents returned by the database 17 | * console.log('Response:', res.data); 18 | * return; 19 | * } 20 | * }).catch(ex => { 21 | * // Exception occured while processing request 22 | * }); 23 | */ 24 | class Get { 25 | /** 26 | * Create an instance of the DB Get Interface. 27 | * @param {string} appId 28 | * @param {string} collection 29 | * @param {string} url 30 | * @param {Object} options 31 | * @param {string} db 32 | * @param {string} op 33 | */ 34 | constructor(appId, collection, url, options, db, op) { 35 | this.appId = appId; 36 | this.collection = collection; 37 | this.url = url; 38 | this.db = db; 39 | this.options = Object.assign({}, options, { method: 'POST' }); 40 | this.params = { find: {}, options: {}, aggregate: {} }; 41 | this.params.op = op; 42 | this.type = 'read'; 43 | 44 | } 45 | 46 | /** 47 | * Prepares the find query 48 | * @param {...Object} conditions - The condition logic. 49 | */ 50 | where(...conditions) { 51 | this.params.find = db.generateFind(and(...conditions)); 52 | return this; 53 | } 54 | whereRaw(condition) { 55 | this.params.find = condition; 56 | return this; 57 | } 58 | /** 59 | * Sets the fields to be selected 60 | * @param {Object} select - The select object. 61 | * @example 62 | * // Given query will only select author and title fields 63 | * const select = { author: 1, title: 1 } 64 | * db.get('posts').select(select).all().then(res => ...) 65 | */ 66 | select(select) { 67 | this.params.options.select = select; 68 | return this; 69 | } 70 | 71 | /** 72 | * Sets the fields to groupBy 73 | * @param {...string} array - The fields to perform group by operation. 74 | * @example 75 | * db.get('expenditures').groupBy('category', 'userId').aggregateCount('amount').apply().then(res => ...) 76 | */ 77 | groupBy(...array) { 78 | this.params.group = array; 79 | return this; 80 | } 81 | 82 | /** 83 | * Sets the fields to perform count aggregation on 84 | * @param {...string} array - The fields to perform count aggregation on. 85 | * @example 86 | * db.get('expenditures').groupBy('category').aggregateCount('amount').apply().then(res => ...) 87 | */ 88 | aggregateCount(...array) { 89 | this.params.aggregate.count = array; 90 | return this; 91 | } 92 | 93 | /** 94 | * Sets the fields to perform max aggregation on 95 | * @param {...string} array - The fields to perform max aggregation on. 96 | * @example 97 | * db.get('expenditures').groupBy('category').aggregateMax('amount').apply().then(res => ...) 98 | */ 99 | aggregateMax(...array) { 100 | this.params.aggregate.max = array; 101 | return this; 102 | } 103 | 104 | /** 105 | * Sets the fields to perform min aggregation on 106 | * @param {...string} array - The fields to perform min aggregation on. 107 | * @example 108 | * db.get('expenditures').groupBy('category').aggregateMin('amount').apply().then(res => ...) 109 | */ 110 | aggregateMin(...array) { 111 | this.params.aggregate.min = array; 112 | return this; 113 | } 114 | 115 | /** 116 | * Sets the fields to perform average aggregation on 117 | * @param {...string} array - The fields to perform average aggregation on. 118 | * @example 119 | * db.get('expenditures').groupBy('category').aggregateAverage('amount').apply().then(res => ...) 120 | */ 121 | aggregateAverage(...array) { 122 | this.params.aggregate.avg = array; 123 | return this; 124 | } 125 | 126 | /** 127 | * Sets the fields to perform sum aggregation on 128 | * @param {...string} array - The fields to perform sum aggregation on. 129 | * @example 130 | * db.get('expenditures').groupBy('category').aggregateSum('amount').apply().then(res => ...) 131 | */ 132 | aggregateSum(...array) { 133 | this.params.aggregate.sum = array; 134 | return this; 135 | } 136 | 137 | /** 138 | * Sets the fields to sort result by. 139 | * @param {...string} array - The fields to sort result by. 140 | * @example 141 | * // Given query will sort results first by age (asc) then by age (desc) 142 | * db.get('posts').sort('title', '-age').all().then(res => ...) 143 | */ 144 | sort(...array) { 145 | this.params.options.sort = array 146 | return this; 147 | } 148 | 149 | /** 150 | * Sets the number of documents to skip in the array. 151 | * @param {number} num - The number of documents to skip. 152 | * @example 153 | * // Given query will skip the first 10 documents 154 | * db.get('posts').skip(10).all().then(res => ...) 155 | */ 156 | skip(num) { 157 | this.params.options.skip = num; 158 | return this; 159 | } 160 | 161 | /** 162 | * Sets the limit on number of documents returned by the query. 163 | * @param {number} num - The limit on number of documents. 164 | * @example 165 | * // Given query will limit the result to 10 documents 166 | * db.get('posts').limit(10).all().then(res => ...) 167 | */ 168 | limit(num) { 169 | this.params.options.limit = num; 170 | return this; 171 | } 172 | 173 | /** 174 | * Makes the query to return a single document as an object. If no documents are returned, the status code is 400. 175 | * @returns {Promise} Returns a promise containing response from server. 176 | * @example 177 | * db.get('posts').one().then(res => ...) 178 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 179 | */ 180 | one() { 181 | this.params.op = 'one'; 182 | this.options.body = JSON.stringify(this.params); 183 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/read`); 184 | return fetchAsync(url, this.options); 185 | } 186 | 187 | /** 188 | * Makes the query to return multiple documents as an array. It is possible for an empty array to be returned. 189 | * @returns {Promise} Returns a promise containing response from server. 190 | * @example 191 | * db.get('posts').all().then(res => ...) 192 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 193 | */ 194 | all() { 195 | this.params.op = 'all'; 196 | 197 | // Set a default limit if offset is specified and limit is not specified. 198 | if (this.params.options.skip !== undefined && this.params.options.limit === undefined) { 199 | this.params.options.limit = 20; 200 | } 201 | 202 | this.options.body = JSON.stringify(this.params); 203 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/read`); 204 | return fetchAsync(url, this.options); 205 | } 206 | 207 | /** 208 | * Makes the query to return multiple documents as an array. It is possible for an empty array to be returned. 209 | * @returns {Promise} Returns a promise containing response from server. 210 | * @example 211 | * db.get('posts').apply().then(res => ...) 212 | */ 213 | apply() { 214 | 215 | // Set a default limit if offset is specified and limit is not specified. 216 | if (this.params.options.skip !== undefined && this.params.options.limit === undefined) { 217 | this.params.options.limit = 20; 218 | } 219 | 220 | this.options.body = JSON.stringify(this.params); 221 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/read`); 222 | return fetchAsync(url, this.options); 223 | } 224 | 225 | /** 226 | * Makes the query to return an array of all the distinct values for the given field. It is possible for an empty array to be returned. 227 | * @returns {Promise} Returns a promise containing response from server. 228 | * @example 229 | * db.get('posts').distinct('category').then(res => ...) 230 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 231 | */ 232 | distinct(key) { 233 | this.params.op = 'distinct'; 234 | this.params.options.distinct = key; 235 | this.options.body = JSON.stringify(this.params); 236 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/read`); 237 | return fetchAsync(url, this.options); 238 | } 239 | 240 | /** 241 | * Makes the query to return the count of total number of documents that were queried. 242 | * @example 243 | * // Given query counts the total number of posts in the 'posts' collection 244 | * db.get('posts').count().then(res => ...) 245 | * @deprecated Since version 0.4.3. Will be deleted in version 1.0.0. Use apply instead. 246 | */ 247 | count() { 248 | this.params.op = 'count'; 249 | this.options.body = JSON.stringify(this.params); 250 | let url = db.dbURL(this.url, this.db, this.appId, 'crud', `${this.collection}/read`); 251 | return fetchAsync(url, this.options); 252 | } 253 | } 254 | 255 | module.exports = Get; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /lib/db/db.js: -------------------------------------------------------------------------------- 1 | const Batch = require('./batch'); 2 | const Get = require('./get'); 3 | const Insert = require('./insert'); 4 | const Update = require('./update'); 5 | const Delete = require('./delete'); 6 | const PreparedQuery = require('./prepared-query'); 7 | const Aggregate = require('./aggregate'); 8 | const myURL = require('url') 9 | if (typeof window === 'undefined') { 10 | global.URL = myURL.URL; 11 | } 12 | 13 | const fetchAsync = require('../utils').fetchAsync; 14 | 15 | /** 16 | * The LiveQuery Interface. 17 | * @external LiveQuery 18 | * @see {@link https://github.com/spaceuptech/space-api-js/wiki/Realtime} 19 | */ 20 | 21 | /** 22 | * @typedef User 23 | * @property {string} _id - The user's unique id. 24 | * @property {string} email The user's email id. 25 | * @property {string} name The user's name. 26 | * @property {string} role The user's role. 27 | */ 28 | 29 | /** 30 | * @typedef AuthResponse 31 | * @property {number} status - The http status code of response. 32 | * @property {Object} data The response payload. 33 | * @property {string} data.token The signed token generated for the user. 34 | * @property {User} data.user Information of the user. 35 | */ 36 | 37 | /** 38 | * Class representing the DB Client Interface. 39 | * @example 40 | * import { API } from 'space-api'; 41 | * 42 | * const api = new API('my-project', 'http://localhost:4122'); 43 | * const db = api.DB("mongo"); 44 | */ 45 | class DB { 46 | /** 47 | * Create an instance of the DB Client Interface. 48 | * @param {string} appId 49 | * @param {string} url 50 | * @param {Object} options 51 | * @param {string} db 52 | * @param {Object} realTime 53 | */ 54 | constructor(appId, url, options, db, realtime) { 55 | this.appId = appId 56 | this.url = url 57 | this.options = options 58 | this.db = db 59 | this.realtime = realtime 60 | } 61 | 62 | beginBatch() { 63 | return new Batch(this.appId, this.url, this.options, this.db); 64 | } 65 | 66 | /** 67 | * Returns a DB Get Object 68 | * @param {string} collection - The collection to query documents. 69 | * @returns {Get} DB Get Object 70 | */ 71 | get(collection) { 72 | return new Get(this.appId, collection, this.url, this.options, this.db, 'all'); 73 | } 74 | 75 | /** 76 | * Returns a DB Get Object to get one particular object 77 | * @param {string} collection - The collection to query documents. 78 | * @returns {Get} DB Get Object 79 | */ 80 | getOne(collection) { 81 | return new Get(this.appId, collection, this.url, this.options, this.db, 'one'); 82 | } 83 | 84 | count(collection) { 85 | return new Get(this.appId, collection, this.url, this.options, this.db, 'count'); 86 | } 87 | 88 | distinct(collection) { 89 | return new Get(this.appId, collection, this.url, this.options, this.db, 'distinct'); 90 | } 91 | 92 | /** 93 | * Returns a DB Insert Object 94 | * @param {string} collection - The collection to insert documents. 95 | * @returns {Insert} DB Insert Object 96 | */ 97 | insert(collection) { 98 | return new Insert(this.appId, collection, this.url, this.options, this.db); 99 | } 100 | 101 | /** 102 | * Returns a DB Update Object to update all matching documents 103 | * @param {string} collection - The collection to update documents. 104 | * @returns {Update} DB Update Object 105 | */ 106 | update(collection) { 107 | return new Update(this.appId, collection, this.url, this.options, this.db, 'all'); 108 | } 109 | 110 | /** 111 | * Returns a DB Update Object to update a particular document 112 | * @param {string} collection - The collection to update document. 113 | * @returns {Update} DB Update Object 114 | */ 115 | updateOne(collection) { 116 | return new Update(this.appId, collection, this.url, this.options, this.db, 'one'); 117 | } 118 | 119 | 120 | /** 121 | * Returns a DB Update Object to upsert a particular document 122 | * @param {string} collection - The collection to update document. 123 | * @returns {Update} DB Update Object 124 | */ 125 | upsert(collection) { 126 | return new Update(this.appId, collection, this.url, this.options, this.db, 'upsert'); 127 | } 128 | 129 | /** 130 | * Returns a DB Delete Object 131 | * @param {string} collection - The collection to delete documents in. 132 | * @returns {Delete} DB Insert Object 133 | */ 134 | delete(collection) { 135 | return new Delete(this.appId, collection, this.url, this.options, this.db, 'all'); 136 | } 137 | 138 | /** 139 | * Returns a DB Delete Object to delete a particular document 140 | * @param {string} collection - The collection to delete document. 141 | * @returns {Delete} DB Delete Object 142 | */ 143 | deleteOne(collection) { 144 | return new Delete(this.appId, collection, this.url, this.options, this.db, 'one'); 145 | } 146 | 147 | /** 148 | * Returns a Prepared Query Object to execute a database prepared query 149 | * @param {string} id - The id of prepared query. 150 | * @returns {PreparedQuery} Prepared Query Object 151 | */ 152 | preparedQuery(id) { 153 | return new PreparedQuery(this.appId, id, this.url, this.options, this.db); 154 | } 155 | 156 | /** 157 | * Returns a DB Aggregate Object 158 | * @param {string} collection - The collection to aggregate documents in. 159 | * @returns {Delete} DB Insert Object 160 | */ 161 | aggr(collection) { 162 | return new Aggregate(this.appId, collection, this.url, this.options, this.db, 'all'); 163 | } 164 | 165 | aggrOne(collection) { 166 | return new Aggregate(this.appId, collection, this.url, this.options, this.db, 'one'); 167 | } 168 | 169 | /** 170 | * Returns a LiveQuery Object 171 | * @param {string} collection - The collection to query documents. 172 | * @returns {external:LiveQuery} LiveQuery Object 173 | * @example 174 | * const onSnapshot = (snapshot, type, changedDoc) => { 175 | * console.log(type, snapshot, changedDoc) 176 | * } 177 | * 178 | * const onError = (err) => { 179 | * console.log('Operation failed:', err) 180 | * } 181 | * 182 | * let subscription = db.liveQuery('posts').where({}).subscribe(onSnapshot, onError) 183 | * 184 | * // Unsubscribe to clean up 185 | * subscription.unsubscribe() 186 | */ 187 | 188 | liveQuery(collection) { 189 | return this.realtime.liveQuery(this.db, collection) 190 | } 191 | 192 | /** 193 | * Fetches the user profile 194 | * @param {string} id - The unique user id 195 | * @returns {Promise} Returns a promise containing response from server 196 | * @example 197 | * db.profile(id).then(res => { 198 | * if (res.status === 200) { 199 | * // res.data.user contains user details 200 | * console.log('Response:', res.data.user); 201 | * return; 202 | * } 203 | * // Request failed 204 | * }).catch(ex => { 205 | * // Exception occured while processing request 206 | * }); 207 | */ 208 | 209 | profile(id) { 210 | let url = dbURL(this.url, this.db, this.appId, 'auth', `profile/${id}`); 211 | return fetchAsync(url, Object.assign({}, this.options, { method: 'GET' })); 212 | } 213 | 214 | /** 215 | * Updates the user profile 216 | * @param {string} id - The unique user id 217 | * @param {string} email - The new email id 218 | * @param {string} name - The new name 219 | * @param {string} pass - The new password 220 | * @returns {Promise} Return a promise containing response from server 221 | * @example 222 | * db.editProfile(id, email, name, pass).then(res => { 223 | * if (res.status === 200) { 224 | * // User account has been updates successfully 225 | * return; 226 | * } 227 | * // Request failed 228 | * }).catch(ex => { 229 | * // Exception occured while processing request 230 | * }); 231 | */ 232 | editProfile(id, email, name, pass) { 233 | const body = JSON.stringify({ update: { $set: { email, name, pass } } }); 234 | let url = dbURL(this.url, this.db, this.appId, 'auth', `edit_profile/${id}`); 235 | return fetchAsync(url, Object.assign({}, this.options, { method: 'PUT', body: body })); 236 | } 237 | 238 | /** 239 | * Fetches all the user profiles 240 | * @returns {Promise} Returns a promise containing response from server 241 | * @example 242 | * db.profiles().then(res => { 243 | * if (res.status === 200) { 244 | * // res.data.users contains user details 245 | * console.log('Response:', res.data.users); 246 | * return; 247 | * } 248 | * // Request failed 249 | * }).catch(ex => { 250 | * // Exception occured while processing request 251 | * }); 252 | */ 253 | profiles() { 254 | let url = dbURL(this.url, this.db, this.appId, 'auth', 'profiles'); 255 | return fetchAsync(url, Object.assign({}, this.options, { method: 'GET' })); 256 | } 257 | /** 258 | * Sends a sign in query to the server 259 | * @param {string} email - The user's email id. 260 | * @param {string} pass - The user's password. 261 | * @returns {Promise} Returns a promise containing response from server 262 | * @example 263 | * db.signIn('demo@example.com', '1234').then(res => { 264 | * if (res.status === 200) { 265 | * // Set the token id to enable crud operations 266 | * api.setToken(res.data.token) 267 | * 268 | * // res.data contains request payload 269 | * console.log('Response:', res.data); 270 | * return; 271 | * } 272 | * // Request failed 273 | * }).catch(ex => { 274 | * // Exception occured while processing request 275 | * }); 276 | */ 277 | signIn(email, pass) { 278 | let url = dbURL(this.url, this.db, this.appId, 'auth', 'email/signin'); 279 | let body = JSON.stringify({ email, pass }); 280 | return fetchAsync(url, Object.assign({}, this.options, { method: 'POST', body: body })); 281 | } 282 | 283 | /** 284 | * Sends a sign up query to the server 285 | * @param {string} email - The user's email id. 286 | * @param {string} name - The user's name. 287 | * @param {string} pass - The user's password. 288 | * @param {string} role - The user's role. 289 | * @returns {Promise} Returns a promise containing response from server 290 | * @example 291 | * db.signUp('demo@example.com', 'UserName', '1234', 'default').then(res => { 292 | * if (res.status === 200) { 293 | * // Set the token id to enable crud operations 294 | * api.setToken(res.data.token) 295 | * 296 | * // res.data contains request payload 297 | * console.log('Response:', res.data); 298 | * return; 299 | * } 300 | * // Request failed 301 | * }).catch(ex => { 302 | * // Exception occured while processing request 303 | * }); 304 | */ 305 | signUp(email, name, pass, role) { 306 | let url = dbURL(this.url, this.db, this.appId, 'auth', 'email/signup'); 307 | let body = JSON.stringify({ email, pass, name, role }); 308 | return fetchAsync(url, Object.assign({}, this.options, { method: 'POST', body: body })); 309 | } 310 | } 311 | 312 | const generateFind = (condition) => { 313 | switch (condition.type) { 314 | case 'and': 315 | let d = {} 316 | condition.clauses.forEach(clause => { 317 | let generated = generateFind(clause) 318 | if (clause.type == 'cond') { 319 | if (d[clause.f1] == undefined) { 320 | d = Object.assign(d, generated) 321 | } 322 | else { 323 | d[clause.f1] = Object.assign(d[clause.f1], generated[clause.f1]) 324 | } 325 | } 326 | else { 327 | d = Object.assign(d, generated) 328 | } 329 | }); 330 | return d 331 | 332 | case 'or': 333 | var newConds = condition.clauses.map(cond => generateFind(cond)) 334 | return { '$or': newConds } 335 | 336 | case 'cond': 337 | switch (condition.op) { 338 | case '==': 339 | return { 340 | [condition.f1]: condition.f2 341 | }; 342 | case '>': 343 | return { 344 | [condition.f1]: { $gt: condition.f2 } 345 | }; 346 | case '<': 347 | return { 348 | [condition.f1]: { $lt: condition.f2 } 349 | }; 350 | case '>=': 351 | return { 352 | [condition.f1]: { $gte: condition.f2 } 353 | }; 354 | case '<=': 355 | return { 356 | [condition.f1]: { $lte: condition.f2 } 357 | }; 358 | case '!=': 359 | return { 360 | [condition.f1]: { $ne: condition.f2 } 361 | }; 362 | case 'in': 363 | return { 364 | [condition.f1]: { $in: condition.f2 } 365 | }; 366 | case 'notIn': 367 | return { 368 | [condition.f1]: { $nin: condition.f2 } 369 | }; 370 | case 'regex': 371 | return { 372 | [condition.f1]: { $regex: condition.f2 } 373 | }; 374 | case 'contains': 375 | return { 376 | [condition.f1]: { $contains: condition.f2 } 377 | }; 378 | } 379 | 380 | } 381 | } 382 | 383 | const dbURL = (prefix, db, appId, moduleName, suffix) => { 384 | return `${prefix}v1/api/${appId}/${moduleName}/${db}/${suffix}`; 385 | }; 386 | 387 | exports.dbURL = dbURL 388 | exports.generateFind = generateFind 389 | exports.DB = DB 390 | -------------------------------------------------------------------------------- /test/integration/index.js: -------------------------------------------------------------------------------- 1 | var { API, cond, and, or } = require("../../index") 2 | 3 | // Create the following tables for SQL: 4 | // CREATE TABLE users (id VARCHAR(50), account VARCHAR(50), email VARCHAR(100), name VARCHAR(100), pass VARCHAR(50), role VARCHAR(50)); 5 | // CREATE TABLE posts (title VARCHAR(50), content VARCHAR(200), author VARCHAR(200)); 6 | 7 | 8 | // Databases 9 | const MONGO = "mongo" 10 | const SQL_POSTGRES = "postgres" 11 | const SQL_MYSQL = "mysql" 12 | 13 | const isToday = (someDate) => { 14 | const today = new Date() 15 | return someDate.getDate() == today.getDate() && 16 | someDate.getMonth() == today.getMonth() && 17 | someDate.getFullYear() == today.getFullYear() 18 | } 19 | 20 | const formatError = (section, desc, params) => { 21 | return { section, desc, params } 22 | } 23 | 24 | startTest(MONGO, "test", "http://localhost:4122") 25 | startTest(SQL_MYSQL, "test", "http://localhost:4122") 26 | startTest(SQL_POSTGRES, "test", "http://localhost:4122") 27 | 28 | async function cleanUp(db) { 29 | let res = await db.delete("posts").all() 30 | if (res.status !== 200) { 31 | throw formatError("CleanUp", "", res) 32 | } 33 | 34 | res = await db.get("posts").all() 35 | if (res.status !== 200 || !(res.data.result === null || res.data.result.length === 0)) { 36 | throw formatError("CleanUp", "Posts not cleaned", res) 37 | } 38 | 39 | res = await db.delete("users").all() 40 | if (res.status !== 200) { 41 | throw formatError("CleanUp", "Users not cleaned", res) 42 | } 43 | 44 | res = await db.get("users").all() 45 | if (res.status !== 200 || !(res.data.result === null || res.data.result.length === 0)) { 46 | throw formatError("CleanUp", "", res) 47 | } 48 | } 49 | 50 | 51 | 52 | async function startTest(dbType, projectId, url) { 53 | let db 54 | try { 55 | let api = new API(projectId, url) 56 | 57 | switch (dbType) { 58 | case MONGO: 59 | db = api.Mongo() 60 | break 61 | case SQL_MYSQL: 62 | db = api.MySQL() 63 | break 64 | case SQL_POSTGRES: 65 | db = api.Postgres() 66 | break 67 | default: 68 | throw formatError("Initialization", "Unsupported Database") 69 | } 70 | 71 | 72 | 73 | // Clean any data before starting tests 74 | await cleanUp(db) 75 | let res = {} 76 | /******************** Signup ********************/ 77 | const user = { email: "user1@gmail.com", name: "User 1", pass: "123", role: "user" } 78 | res = await db.signUp(user.email, user.name, user.pass, user.role) 79 | if (res.status !== 200) { 80 | throw formatError("SignUp", "Status not matching", res) 81 | } 82 | if (res.data.user.email !== user.email || res.data.user.name !== user.name || res.data.user.role !== user.role) { 83 | throw formatError("SignUp", "User not matching", res) 84 | } 85 | api.setToken(res.data.token) 86 | 87 | /******************** Signin ********************/ 88 | res = await db.signIn(user.email, user.pass) 89 | if (res.status !== 200) { 90 | throw formatError("SignIn", "Status not matching", res) 91 | } 92 | if (res.data.user.email !== user.email || res.data.user.name !== user.name || res.data.user.role !== user.role) { 93 | throw formatError("SignIn", "User not matching", res) 94 | } 95 | api.setToken(res.data.token) 96 | 97 | let userId = "" 98 | switch (dbType) { 99 | case MONGO: 100 | userId = res.data.user._id 101 | break 102 | default: 103 | userId = res.data.user.id 104 | } 105 | 106 | /******************** Insert One ********************/ 107 | const postDate = new Date(2015, 1, 25, 10, 10, 10, 0) 108 | let posts = [ 109 | { title: "Post 1", content: "This is a good post", author: user.email }, 110 | { title: "Post 2", content: "This is a better post", author: user.email }, 111 | { title: "Post 3", content: "This is a good post", author: user.email } 112 | ] 113 | 114 | switch (dbType) { 115 | case MONGO: 116 | posts.push({ 117 | title: "Post 4", content: "This is a good post", author: user.email, 118 | highestScore: 400, lowestScore: 100, score: 10, 119 | views: 0, viewedBy: [], abc: "123", "xyz": "456", 120 | updated: postDate, lastUpdated: postDate.getTime() 121 | }) 122 | break 123 | default: 124 | posts.push({ title: "Post 4", content: "This is a good post", author: user.email }) 125 | } 126 | 127 | res = await db.insert("posts").one(posts[0]) 128 | if (res.status !== 200) { 129 | throw formatError("Insert One", "Status not matching", res) 130 | } 131 | 132 | /******************** Insert All ********************/ 133 | res = await db.insert("posts").all(posts.slice(1)) 134 | if (res.status !== 200) { 135 | throw formatError("Insert All", "Status not matching", res) 136 | } 137 | 138 | /******************** Get One ********************/ 139 | res = await db.get("posts").where(cond("title", "==", "Post 1")).one() 140 | if (res.status !== 200) { 141 | throw formatError("Get One", "Status not matching", res) 142 | } 143 | 144 | if (res.data.result.title !== "Post 1" || res.data.result.author !== user.email || res.data.result.content != "This is a good post") { 145 | throw formatError("Get One", "Result not matching", res) 146 | } 147 | 148 | /******************** Get All ********************/ 149 | res = await db.get("posts").all() 150 | if (res.status !== 200) { 151 | throw formatError("Get All", "Status not matching", res) 152 | } 153 | 154 | if (res.data.result.length !== 4 || res.data.result[0].title !== "Post 1" || res.data.result[0].author !== user.email || res.data.result[0].content != "This is a good post") { 155 | throw formatError("Get All", "Result not matching", res) 156 | } 157 | 158 | /******************** Get Count ********************/ 159 | // count is not supported as of now in sql 160 | switch (dbType) { 161 | case MONGO: 162 | res = await db.get("posts").count() 163 | if (res.status !== 200) { 164 | throw formatError("Get Count", "Status not matching", res) 165 | } 166 | 167 | if (res.data.result !== 4) { 168 | throw formatError("Get Count", "Result not matching", res) 169 | } 170 | break 171 | } 172 | 173 | /******************** Get All (Options - select, skip, limit, sort) ********************/ 174 | res = await db.get("posts").where(cond("title", "!=", "Post 3")).select({ title: 1, content: 1 }).skip(2).limit(1).sort("-title").all() 175 | if (res.status !== 200) { 176 | throw formatError("Get All Options", "Status not matching", res) 177 | } 178 | 179 | if (res.data.result.length !== 1 || res.data.result[0].title !== "Post 1" || res.data.result[0].author !== undefined || res.data.result[0].content != "This is a good post") { 180 | throw formatError("Get All Options", "Result not matching", res) 181 | } 182 | 183 | /******************** Get One (And Clause) ********************/ 184 | res = await db.get("posts").where(and(cond("author", "==", user.email), cond("title", "==", "Post 3"))).one() 185 | if (res.status !== 200) { 186 | throw formatError("Get One (AND Clause)", "Status not matching", res) 187 | } 188 | 189 | if (res.data.result.title !== "Post 3" || res.data.result.author !== user.email || res.data.result.content != "This is a good post") { 190 | throw formatError("Get One (AND Clause)", "Result not matching", res) 191 | } 192 | 193 | /******************** Get All (Or Clause) ********************/ 194 | res = await db.get("posts").where(or(cond("title", "==", "Post 2"), cond("title", "==", "Post 3"))).all() 195 | if (res.status !== 200) { 196 | throw formatError("Get All (OR Clause)", "Status not matching", res) 197 | } 198 | 199 | if (res.data.result.length !== 2 || res.data.result[0].title !== "Post 2" || res.data.result[1].title !== "Post 3") { 200 | throw formatError("Get All (OR Clause)", "Result not matching", res) 201 | } 202 | 203 | /******************** Get Distinct ********************/ 204 | // Distinct is not supported as of now in sql 205 | switch (dbType) { 206 | case MONGO: 207 | res = await db.get("posts").distinct("content") 208 | if (res.status !== 200) { 209 | throw formatError("Get Distinct", "Status not matching", res) 210 | } 211 | 212 | if (res.data.result.length !== 2 || res.data.result[0] !== "This is a good post" || res.data.result[1] !== "This is a better post") { 213 | throw formatError("Get Distinct", "Result not matching", res) 214 | } 215 | break 216 | } 217 | 218 | /******************** Update One ********************/ 219 | switch (dbType) { 220 | case MONGO: 221 | res = await db.update("posts").where(cond("title", "==", "Post 4")) 222 | .set({ content: "This is a better post" }) 223 | .push({ viewedBy: "User 2" }) 224 | .inc({ views: 10 }) 225 | .mul({ score: 5 }) 226 | .max({ highestScore: 500 }) 227 | .min({ lowestScore: 40 }) 228 | .rename({ xyz: "zyx" }) 229 | .remove("abc") 230 | .currentDate("updated") 231 | .currentTimestamp("lastUpdated") 232 | .one() 233 | 234 | if (res.status !== 200) { 235 | throw formatError("Update One", "Status not matching", res) 236 | } 237 | 238 | res = await db.get("posts").where(cond("title", "==", "Post 4")).one() 239 | if (res.status !== 200) { 240 | throw formatError("Update One", "Cannot fetch updated doc", res) 241 | } 242 | 243 | if (res.data.result.content !== "This is a better post") { 244 | throw formatError("Update One", "Set did not worked", res) 245 | } 246 | 247 | if (res.data.result.views !== 10) { 248 | throw formatError("Update One", "Inc did not worked", res) 249 | } 250 | 251 | if (res.data.result.score !== 50) { 252 | throw formatError("Update One", "Mul did not worked", res) 253 | } 254 | 255 | if (res.data.result.highestScore !== 500) { 256 | throw formatError("Update One", "Max did not worked", res) 257 | } 258 | 259 | if (res.data.result.lowestScore !== 40) { 260 | throw formatError("Update One", "Min did not worked", res) 261 | } 262 | 263 | if (res.data.result.abc !== undefined) { 264 | throw formatError("Update One", "Remove did not worked", res) 265 | } 266 | 267 | if (res.data.result.zyx !== "456") { 268 | throw formatError("Update One", "Rename did not worked", res) 269 | } 270 | 271 | if (res.data.result.viewedBy.length !== 1 || res.data.result.viewedBy[0] !== "User 2") { 272 | throw formatError("Update One", "Push did not worked", res) 273 | } 274 | 275 | if (!isToday(new Date(res.data.result.updated))) { 276 | throw formatError("Update One", "Current Date did not worked", res) 277 | } 278 | 279 | if (new Date().getTime() <= postDate.getTime()) { 280 | throw formatError("Update One", "Current Timestamp did not worked", res) 281 | } 282 | break 283 | } 284 | 285 | /******************** Update All ********************/ 286 | res = await db.update("posts").where(or(cond("title", "==", "Post 1"), cond("title", "==", "Post 3"))).set({ content: "This is an awesome post" }).all() 287 | 288 | if (res.status !== 200) { 289 | throw formatError("Update All", "Status not matching", res) 290 | } 291 | 292 | res = await db.get("posts").where(cond("content", "==", "This is an awesome post")).all() 293 | if (res.status !== 200) { 294 | throw formatError("Update All", "Result not matching", res) 295 | } 296 | 297 | if (res.data.result.length !== 2 || res.data.result[0].title !== "Post 1" || res.data.result[1].title !== "Post 3" || res.data.result[0].content !== "This is an awesome post") { 298 | throw formatError("Update All", "Result not matching", res) 299 | } 300 | 301 | /******************** Upsert ********************/ 302 | // Upsert is not supported in sql 303 | switch (dbType) { 304 | case MONGO: 305 | res = await db.update("posts").where(and(cond("title", "==", "Post 5"), cond("author", "==", user.email))).set({ content: "This is an awesome post" }).upsert() 306 | if (res.status !== 200) { 307 | throw formatError("Upsert", "Status not matching", res) 308 | } 309 | res = await db.get("posts").where(cond("title", "==", "Post 5")).all() 310 | if (res.status !== 200) { 311 | throw formatError("Upsert", "Cannot fetch upserted doc", res) 312 | } 313 | 314 | if (res.data.result.length !== 1 || res.data.result[0].title !== "Post 5" || res.data.result[0].content !== "This is an awesome post") { 315 | throw formatError("Upsert", "Result not matching", res) 316 | } 317 | break 318 | } 319 | 320 | /******************** Delete One ********************/ 321 | switch (dbType) { 322 | case MONGO: 323 | res = await db.delete("posts").where(cond("title", "==", "Post 2")).one() 324 | if (res.status !== 200) { 325 | throw formatError("Delete One", "Status not matching", res) 326 | } 327 | 328 | res = await db.get("posts").where(cond("title", "==", "Post 2")).one() 329 | if (res.status !== 500) { 330 | throw formatError("Delete One", "Wrong status while fetching deleted doc", res) 331 | } 332 | break 333 | } 334 | 335 | /******************** Delete All ********************/ 336 | res = await db.delete("posts").where(cond("title", "!=", "Post 1")).all() 337 | if (res.status !== 200) { 338 | throw formatError("Delete All", "Status not matching", res) 339 | } 340 | 341 | res = await db.get("posts").all() 342 | if (res.status !== 200) { 343 | throw formatError("Delete All", "Cannot fetch remaining docs", res) 344 | } 345 | 346 | if (res.data.result.length !== 1 || res.data.result[0].title !== "Post 1") { 347 | throw formatError("Delete All", "Result not matching", res) 348 | } 349 | 350 | /******************** Fetch Profile ********************/ 351 | res = await db.profile(userId) 352 | if (res.status !== 200) { 353 | throw formatError("Get Profile", "Status not matching", res) 354 | } 355 | 356 | if (res.data.user.name !== "User 1" || res.data.user.email !== "user1@gmail.com" || res.data.user.role !== "user") { 357 | throw formatError("Get Profile", "Profile not matching", res) 358 | } 359 | 360 | /******************** Fetch Profiles ********************/ 361 | res = await db.profiles() 362 | if (res.status !== 200) { 363 | throw formatError("Get Profiles", "Status not matching", res) 364 | } 365 | 366 | if (res.data.users.length !== 1 || res.data.users[0].name !== "User 1" || res.data.users[0].email !== "user1@gmail.com" || res.data.users[0].role !== "user") { 367 | throw formatError("Get Profiles", "Profile not matching", res) 368 | } 369 | 370 | /******************** FaaS ********************/ 371 | res = await api.call('echo-engine', 'echo', 'Function as a Service is awesome!', 1000) 372 | if (res.status !== 200) { 373 | throw formatError("FaaS", "Status not matching", res) 374 | } 375 | 376 | if (res.data.result.message !== 'Function as a Service is awesome!') { 377 | throw formatError("FaaS", "Result not matching", res) 378 | } 379 | 380 | // Clean up data 381 | await cleanUp(db) 382 | 383 | 384 | console.log("\x1b[32m%s\x1b[0m", "All tests for " + dbType + " have passed!!"); 385 | }catch (errorObj) { 386 | console.log(errorObj) 387 | console.log("\x1b[31m%s\x1b[0m", "Error in " + dbType.toUpperCase() + " (" + errorObj.section + ")") 388 | console.log("\x1b[31m%s\x1b[0m", "Description - " + errorObj.desc) 389 | console.log("\x1b[31m%s\x1b[0m", "Response - " + JSON.stringify(errorObj.params)) 390 | 391 | // Clean up data 392 | try { 393 | await cleanUp(db) 394 | } catch (e) {} 395 | } 396 | } 397 | 398 | //startTest().catch(err => console.log(err)) 399 | -------------------------------------------------------------------------------- /lib/db/README.md: -------------------------------------------------------------------------------- 1 | ## Classes 2 | 3 |
4 |
DB
5 |

Class representing the DB Client Interface.

6 |
7 |
Insert
8 |

Class representing the DB Insert Interface.

9 |
10 |
Get
11 |

Class representing the DB Get Interface.

12 |
13 |
Update
14 |

Class representing the DB Update Interface.

15 |
16 |
Delete
17 |

Class representing the DB Delete Interface.

18 |
19 |
Aggregate
20 |

Class representing the DB Aggregate Interface.

21 |
22 |
23 | 24 | ## Typedefs 25 | 26 |
27 |
User
28 |
29 |
AuthResponse
30 |
31 |
32 | 33 | ## External 34 | 35 |
36 |
LiveQuery
37 |

The LiveQuery Interface.

38 |
39 |
40 | 41 | 42 | 43 | ## DB 44 | Class representing the DB Client Interface. 45 | 46 | **Kind**: global class 47 | 48 | * [DB](#DB) 49 | * [new DB(appId, url, options, db, realTime)](#new_DB_new) 50 | * [.get(collection)](#DB+get) ⇒ [Get](#Get) 51 | * [.getOne(collection)](#DB+getOne) ⇒ [Get](#Get) 52 | * [.insert(collection)](#DB+insert) ⇒ [Insert](#Insert) 53 | * [.update(collection)](#DB+update) ⇒ [Update](#Update) 54 | * [.updateOne(collection)](#DB+updateOne) ⇒ [Update](#Update) 55 | * [.upsert(collection)](#DB+upsert) ⇒ [Update](#Update) 56 | * [.delete(collection)](#DB+delete) ⇒ [Delete](#Delete) 57 | * [.deleteOne(collection)](#DB+deleteOne) ⇒ [Delete](#Delete) 58 | * [.preparedQuery(id)](#DB+preparedQuery) ⇒ PreparedQuery 59 | * [.aggr(collection)](#DB+aggr) ⇒ [Delete](#Delete) 60 | * [.liveQuery(collection)](#DB+liveQuery) ⇒ [LiveQuery](#external_LiveQuery) 61 | * [.profile(id)](#DB+profile) ⇒ Promise 62 | * [.editProfile(id, email, name, pass)](#DB+editProfile) ⇒ Promise 63 | * [.profiles()](#DB+profiles) ⇒ Promise 64 | * [.signIn(email, pass)](#DB+signIn) ⇒ [Promise.<AuthResponse>](#AuthResponse) 65 | * [.signUp(email, name, pass, role)](#DB+signUp) ⇒ [Promise.<AuthResponse>](#AuthResponse) 66 | 67 | 68 | 69 | ### new DB(appId, url, options, db, realTime) 70 | Create an instance of the DB Client Interface. 71 | 72 | 73 | | Param | Type | 74 | | --- | --- | 75 | | appId | string | 76 | | url | string | 77 | | options | Object | 78 | | db | string | 79 | | realTime | Object | 80 | 81 | **Example** 82 | ```js 83 | import { API } from 'space-api'; 84 | 85 | const api = new API('my-project', 'http://localhost:4122'); 86 | const db = api.DB("mongo"); 87 | ``` 88 | 89 | 90 | ### dB.get(collection) ⇒ [Get](#Get) 91 | Returns a DB Get Object 92 | 93 | **Kind**: instance method of [DB](#DB) 94 | **Returns**: [Get](#Get) - DB Get Object 95 | 96 | | Param | Type | Description | 97 | | --- | --- | --- | 98 | | collection | string | The collection to query documents. | 99 | 100 | 101 | 102 | ### dB.getOne(collection) ⇒ [Get](#Get) 103 | Returns a DB Get Object to get one particular object 104 | 105 | **Kind**: instance method of [DB](#DB) 106 | **Returns**: [Get](#Get) - DB Get Object 107 | 108 | | Param | Type | Description | 109 | | --- | --- | --- | 110 | | collection | string | The collection to query documents. | 111 | 112 | 113 | 114 | ### dB.insert(collection) ⇒ [Insert](#Insert) 115 | Returns a DB Insert Object 116 | 117 | **Kind**: instance method of [DB](#DB) 118 | **Returns**: [Insert](#Insert) - DB Insert Object 119 | 120 | | Param | Type | Description | 121 | | --- | --- | --- | 122 | | collection | string | The collection to insert documents. | 123 | 124 | 125 | 126 | ### dB.update(collection) ⇒ [Update](#Update) 127 | Returns a DB Update Object to update all matching documents 128 | 129 | **Kind**: instance method of [DB](#DB) 130 | **Returns**: [Update](#Update) - DB Update Object 131 | 132 | | Param | Type | Description | 133 | | --- | --- | --- | 134 | | collection | string | The collection to update documents. | 135 | 136 | 137 | 138 | ### dB.updateOne(collection) ⇒ [Update](#Update) 139 | Returns a DB Update Object to update a particular document 140 | 141 | **Kind**: instance method of [DB](#DB) 142 | **Returns**: [Update](#Update) - DB Update Object 143 | 144 | | Param | Type | Description | 145 | | --- | --- | --- | 146 | | collection | string | The collection to update document. | 147 | 148 | 149 | 150 | ### dB.upsert(collection) ⇒ [Update](#Update) 151 | Returns a DB Update Object to upsert a particular document 152 | 153 | **Kind**: instance method of [DB](#DB) 154 | **Returns**: [Update](#Update) - DB Update Object 155 | 156 | | Param | Type | Description | 157 | | --- | --- | --- | 158 | | collection | string | The collection to update document. | 159 | 160 | 161 | 162 | ### dB.delete(collection) ⇒ [Delete](#Delete) 163 | Returns a DB Delete Object 164 | 165 | **Kind**: instance method of [DB](#DB) 166 | **Returns**: [Delete](#Delete) - DB Insert Object 167 | 168 | | Param | Type | Description | 169 | | --- | --- | --- | 170 | | collection | string | The collection to delete documents in. | 171 | 172 | 173 | 174 | ### dB.deleteOne(collection) ⇒ [Delete](#Delete) 175 | Returns a DB Delete Object to delete a particular document 176 | 177 | **Kind**: instance method of [DB](#DB) 178 | **Returns**: [Delete](#Delete) - DB Delete Object 179 | 180 | | Param | Type | Description | 181 | | --- | --- | --- | 182 | | collection | string | The collection to delete document. | 183 | 184 | 185 | 186 | ### dB.preparedQuery(id) ⇒ PreparedQuery 187 | Returns a Prepared Query Object to execute a database prepared query 188 | 189 | **Kind**: instance method of [DB](#DB) 190 | **Returns**: PreparedQuery - Prepared Query Object 191 | 192 | | Param | Type | Description | 193 | | --- | --- | --- | 194 | | id | string | The id of prepared query. | 195 | 196 | 197 | 198 | ### dB.aggr(collection) ⇒ [Delete](#Delete) 199 | Returns a DB Aggregate Object 200 | 201 | **Kind**: instance method of [DB](#DB) 202 | **Returns**: [Delete](#Delete) - DB Insert Object 203 | 204 | | Param | Type | Description | 205 | | --- | --- | --- | 206 | | collection | string | The collection to aggregate documents in. | 207 | 208 | 209 | 210 | ### dB.liveQuery(collection) ⇒ [LiveQuery](#external_LiveQuery) 211 | Returns a LiveQuery Object 212 | 213 | **Kind**: instance method of [DB](#DB) 214 | **Returns**: [LiveQuery](#external_LiveQuery) - LiveQuery Object 215 | 216 | | Param | Type | Description | 217 | | --- | --- | --- | 218 | | collection | string | The collection to query documents. | 219 | 220 | **Example** 221 | ```js 222 | const onSnapshot = (snapshot, type, changedDoc) => { 223 | console.log(type, snapshot, changedDoc) 224 | } 225 | 226 | const onError = (err) => { 227 | console.log('Operation failed:', err) 228 | } 229 | 230 | let subscription = db.liveQuery('posts').where({}).subscribe(onSnapshot, onError) 231 | 232 | // Unsubscribe to clean up 233 | subscription.unsubscribe() 234 | ``` 235 | 236 | 237 | ### dB.profile(id) ⇒ Promise 238 | Fetches the user profile 239 | 240 | **Kind**: instance method of [DB](#DB) 241 | **Returns**: Promise - Returns a promise containing response from server 242 | 243 | | Param | Type | Description | 244 | | --- | --- | --- | 245 | | id | string | The unique user id | 246 | 247 | **Example** 248 | ```js 249 | db.profile(id).then(res => { 250 | if (res.status === 200) { 251 | // res.data.user contains user details 252 | console.log('Response:', res.data.user); 253 | return; 254 | } 255 | // Request failed 256 | }).catch(ex => { 257 | // Exception occured while processing request 258 | }); 259 | ``` 260 | 261 | 262 | ### dB.editProfile(id, email, name, pass) ⇒ Promise 263 | Updates the user profile 264 | 265 | **Kind**: instance method of [DB](#DB) 266 | **Returns**: Promise - Return a promise containing response from server 267 | 268 | | Param | Type | Description | 269 | | --- | --- | --- | 270 | | id | string | The unique user id | 271 | | email | string | The new email id | 272 | | name | string | The new name | 273 | | pass | string | The new password | 274 | 275 | **Example** 276 | ```js 277 | db.editProfile(id, email, name, pass).then(res => { 278 | if (res.status === 200) { 279 | // User account has been updates successfully 280 | return; 281 | } 282 | // Request failed 283 | }).catch(ex => { 284 | // Exception occured while processing request 285 | }); 286 | ``` 287 | 288 | 289 | ### dB.profiles() ⇒ Promise 290 | Fetches all the user profiles 291 | 292 | **Kind**: instance method of [DB](#DB) 293 | **Returns**: Promise - Returns a promise containing response from server 294 | **Example** 295 | ```js 296 | db.profiles().then(res => { 297 | if (res.status === 200) { 298 | // res.data.users contains user details 299 | console.log('Response:', res.data.users); 300 | return; 301 | } 302 | // Request failed 303 | }).catch(ex => { 304 | // Exception occured while processing request 305 | }); 306 | ``` 307 | 308 | 309 | ### dB.signIn(email, pass) ⇒ [Promise.<AuthResponse>](#AuthResponse) 310 | Sends a sign in query to the server 311 | 312 | **Kind**: instance method of [DB](#DB) 313 | **Returns**: [Promise.<AuthResponse>](#AuthResponse) - Returns a promise containing response from server 314 | 315 | | Param | Type | Description | 316 | | --- | --- | --- | 317 | | email | string | The user's email id. | 318 | | pass | string | The user's password. | 319 | 320 | **Example** 321 | ```js 322 | db.signIn('demo@example.com', '1234').then(res => { 323 | if (res.status === 200) { 324 | // Set the token id to enable crud operations 325 | api.setToken(res.data.token) 326 | 327 | // res.data contains request payload 328 | console.log('Response:', res.data); 329 | return; 330 | } 331 | // Request failed 332 | }).catch(ex => { 333 | // Exception occured while processing request 334 | }); 335 | ``` 336 | 337 | 338 | ### dB.signUp(email, name, pass, role) ⇒ [Promise.<AuthResponse>](#AuthResponse) 339 | Sends a sign up query to the server 340 | 341 | **Kind**: instance method of [DB](#DB) 342 | **Returns**: [Promise.<AuthResponse>](#AuthResponse) - Returns a promise containing response from server 343 | 344 | | Param | Type | Description | 345 | | --- | --- | --- | 346 | | email | string | The user's email id. | 347 | | name | string | The user's name. | 348 | | pass | string | The user's password. | 349 | | role | string | The user's role. | 350 | 351 | **Example** 352 | ```js 353 | db.signUp('demo@example.com', 'UserName', '1234', 'default').then(res => { 354 | if (res.status === 200) { 355 | // Set the token id to enable crud operations 356 | api.setToken(res.data.token) 357 | 358 | // res.data contains request payload 359 | console.log('Response:', res.data); 360 | return; 361 | } 362 | // Request failed 363 | }).catch(ex => { 364 | // Exception occured while processing request 365 | }); 366 | ``` 367 | 368 | 369 | ## Insert 370 | Class representing the DB Insert Interface. 371 | 372 | **Kind**: global class 373 | 374 | * [Insert](#Insert) 375 | * [new Insert(appId, collection, url, options, db)](#new_Insert_new) 376 | * ~~[.one(doc)](#Insert+one) ⇒ Promise~~ 377 | * ~~[.all(docs)](#Insert+all) ⇒ Promise~~ 378 | * [.apply(docs)](#Insert+apply) ⇒ Promise 379 | * [.docs(docs)](#Insert+docs) 380 | * [.doc(doc)](#Insert+doc) 381 | 382 | 383 | 384 | ### new Insert(appId, collection, url, options, db) 385 | Create an instance of the DB Insert Interface. 386 | 387 | 388 | | Param | Type | 389 | | --- | --- | 390 | | appId | string | 391 | | collection | string | 392 | | url | string | 393 | | options | Object | 394 | | db | string | 395 | 396 | **Example** 397 | ```js 398 | import { API, cond, or, and } from 'space-api'; 399 | 400 | const api = new API('my-project', 'http://localhost:4122'); 401 | const db = api.DB("mongo"); 402 | 403 | const doc = { author: 'John', title: 'Title1', _id: 1 }; 404 | db.insert('posts').one(doc).then(res => { 405 | if (res.status === 200) { 406 | // Document was inserted successfully 407 | return; 408 | } 409 | }).catch(ex => { 410 | // Exception occured while processing request 411 | }); 412 | ``` 413 | 414 | 415 | ### ~~insert.one(doc) ⇒ Promise~~ 416 | ***Deprecated*** 417 | 418 | Makes the query to insert a single document. 419 | 420 | **Kind**: instance method of [Insert](#Insert) 421 | **Returns**: Promise - Returns a promise containing response from server. 422 | 423 | | Param | Type | Description | 424 | | --- | --- | --- | 425 | | doc | Object | The document to be inserted. | 426 | 427 | **Example** 428 | ```js 429 | const doc = { author: 'John', title: 'Title1', _id: 1 }; 430 | db.insert('posts').one(doc).then(res => ...) 431 | ``` 432 | 433 | 434 | ### ~~insert.all(docs) ⇒ Promise~~ 435 | ***Deprecated*** 436 | 437 | Makes the query to insert multiple documents. 438 | 439 | **Kind**: instance method of [Insert](#Insert) 440 | **Returns**: Promise - Returns a promise containing response from server. 441 | 442 | | Param | Type | Description | 443 | | --- | --- | --- | 444 | | docs | Array.<Object> | The documents to be inserted. | 445 | 446 | **Example** 447 | ```js 448 | const docs = [{ author: 'John', title: 'Title1', _id: 1 }]; 449 | db.insert('posts').all(docs).then(res => ...) 450 | ``` 451 | 452 | 453 | ### insert.apply(docs) ⇒ Promise 454 | Makes the query to insert multiple documents. 455 | 456 | **Kind**: instance method of [Insert](#Insert) 457 | **Returns**: Promise - Returns a promise containing response from server. 458 | 459 | | Param | Type | Description | 460 | | --- | --- | --- | 461 | | docs | Array.<Object> | The documents to be inserted. | 462 | 463 | **Example** 464 | ```js 465 | const docs = [{ author: 'John', title: 'Title1', _id: 1 }]; 466 | db.insert('posts').docs(docs).apply().then(res => ...) 467 | ``` 468 | 469 | 470 | ### insert.docs(docs) 471 | Accepts the documents to be inserted. 472 | 473 | **Kind**: instance method of [Insert](#Insert) 474 | 475 | | Param | Type | Description | 476 | | --- | --- | --- | 477 | | docs | Array.<Object> | The documents to be inserted. | 478 | 479 | 480 | 481 | ### insert.doc(doc) 482 | Accepts the document to be inserted. 483 | 484 | **Kind**: instance method of [Insert](#Insert) 485 | 486 | | Param | Type | Description | 487 | | --- | --- | --- | 488 | | doc | Array.<Object> | The document to be inserted. | 489 | 490 | 491 | 492 | ## Get 493 | Class representing the DB Get Interface. 494 | 495 | **Kind**: global class 496 | 497 | * [Get](#Get) 498 | * [new Get(appId, collection, url, options, db, op)](#new_Get_new) 499 | * [.where(...conditions)](#Get+where) 500 | * [.select(select)](#Get+select) 501 | * [.groupBy(...array)](#Get+groupBy) 502 | * [.aggregateCount(...array)](#Get+aggregateCount) 503 | * [.aggregateMax(...array)](#Get+aggregateMax) 504 | * [.aggregateMin(...array)](#Get+aggregateMin) 505 | * [.aggregateAverage(...array)](#Get+aggregateAverage) 506 | * [.aggregateSum(...array)](#Get+aggregateSum) 507 | * [.sort(...array)](#Get+sort) 508 | * [.skip(num)](#Get+skip) 509 | * [.limit(num)](#Get+limit) 510 | * ~~[.one()](#Get+one) ⇒ Promise~~ 511 | * ~~[.all()](#Get+all) ⇒ Promise~~ 512 | * [.apply()](#Get+apply) ⇒ Promise 513 | * ~~[.distinct()](#Get+distinct) ⇒ Promise~~ 514 | * ~~[.count()](#Get+count)~~ 515 | 516 | 517 | 518 | ### new Get(appId, collection, url, options, db, op) 519 | Create an instance of the DB Get Interface. 520 | 521 | 522 | | Param | Type | 523 | | --- | --- | 524 | | appId | string | 525 | | collection | string | 526 | | url | string | 527 | | options | Object | 528 | | db | string | 529 | | op | string | 530 | 531 | **Example** 532 | ```js 533 | import { API, cond, or, and } from 'space-api'; 534 | 535 | const api = new API('my-project', 'http://localhost:4122'); 536 | const db = api.DB("mongo"); 537 | 538 | db.get('posts').where(and(cond('title', '==', 'Title1'))).all().then(res => { 539 | if (res.status === 200) { 540 | // res.data contains the documents returned by the database 541 | console.log('Response:', res.data); 542 | return; 543 | } 544 | }).catch(ex => { 545 | // Exception occured while processing request 546 | }); 547 | ``` 548 | 549 | 550 | ### get.where(...conditions) 551 | Prepares the find query 552 | 553 | **Kind**: instance method of [Get](#Get) 554 | 555 | | Param | Type | Description | 556 | | --- | --- | --- | 557 | | ...conditions | Object | The condition logic. | 558 | 559 | 560 | 561 | ### get.select(select) 562 | Sets the fields to be selected 563 | 564 | **Kind**: instance method of [Get](#Get) 565 | 566 | | Param | Type | Description | 567 | | --- | --- | --- | 568 | | select | Object | The select object. | 569 | 570 | **Example** 571 | ```js 572 | // Given query will only select author and title fields 573 | const select = { author: 1, title: 1 } 574 | db.get('posts').select(select).all().then(res => ...) 575 | ``` 576 | 577 | 578 | ### get.groupBy(...array) 579 | Sets the fields to groupBy 580 | 581 | **Kind**: instance method of [Get](#Get) 582 | 583 | | Param | Type | Description | 584 | | --- | --- | --- | 585 | | ...array | string | The fields to perform group by operation. | 586 | 587 | **Example** 588 | ```js 589 | db.get('expenditures').groupBy('category', 'userId').aggregateCount('amount').apply().then(res => ...) 590 | ``` 591 | 592 | 593 | ### get.aggregateCount(...array) 594 | Sets the fields to perform count aggregation on 595 | 596 | **Kind**: instance method of [Get](#Get) 597 | 598 | | Param | Type | Description | 599 | | --- | --- | --- | 600 | | ...array | string | The fields to perform count aggregation on. | 601 | 602 | **Example** 603 | ```js 604 | db.get('expenditures').groupBy('category').aggregateCount('amount').apply().then(res => ...) 605 | ``` 606 | 607 | 608 | ### get.aggregateMax(...array) 609 | Sets the fields to perform max aggregation on 610 | 611 | **Kind**: instance method of [Get](#Get) 612 | 613 | | Param | Type | Description | 614 | | --- | --- | --- | 615 | | ...array | string | The fields to perform max aggregation on. | 616 | 617 | **Example** 618 | ```js 619 | db.get('expenditures').groupBy('category').aggregateMax('amount').apply().then(res => ...) 620 | ``` 621 | 622 | 623 | ### get.aggregateMin(...array) 624 | Sets the fields to perform min aggregation on 625 | 626 | **Kind**: instance method of [Get](#Get) 627 | 628 | | Param | Type | Description | 629 | | --- | --- | --- | 630 | | ...array | string | The fields to perform min aggregation on. | 631 | 632 | **Example** 633 | ```js 634 | db.get('expenditures').groupBy('category').aggregateMin('amount').apply().then(res => ...) 635 | ``` 636 | 637 | 638 | ### get.aggregateAverage(...array) 639 | Sets the fields to perform average aggregation on 640 | 641 | **Kind**: instance method of [Get](#Get) 642 | 643 | | Param | Type | Description | 644 | | --- | --- | --- | 645 | | ...array | string | The fields to perform average aggregation on. | 646 | 647 | **Example** 648 | ```js 649 | db.get('expenditures').groupBy('category').aggregateAverage('amount').apply().then(res => ...) 650 | ``` 651 | 652 | 653 | ### get.aggregateSum(...array) 654 | Sets the fields to perform sum aggregation on 655 | 656 | **Kind**: instance method of [Get](#Get) 657 | 658 | | Param | Type | Description | 659 | | --- | --- | --- | 660 | | ...array | string | The fields to perform sum aggregation on. | 661 | 662 | **Example** 663 | ```js 664 | db.get('expenditures').groupBy('category').aggregateSum('amount').apply().then(res => ...) 665 | ``` 666 | 667 | 668 | ### get.sort(...array) 669 | Sets the fields to sort result by. 670 | 671 | **Kind**: instance method of [Get](#Get) 672 | 673 | | Param | Type | Description | 674 | | --- | --- | --- | 675 | | ...array | string | The fields to sort result by. | 676 | 677 | **Example** 678 | ```js 679 | // Given query will sort results first by age (asc) then by age (desc) 680 | db.get('posts').sort('title', '-age').all().then(res => ...) 681 | ``` 682 | 683 | 684 | ### get.skip(num) 685 | Sets the number of documents to skip in the array. 686 | 687 | **Kind**: instance method of [Get](#Get) 688 | 689 | | Param | Type | Description | 690 | | --- | --- | --- | 691 | | num | number | The number of documents to skip. | 692 | 693 | **Example** 694 | ```js 695 | // Given query will skip the first 10 documents 696 | db.get('posts').skip(10).all().then(res => ...) 697 | ``` 698 | 699 | 700 | ### get.limit(num) 701 | Sets the limit on number of documents returned by the query. 702 | 703 | **Kind**: instance method of [Get](#Get) 704 | 705 | | Param | Type | Description | 706 | | --- | --- | --- | 707 | | num | number | The limit on number of documents. | 708 | 709 | **Example** 710 | ```js 711 | // Given query will limit the result to 10 documents 712 | db.get('posts').limit(10).all().then(res => ...) 713 | ``` 714 | 715 | 716 | ### ~~get.one() ⇒ Promise~~ 717 | ***Deprecated*** 718 | 719 | Makes the query to return a single document as an object. If no documents are returned, the status code is 400. 720 | 721 | **Kind**: instance method of [Get](#Get) 722 | **Returns**: Promise - Returns a promise containing response from server. 723 | **Example** 724 | ```js 725 | db.get('posts').one().then(res => ...) 726 | ``` 727 | 728 | 729 | ### ~~get.all() ⇒ Promise~~ 730 | ***Deprecated*** 731 | 732 | Makes the query to return multiple documents as an array. It is possible for an empty array to be returned. 733 | 734 | **Kind**: instance method of [Get](#Get) 735 | **Returns**: Promise - Returns a promise containing response from server. 736 | **Example** 737 | ```js 738 | db.get('posts').all().then(res => ...) 739 | ``` 740 | 741 | 742 | ### get.apply() ⇒ Promise 743 | Makes the query to return multiple documents as an array. It is possible for an empty array to be returned. 744 | 745 | **Kind**: instance method of [Get](#Get) 746 | **Returns**: Promise - Returns a promise containing response from server. 747 | **Example** 748 | ```js 749 | db.get('posts').apply().then(res => ...) 750 | ``` 751 | 752 | 753 | ### ~~get.distinct() ⇒ Promise~~ 754 | ***Deprecated*** 755 | 756 | Makes the query to return an array of all the distinct values for the given field. It is possible for an empty array to be returned. 757 | 758 | **Kind**: instance method of [Get](#Get) 759 | **Returns**: Promise - Returns a promise containing response from server. 760 | **Example** 761 | ```js 762 | db.get('posts').distinct('category').then(res => ...) 763 | ``` 764 | 765 | 766 | ### ~~get.count()~~ 767 | ***Deprecated*** 768 | 769 | Makes the query to return the count of total number of documents that were queried. 770 | 771 | **Kind**: instance method of [Get](#Get) 772 | **Example** 773 | ```js 774 | // Given query counts the total number of posts in the 'posts' collection 775 | db.get('posts').count().then(res => ...) 776 | ``` 777 | 778 | 779 | ## Update 780 | Class representing the DB Update Interface. 781 | 782 | **Kind**: global class 783 | 784 | * [Update](#Update) 785 | * [new Update(appId, collection, url, options, db, op)](#new_Update_new) 786 | * [.where(...conditions)](#Update+where) 787 | * [.set(obj)](#Update+set) 788 | * [.push(obj)](#Update+push) 789 | * [.remove(...fields)](#Update+remove) 790 | * [.rename(obj)](#Update+rename) 791 | * [.inc(obj)](#Update+inc) 792 | * [.mul(obj)](#Update+mul) 793 | * [.max(obj)](#Update+max) 794 | * [.min(obj)](#Update+min) 795 | * [.currentTimestamp(...values)](#Update+currentTimestamp) 796 | * [.currentDate(...values)](#Update+currentDate) 797 | * ~~[.one()](#Update+one) ⇒ Promise~~ 798 | * ~~[.all()](#Update+all) ⇒ Promise~~ 799 | * [.apply()](#Update+apply) ⇒ Promise 800 | * [.upsert()](#Update+upsert) ⇒ Promise 801 | 802 | 803 | 804 | ### new Update(appId, collection, url, options, db, op) 805 | Create an instance of the DB Update Interface. 806 | 807 | 808 | | Param | Type | 809 | | --- | --- | 810 | | appId | string | 811 | | collection | string | 812 | | url | string | 813 | | options | Object | 814 | | db | string | 815 | | op | string | 816 | 817 | **Example** 818 | ```js 819 | import { API, cond, or, and } from 'space-api'; 820 | 821 | const api = new API('my-project', 'http://localhost:4122'); 822 | const db = api.DB("mongo"); 823 | 824 | db.update('posts').where(and(cond('title', '==', 'Title1'))).set({ title: 'Title2' }).all().then(res => { 825 | if (res.status === 200) { 826 | // The documents were updated successfully 827 | return; 828 | } 829 | }).catch(ex => { 830 | // Exception occured while processing request 831 | }); 832 | ``` 833 | 834 | 835 | ### update.where(...conditions) 836 | Prepares the find query 837 | 838 | **Kind**: instance method of [Update](#Update) 839 | 840 | | Param | Type | Description | 841 | | --- | --- | --- | 842 | | ...conditions | Object | The condition logic. | 843 | 844 | 845 | 846 | ### update.set(obj) 847 | Sets the value of a field in a document. 848 | 849 | **Kind**: instance method of [Update](#Update) 850 | 851 | | Param | Type | Description | 852 | | --- | --- | --- | 853 | | obj | Object | The Object containing fields to set. | 854 | 855 | **Example** 856 | ```js 857 | db.update('posts').set({ author: 'Drake' }).all().then(res => ...) 858 | ``` 859 | 860 | 861 | ### update.push(obj) 862 | Adds an item to an array. 863 | 864 | **Kind**: instance method of [Update](#Update) 865 | 866 | | Param | Type | Description | 867 | | --- | --- | --- | 868 | | obj | Object | The Object containing fields to set. | 869 | 870 | **Example** 871 | ```js 872 | db.update('posts').push({ author: 'Drake' }).all().then(res => ...) 873 | ``` 874 | 875 | 876 | ### update.remove(...fields) 877 | Removes the specified field from a document. 878 | 879 | **Kind**: instance method of [Update](#Update) 880 | 881 | | Param | Type | Description | 882 | | --- | --- | --- | 883 | | ...fields | string | The fields to remove. | 884 | 885 | **Example** 886 | ```js 887 | db.update('posts').remove('age', 'likes').all().then(res => ...) 888 | ``` 889 | 890 | 891 | ### update.rename(obj) 892 | Renames the specified field. 893 | 894 | **Kind**: instance method of [Update](#Update) 895 | 896 | | Param | Type | Description | 897 | | --- | --- | --- | 898 | | obj | Object | The object containing fields to rename. | 899 | 900 | **Example** 901 | ```js 902 | db.update('posts').rename({ mobile: 'contact' }).all().then(res => ...) 903 | ``` 904 | 905 | 906 | ### update.inc(obj) 907 | Increments the value of the field by the specified amount. 908 | 909 | **Kind**: instance method of [Update](#Update) 910 | 911 | | Param | Type | Description | 912 | | --- | --- | --- | 913 | | obj | Object | The object containing fields to increment along with the value. | 914 | 915 | **Example** 916 | ```js 917 | // The value of added with 1 918 | db.update('posts').inc({ views: 1 }).all().then(res => ...) 919 | ``` 920 | 921 | 922 | ### update.mul(obj) 923 | Multiplies the value of the field by the specified amount. 924 | 925 | **Kind**: instance method of [Update](#Update) 926 | 927 | | Param | Type | Description | 928 | | --- | --- | --- | 929 | | obj | Object | The object containing fields to multiply along with the value. | 930 | 931 | **Example** 932 | ```js 933 | // The value of amount will be multiplied by 4 934 | db.update('posts').mul({ amount: 4 }).all().then(res => ...) 935 | ``` 936 | 937 | 938 | ### update.max(obj) 939 | Only updates the field if the specified value is greater than the existing field value. 940 | 941 | **Kind**: instance method of [Update](#Update) 942 | 943 | | Param | Type | Description | 944 | | --- | --- | --- | 945 | | obj | Object | The object containing fields to set. | 946 | 947 | **Example** 948 | ```js 949 | db.update('posts').max({ highScore: 1200 }).all().then(res => ...) 950 | ``` 951 | 952 | 953 | ### update.min(obj) 954 | Only updates the field if the specified value is lesser than the existing field value. 955 | 956 | **Kind**: instance method of [Update](#Update) 957 | 958 | | Param | Type | Description | 959 | | --- | --- | --- | 960 | | obj | Object | The object containing fields to set. | 961 | 962 | **Example** 963 | ```js 964 | db.update('posts').min({ lowestScore: 300 }).all().then(res => ...) 965 | ``` 966 | 967 | 968 | ### update.currentTimestamp(...values) 969 | Sets the value of a field to current timestamp. 970 | 971 | **Kind**: instance method of [Update](#Update) 972 | 973 | | Param | Type | Description | 974 | | --- | --- | --- | 975 | | ...values | string | The fields to set. | 976 | 977 | **Example** 978 | ```js 979 | db.update('posts').currentTimestamp('lastModified').all().then(res => ...) 980 | ``` 981 | 982 | 983 | ### update.currentDate(...values) 984 | Sets the value of a field to current date. 985 | 986 | **Kind**: instance method of [Update](#Update) 987 | 988 | | Param | Type | Description | 989 | | --- | --- | --- | 990 | | ...values | string | The fields to set. | 991 | 992 | **Example** 993 | ```js 994 | db.update('posts').currentDate('lastModified').all().then(res => ...) 995 | ``` 996 | 997 | 998 | ### ~~update.one() ⇒ Promise~~ 999 | ***Deprecated*** 1000 | 1001 | Makes the query to update a single document which matches first. 1002 | 1003 | **Kind**: instance method of [Update](#Update) 1004 | **Returns**: Promise - Returns a promise containing response from server 1005 | 1006 | 1007 | ### ~~update.all() ⇒ Promise~~ 1008 | ***Deprecated*** 1009 | 1010 | Makes the query to update all documents which matches. 1011 | 1012 | **Kind**: instance method of [Update](#Update) 1013 | **Returns**: Promise - Returns a promise containing response from server 1014 | 1015 | 1016 | ### update.apply() ⇒ Promise 1017 | Makes the query to update all documents which matches. 1018 | 1019 | **Kind**: instance method of [Update](#Update) 1020 | **Returns**: Promise - Returns a promise containing response from server 1021 | 1022 | 1023 | ### update.upsert() ⇒ Promise 1024 | Makes the query to update all, else insert a document. 1025 | 1026 | **Kind**: instance method of [Update](#Update) 1027 | **Returns**: Promise - Returns a promise containing response from server 1028 | 1029 | 1030 | ## Delete 1031 | Class representing the DB Delete Interface. 1032 | 1033 | **Kind**: global class 1034 | 1035 | * [Delete](#Delete) 1036 | * [new Delete(appId, collection, url, options, db, op)](#new_Delete_new) 1037 | * [.where(...conditions)](#Delete+where) 1038 | * ~~[.one()](#Delete+one) ⇒ Promise~~ 1039 | * ~~[.all()](#Delete+all) ⇒ Promise~~ 1040 | * [.apply()](#Delete+apply) ⇒ Promise 1041 | 1042 | 1043 | 1044 | ### new Delete(appId, collection, url, options, db, op) 1045 | Create an instance of the DB Delete Interface. 1046 | 1047 | 1048 | | Param | Type | 1049 | | --- | --- | 1050 | | appId | string | 1051 | | collection | string | 1052 | | url | string | 1053 | | options | Object | 1054 | | db | string | 1055 | | op | string | 1056 | 1057 | **Example** 1058 | ```js 1059 | import { API, cond, or, and } from 'space-api'; 1060 | 1061 | const api = new API('my-project', 'localhost:4122'); 1062 | const db = api.DB("mongo"); 1063 | 1064 | db.delete('posts').where(and(cond('title', '==', 'Title1'))).all().then(res => { 1065 | if (res.status === 200) { 1066 | // The documents were deleted successfully 1067 | return; 1068 | } 1069 | }).catch(ex => { 1070 | // Exception occured while processing request 1071 | }); 1072 | ``` 1073 | 1074 | 1075 | ### delete.where(...conditions) 1076 | Prepares the find query 1077 | 1078 | **Kind**: instance method of [Delete](#Delete) 1079 | 1080 | | Param | Type | Description | 1081 | | --- | --- | --- | 1082 | | ...conditions | Object | The condition logic. | 1083 | 1084 | 1085 | 1086 | ### ~~delete.one() ⇒ Promise~~ 1087 | ***Deprecated*** 1088 | 1089 | Makes the query to delete a single document which matches first. 1090 | 1091 | **Kind**: instance method of [Delete](#Delete) 1092 | **Returns**: Promise - Returns a promise containing response from server. 1093 | **Example** 1094 | ```js 1095 | db.delete('posts').one().then(res => ...) 1096 | ``` 1097 | 1098 | 1099 | ### ~~delete.all() ⇒ Promise~~ 1100 | ***Deprecated*** 1101 | 1102 | Makes the query to delete all the documents which match. 1103 | 1104 | **Kind**: instance method of [Delete](#Delete) 1105 | **Returns**: Promise - Returns a promise containing response from server. 1106 | **Example** 1107 | ```js 1108 | db.delete('posts').all().then(res => ...) 1109 | ``` 1110 | 1111 | 1112 | ### delete.apply() ⇒ Promise 1113 | Makes the query to delete all the documents which match. 1114 | 1115 | **Kind**: instance method of [Delete](#Delete) 1116 | **Returns**: Promise - Returns a promise containing response from server. 1117 | **Example** 1118 | ```js 1119 | db.delete('posts').apply().then(res => ...) 1120 | ``` 1121 | 1122 | 1123 | ## Aggregate 1124 | Class representing the DB Aggregate Interface. 1125 | 1126 | **Kind**: global class 1127 | 1128 | * [Aggregate](#Aggregate) 1129 | * [new Aggregate(appId, collection, url, options, db, op)](#new_Aggregate_new) 1130 | * [.pipe(pipeObj)](#Aggregate+pipe) 1131 | * ~~[.one()](#Aggregate+one) ⇒ Promise~~ 1132 | * ~~[.all()](#Aggregate+all) ⇒ Promise~~ 1133 | * [.apply()](#Aggregate+apply) ⇒ Promise 1134 | 1135 | 1136 | 1137 | ### new Aggregate(appId, collection, url, options, db, op) 1138 | Create an instance of the DB Aggregate Interface. 1139 | 1140 | 1141 | | Param | Type | 1142 | | --- | --- | 1143 | | appId | string | 1144 | | collection | string | 1145 | | url | string | 1146 | | options | Object | 1147 | | db | string | 1148 | | op | string | 1149 | 1150 | **Example** 1151 | ```js 1152 | import { API, cond, or, and } from 'space-api'; 1153 | 1154 | const api = new API('my-project', 'http://localhost:4122'); 1155 | const db = api.DB("mongo"); 1156 | 1157 | const pipe = [ 1158 | { $match: { status: 'A' } }, 1159 | { $group: { _id: '$cust_id', total: { $sum: '$amount' } } } 1160 | ] 1161 | 1162 | db.aggr('posts').pipe(pipe).apply().then(res => { 1163 | if (res.status === 200) { 1164 | // res.data contains the documents returned by the database 1165 | console.log('Response:', res.data); 1166 | return 1167 | } 1168 | }).catch(ex => { 1169 | // Exception occured while processing request 1170 | }); 1171 | ``` 1172 | 1173 | 1174 | ### aggregate.pipe(pipeObj) 1175 | Prepares the Pipe query 1176 | 1177 | **Kind**: instance method of [Aggregate](#Aggregate) 1178 | 1179 | | Param | Type | Description | 1180 | | --- | --- | --- | 1181 | | pipeObj | Array.<Object> | The pipeline object. | 1182 | 1183 | 1184 | 1185 | ### ~~aggregate.one() ⇒ Promise~~ 1186 | ***Deprecated*** 1187 | 1188 | Makes the query to return single object. 1189 | 1190 | **Kind**: instance method of [Aggregate](#Aggregate) 1191 | **Returns**: Promise - Returns a promise containing response from server. 1192 | **Example** 1193 | ```js 1194 | db.aggr('posts').pipe([...]).one().then(res => ...) 1195 | ``` 1196 | 1197 | 1198 | ### ~~aggregate.all() ⇒ Promise~~ 1199 | ***Deprecated*** 1200 | 1201 | Makes the query to return all objects. 1202 | 1203 | **Kind**: instance method of [Aggregate](#Aggregate) 1204 | **Returns**: Promise - Returns a promise containing response from server. 1205 | **Example** 1206 | ```js 1207 | db.aggr('posts').pipe([...]).all().then(res => ...) 1208 | ``` 1209 | 1210 | 1211 | ### aggregate.apply() ⇒ Promise 1212 | Makes the query to return all objects. 1213 | 1214 | **Kind**: instance method of [Aggregate](#Aggregate) 1215 | **Returns**: Promise - Returns a promise containing response from server. 1216 | **Example** 1217 | ```js 1218 | db.aggr('posts').pipe([...]).apply().then(res => ...) 1219 | ``` 1220 | 1221 | 1222 | ## User 1223 | **Kind**: global typedef 1224 | **Properties** 1225 | 1226 | | Name | Type | Description | 1227 | | --- | --- | --- | 1228 | | _id | string | The user's unique id. | 1229 | | email | string | The user's email id. | 1230 | | name | string | The user's name. | 1231 | | role | string | The user's role. | 1232 | 1233 | 1234 | 1235 | ## AuthResponse 1236 | **Kind**: global typedef 1237 | **Properties** 1238 | 1239 | | Name | Type | Description | 1240 | | --- | --- | --- | 1241 | | status | number | The http status code of response. | 1242 | | data | Object | The response payload. | 1243 | | data.token | string | The signed token generated for the user. | 1244 | | data.user | [User](#User) | Information of the user. | 1245 | 1246 | 1247 | 1248 | ## LiveQuery 1249 | The LiveQuery Interface. 1250 | 1251 | **Kind**: global external 1252 | **See**: [https://github.com/spaceuptech/space-api-js/wiki/Realtime](https://github.com/spaceuptech/space-api-js/wiki/Realtime) 1253 | --------------------------------------------------------------------------------