Source

client/core/db/online.js

/**
 * 
 * ## Online database wrapper for KissJS server
 * 
 * Standard REST requests are performed for basic CRUD operations, except for:
 * - find(modelId, query)
 * - findById(modelId, ids, sort, sortSyntax)
 * - updateOneDeep(modelId, recordId, update)
 * - updateBulk(operations)
 * - deleteOne(modelId, recordId, sendToTrash)
 * - deleteMany(modelId, query, sendToTrash)
 * 
 * **Remember: these custom methods are design for KissJS server, and will not behave the same if used on another REST endpoint.**
 * 
 * The **find** method provides a rich and expressive way of querying the database with a **query** parameter.
 * The query can be an object with complex filters (multi-level AND / OR) and multi-field sorting, so, we need a way to send that (eventually big) object to the server.
 * As the HTTP GET method has some limitations about the amount of data that can be sent, KissJS uses a POST instead (despite it's a READ operation).
 * 
 * The **updateOneDeep** method performs a deep update of the record and its relationships.
 * This is the main method used to perform the updates of foreign records in a NoSQL environment.
 * 
 * The **updateBulk** method offers a convenient way to perform multiple updates in multiple collections at once.
 * The operations parameter is an array of operations, where each operation contains:
 * - the target modelId
 * - the target recordId
 * - the updates to apply to the target record
 * 
 * The **deleteOne** and **deleteMany** operations have an extra "sendToTrash" parameter which is useful to implement soft-deletion.
 * 
 * Below is a table of correspondance between the API and how it is converted to HTTP requests:
 *  
 * kiss.db.online | HTTP | HTTP body
 * --- | --- | ---
 * insertOne(modelId, record) | POST /modelId | record
 * insertMany(modelId, records) | POST /modelId | records
 * updateOne(modelId, recordId, update) | PATCH /modelId/recordId | update
 * updateOneDeep(modelId, recordId, update) | PATCH /modelId/recordId | {operation: "updateOneDeep", update} => update the record and its relationships
 * updateMany(modelId, query, update) | PATCH /modelId | query+update
 * updateBulk(operations) | PATCH /data | operations
 * findOne(modelId, recordId) | GET /modelId/recordId | <none>
 * findById(modelId, ids, sort, sortSyntax) | POST /modelId | Array of ids
 * find(modelId) | GET /modelId | <none>
 * find(modelId, query) | POST /modelId | query
 * deleteOne(modelId, recordId, sendToTrash) | DELETE /modelId/recordId | {sendToTrash: true/false}
 * deleteMany(modelId, query, sendToTrash) | POST /modelId | {operation: "delete", sendToTrash: true/false}
 * 
 * @namespace kiss.db.online
 */
