UNPKG

vanilla-party

Version:

An easy to use library for simple multi-user JavaScript apps.

162 lines (135 loc) 4.24 kB
import { JSONValue, JSONObject, RecordData, } from "@deepstream/client/dist/src/constants" import { SubscriptionCallback as OriginalSubscriptionCallback } from "@deepstream/client/dist/src/record/record" import * as log from "./log" export { JSONValue, JSONObject, RecordData } export type UserData = unknown export type SubscriptionCallback = | OriginalSubscriptionCallback | ((...args: Parameters<OriginalSubscriptionCallback>) => Promise<void>) /** * checks if `value` is JSONValue (JSON serializable) * logs error message if not * * @param value value to check * @param name the name of the value to use in debugging messages * @returns if the value is JSONValue */ export function isJSONValue( value: UserData, name = "unknown" ): value is JSONValue { const isJSON = validateJSONValue(value, "") if (!isJSON) { let details = "" if (validationError) { details = `\n"${name}${validationError.path}" ${validationError.message}` } log.warn(`User provided data is not JSON serializable.${details}`) } return isJSON } /** * checks if `value` is JSONObject (JSON serializable + object) * logs error message if not * * @param value value to check * @returns if the value is JSONObject */ export function isJSONObject( value: UserData, name?: string ): value is JSONObject { if (typeof value !== "object") { log.warn(`User provided data is not an object.`) return false } return isJSONValue(value, name) } export function isEmpty(o: any): boolean { if (typeof o !== "object") return false return Object.keys(o as object).length === 0 } function defined(value: unknown): boolean { return typeof value !== "undefined" } /** * checks the given value to see if it is JSON serializable * recursively checks all objects and arrays * * @param value the value to validate * @param path the starting path, this is used for error messages only * (and is currently useless) * @returns if the value is JSON serializable */ interface ValidationError { path: string message: string } // validationError is a module global and can be checked if/after validateJSONValue returns false for info on the error. // note: is there a better pattern for providing error details here? let validationError: ValidationError | null function validateJSONValue(value: UserData, path = ""): boolean { validationError = null // JSONPrimitives are okay if (["string", "number", "boolean"].includes(typeof value)) return true // nulls are okay if (value === null) return true // arrays okay, check children if (Array.isArray(value)) { for (const [i, v] of Object.entries(value)) { if (!validateJSONValue(v, `${path}[${i}]`)) return false } return true } // plain objects okay, check children if (typeof value === "object" && value.constructor === Object) { for (const [k, v] of Object.entries(value)) { if (!validateJSONValue(v, `${path}.${k}`)) return false } return true } if (typeof value === "function") { validationError = { path, message: `is a function. Functions are not allowed.`, } return false } if (typeof value === "symbol") { validationError = { path, message: `is a symbol. Symbols are not allowed.`, } return false } if (typeof value === "object") { // no warning for any objects not yet classified because its sometimes okay // for example the "stickies" example uses a Rect class with no methods // could possibly walk the prototype chain to see if there are any // properties that won't be shared and warn return true } if (typeof value === "undefined") { // allow it, key should be removed return true } validationError = { path, message: `is an unknown type. vanilla-party doesn't know what to do with it.`, } // if we got this far, we don't know what it is return false } /** * JSONValue is a Deepstream type that reflects what is JSON serializable * * checking a key on a JSONValue will warn because a JSONValue might * not be an object, it could be a primitive. * * .log((value as JSONObject).test); // okay * .log(value.test); // not okay */