kiss.db.online = {
	// Database endpoint, which defaults to the server root path url
	url: "",

	/**
	 * Insert one record in a collection. See [db.insertOne](kiss.db.html#.insertOne)
	 * 
	 * @async
	 * @param {string} modelId
	 * @param {object} record - A single record
	 * @returns {object} The server response
	 */
	async insertOne(modelId, record) {
		log("kiss.db - online - insertOne - Model " + modelId, 0, record)

		const response = await kiss.ajax.request({
			url: "/" + modelId,
			method: "post",
			body: JSON.stringify(record)
		})

		return response
	},

	/**
	 * Insert many records in a collection. See [db.insertMany](kiss.db.html#.insertMany)
	 * 
	 * @async
	 * @param {string} modelId
	 * @param {object[]} records - An array of records [{...}, {...}] for bulk insert
	 * @returns {object} The server response
	 */
	async insertMany(modelId, records) {
		log("kiss.db - online - insertMany - Model " + modelId + " / " + records.length + " record(s)", 0, records)

		const response = await kiss.ajax.request({
			url: "/" + modelId,
			method: "post",
			body: JSON.stringify(records)
		})

		return response
	},

	/**
	 * Update a record in a collection. See [db.updateOne](kiss.db.html#.update)
	 * 
	 * @async
	 * @param {string} modelId
	 * @param {string} recordId
	 * @param {string} update - Update to apply to the record. Ex: {firstName: "Bob"}
	 * @returns {object} The server response
	 */
	async updateOne(modelId, recordId, update) {
		log("kiss.db - online - updateOne - Model " + modelId + " / Record " + recordId, 0, update)

		const response = await kiss.ajax.request({
			url: "/" + modelId + "/" + recordId,
			method: "patch",
			body: JSON.stringify(update)
		})

		return response
	},

	/**
	 * Update many records in a single collection. See [db.updateMany](kiss.db.html#.updateMany)
	 * 
	 * @async
	 * @param {string} modelId
	 * @param {object} query
	 * @param {object} update
	 * @returns {object} The server response
	 */
	async updateMany(modelId, query, update) {
		log("kiss.db - online - updateMany - Model " + modelId, 0, update)

		const response = await kiss.ajax.request({
			url: "/" + modelId,
			method: "patch",
			body: JSON.stringify({
				query: query,
				update: update
			})
		})

		return response
	},

	/**
	 * Update a single field of a record then propagate the triggered mutations to foreign records
	 * 
	 * IMPORTANT: this method does not broadcast to the active user because
	 * all relationships computations are done server-side
	 * 
	 * @async
	 * @param {string} modelId
	 * @param {string} recordId
	 * @param {string} [update] - If not specified, re-compute all the computed fields
	 * @returns {boolean} true if the update is successful
	 * 
	 * @example
	 * await kiss.db.updateOneDeep("company", "f07xF008d", {"name": "airprocess"})
	 */
	async updateOneDeep(modelId, recordId, update) {
		log("kiss.db - online - updateOneDeep - Model " + modelId + " / Record " + recordId, 0, update)

		const response = await kiss.ajax.request({
			url: "/" + modelId + "/" + recordId,
			method: "patch",
			body: JSON.stringify({
				operation: "updateOneDeep",
				update
			})
		})

		return response
	},

	/**
	 * Update the 2 records connected by a link
	 * 
	 * @param {object} link 
	 * @returns The transaction result
	 */
	async updateLink(link) {
		log("kiss.db - online - updateLink: ", 0, link)

		const response = await kiss.ajax.request({
			url: "/updateLink",
			method: "post",
			body: JSON.stringify(link)
		})

		return response
	},

	/**
	 * Update multiple records in multiple collections. See [db.updateBulk](kiss.db.html#.updateBulk)
	 * 
	 * @async
	 * @param {object[]} operations - The list of updates to perform
	 * @returns {object} The server response
	 */
	async updateBulk(operations) {
		log("kiss.db - online - updateBulk", 0, operations)

		const response = await kiss.ajax.request({
			url: "/bulk",
			method: "patch",
			body: JSON.stringify(operations)
		})

		return response
	},

	/**
	 * Find a single record by id. See [db.findOne](kiss.db.html#.findOne)
	 * 
	 * @async
	 * @param {string} modelId 
	 * @param {string} recordId
	 * @returns {object} The server response
	 */
	async findOne(modelId, recordId) {
		log("kiss.db - online - findOne - Model " + modelId + " / Record " + recordId)

		return await kiss.ajax.request({
			url: "/" + modelId + "/" + recordId,
			method: "get"
		})
	},

	/**
	 * Find multiple records by id
	 * 
	 * @async
	 * @param {string} modelId 
	 * @param {string[]} ids - ids of the records to retrieve
	 * @param {object[]|object} [sort] - Sort options, as a normalized array or a Mongo object. Normalized example: [{fieldA: "asc"}, {fieldB: "desc"}]. Mongo example: {fieldA: 1, fieldB: -1}
	 * @param {string} [sortSyntax] - Sort syntax: "nomalized" | "mongo". Default is normalized
	 * @returns {object[]} The found records
	 */
	async findById(modelId, ids, sort = [], sortSyntax = "normalized") {
		log("kiss.db - online - findById - Model " + modelId + " / Records: " + ids.join())

		const search = {
			operation: "search",
			ids,
			sort,
			sortSyntax
		}

		return await kiss.ajax.request({
			url: "/" + modelId,
			method: "post",
			body: JSON.stringify(search)
		})
	},

	/**
	 * Find documents in a collection. See [db.find](kiss.db.html#.find)
	 * 
	 * Important:
	 * - without query params, it uses standard GET
	 * - with query params, the only way is to "POST" the request to the server
	 * In effet, the HTTP GET query parameters are limited in size, which is blocking to handle complex queries.
	 * 
	 * @async
	 * @param {string} modelId
	 * @param {object} [query] - Optional query object
	 * @returns {object[]} An array containing the records data, or an empty array if no record matches
	 */
	async find(modelId, query) {
		log("kiss.db - online - find - Model " + modelId + " / Query:", 0, query)

		// No query: we return all records with a find()
		if (!query) {
			return await kiss.ajax.request({
				url: "/" + modelId,
				method: "get"
			})
		}

		// Sanitize the query
		const search = {
			operation: "search",
			filter: query.filter || {},
			filterSyntax: query.filterSyntax || "normalized",
			sort: query.sort || [],
			sortSyntax: query.sortSyntax || "normalized",
			group: query.group || [],
			projection: query.projection || {},
			skip: query.skip,
			limit: query.limit,
			groupUnwind: query.groupUnwind
		}

		return await kiss.ajax.request({
			url: "/" + modelId,
			method: "post", // Only way to post the query object properly
			body: JSON.stringify(search)
		})
	},

	/**
	 * Delete an element from a collection. See [db.delete](kiss.db.html#.delete)
	 * 
	 * @async
	 * @param {string} modelId
	 * @param {string} recordId
	 * @param {boolean} [sendToTrash] - If true, keeps the original record in a "trash" collection
	 * @returns {boolean}
	 */
	async deleteOne(modelId, recordId, sendToTrash) {
		log("kiss.db - online - deleteOne - Model " + modelId + " / Record " + recordId)

		const response = await kiss.ajax.request({
			url: "/" + modelId + "/" + recordId,
			method: "delete",
			body: JSON.stringify({
				sendToTrash: !!sendToTrash
			})
		})

		return response
	},

	/**
	 * Delete many records from a collection
	 * 
	 * Note: REST doesn't have any standard way to perform a bulk delete.
	 * For this, we use a custom POST operation, as suggested in the Google API Design Guide
	 * 
	 * @param {string} modelId 
	 * @param {object} query
	 * @param {boolean} [sendToTrash] - If true, keeps the original record in a "trash" collection
	 * @returns {object} The server response
	 */
	async deleteMany(modelId, query, sendToTrash) {
		log("kiss.db - online - deleteMany - Model " + modelId, 0, query)

		const params = {
			operation: "delete",
			filter: query || {},
			sendToTrash: !!sendToTrash
		}

		const response = await kiss.ajax.request({
			url: "/" + modelId,
			method: "post", // Only way to post the query object properly
			body: JSON.stringify(params),
			showLoading: true
		})

		return response
	},

	/**
	 * TODO: Count the number of records that match a query
	 * 
	 * @async
	 * @param {string} modelId
	 * @param {object} query - Use same query format as for find() method
	 * @returns {number} The number of records
	 */
	count(modelId, query) {
		log("kiss.db - online - count - Model " + modelId, 0, query)

		// TODO
		return 0
	}
}