New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@interweave/interweave

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@interweave/interweave - npm Package Compare versions

Comparing version 0.0.6 to 0.0.7

dist/helpers.d.ts

7

dist/index.d.ts

@@ -1,5 +0,6 @@

import { type Schema, type KeyConfiguration, type Request, type Error } from "./interfaces";
import { validate, validateSchema } from "./validate";
import { type Schema, type KeyConfiguration, type Request, type Error, type SchemaKeys } from "./interfaces";
import { validate, type ExternalValidateOptions as ValidateOptions, type ErrorsReturnObject } from "./validate";
import { validateSchema } from "./validateSchema";
import { buildInterface } from "./sync";
export { type Schema, type KeyConfiguration, type Request, type Error };
export { type Schema, type KeyConfiguration, type Request, type Error, type SchemaKeys, type ValidateOptions, type ErrorsReturnObject, };
export { buildInterface, validate, validateSchema };

@@ -6,4 +6,5 @@ "use strict";

Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return validate_1.validate; } });
Object.defineProperty(exports, "validateSchema", { enumerable: true, get: function () { return validate_1.validateSchema; } });
const validateSchema_1 = require("./validateSchema");
Object.defineProperty(exports, "validateSchema", { enumerable: true, get: function () { return validateSchema_1.validateSchema; } });
const sync_1 = require("./sync");
Object.defineProperty(exports, "buildInterface", { enumerable: true, get: function () { return sync_1.buildInterface; } });

@@ -199,6 +199,7 @@ export interface Error {

}
export interface SchemaKeys {
[key: string]: KeyConfiguration;
}
export interface Schema {
keys: {
[key: string]: KeyConfiguration;
};
keys: SchemaKeys;
/**

@@ -205,0 +206,0 @@ * Instructions for utilizing this data via the API

@@ -13,3 +13,3 @@ "use strict";

exports.buildInterface = void 0;
const validate_1 = require("./validate");
const validateSchema_1 = require("./validateSchema");
const API_URL = "https://api.interweave.studio/api/v1/projects";

@@ -23,3 +23,3 @@ const BUILD_INTERFACE_URL = ({ projectId }) => `${API_URL}/${projectId}/interfaces`;

// Validate configuration
(0, validate_1.validateSchema)(schema);
(0, validateSchema_1.validateSchema)(schema);
// Push to API

@@ -26,0 +26,0 @@ try {

@@ -63,2 +63,34 @@ "use strict";

},
nestedObject: {
schema: {
type: "object",
object_schema: {
keys: {
nestedOne: {
schema: {
type: "string",
},
},
nestedTwo: {
schema: {
type: "object",
object_schema: {
keys: {
nestedThree: {
schema: {
type: "string",
},
validation: {
min_length: 5,
is_email: true,
},
},
},
},
},
},
},
},
},
},
category: {

@@ -103,4 +135,4 @@ schema: {

// This can be executed at build time
const x = (0, validate_1.validateSchema)(newOne);
console.log(x);
// const x = validateSchema(newOne);
// console.log(x);
// This can be executed at runtime

@@ -114,4 +146,29 @@ // validate(_, newOne);

(0, fs_1.writeFile)((0, path_1.join)(__dirname, "../hm.json"), JSON.stringify(newObj), (err) => {
console.log(err);
if (err) {
console.log(err);
}
});
const newValues = {
id: "hi",
title: "text",
price: 600,
rating: 4.5,
stock: 23,
brand: "Apple",
thumbnail: "bla bla bla",
images: ["bla bla bla"],
description: "heyy",
category: "smartphones",
discountPercentage: 25,
nestedObject: {
nestedOne: "hi!",
nestedTwo: {
nestedThree: "mike@gmail.com",
},
},
};
const errorsObj = (0, validate_1.validate)(newValues, newOne, {
returnErrors: true,
});
console.log("E: ", errorsObj);
// (async () => {

@@ -118,0 +175,0 @@ // try {

@@ -1,10 +0,52 @@

import { type Schema } from "./interfaces";
import { type Schema, type KeyConfiguration } from "./interfaces";
interface ValidateOptions {
/**
* Whether to return an object of keys and their errors
*/
returnErrors?: boolean;
/**
* Specify the wider object so that we may properly look for dependent fields
*/
fullValueObject?: Record<string, unknown>;
/**
* recursive
*/
recursiveOpts?: {
/**
* Errors object for recursively handling the store
*/
errorsObj?: ErrorsReturnObject;
/**
* Path of the key
*/
path?: string;
};
}
export type ExternalValidateOptions = Omit<ValidateOptions, "recursiveOpts">;
export interface ErrorsReturnObject {
didError: boolean;
keys: {
[key: string]: {
errors: string[];
requiredAndMissing?: boolean;
};
};
}
export declare function validate(obj: {
[key: string]: any;
}, schema: Schema, fullValueObject?: {
[key: string]: any;
}): void;
}, schema: Schema, opts?: ValidateOptions): ErrorsReturnObject | undefined;
interface ValidateKeyConfigurationOptions {
onError?: (err: string) => any;
recursiveOpts?: {
path?: string;
errorsObj?: ErrorsReturnObject;
};
}
/**
* Recursively traverse an object's keys and ensure each is okay
*
* Purely for checking the validity of the supplied object
* NOT the supplied configuration
* at this point we assume the configuration is correct
*/
export declare function validateSchema(schema: Schema): boolean;
export declare function validateKeyConfiguration(key: string, value: any, config: KeyConfiguration, fullValueObject: Record<string, unknown>, opts?: ValidateKeyConfigurationOptions): boolean;
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateSchema = exports.validate = void 0;
exports.validateKeyConfiguration = exports.validate = void 0;
const helpers_1 = require("./helpers");
const throwError = (err) => {
throw new Error(`${err}`);
};
function get(object, path, defaultValue = null) {
// Convert dot notation to bracket notation
path = path.replace(/\[(\w+)\]/g, ".$1");
path = path.replace(/^\./, "");
// Split path into an array of keys
const keys = path.split(".");
// Iterate over the keys to retrieve the value
let result = object;
for (const key of keys) {
if (result != null &&
Object.prototype.hasOwnProperty.call(result, key)) {
result = result[key];
}
else {
return defaultValue;
}
}
return result === undefined ? defaultValue : result;
}
// obj will be the object we're testing
// schema will be the schema we're testing against
function validate(obj, schema, fullValueObject) {
function validate(obj, schema, opts) {
var _a;
const schemaKeys = Object.keys(schema.keys);
const requiredKeys = schemaKeys.filter((k) => !schema.keys[k].schema.is_optional);
const objectKeys = Object.keys(obj);
// Handle storing all errors
// This object doesn't get passed around but the function that sets this does
// We pass the errors object back and forth to maintain the store as we pass through recursive steps
const errorsReturnObject = ((_a = opts === null || opts === void 0 ? void 0 : opts.recursiveOpts) === null || _a === void 0 ? void 0 : _a.errorsObj)
? opts.recursiveOpts.errorsObj
: {
didError: false,
keys: {},
};
// Controls setting the errors in our store
const setErrorInReturnObject = (
/**
* Key to set in the object
*/
key,
/**
* Error that was encountered
*/
err,
/**
* Whether or not this field was required AND missing
* This will help us control frontend errors
*/
requiredAndMissing) => {
// Let it be known that this validation has failed
errorsReturnObject.didError = true;
// Set to an object if the key doesn't have anything set yet
if (typeof errorsReturnObject.keys[key] !== "object") {
errorsReturnObject.keys[key] = {
errors: [],
requiredAndMissing: false,
};
}
// Shorten access to path
// Can't move this above becuase we get yelled at for resetting a const
const fieldInErrorObject = errorsReturnObject.keys[key];
// Keep track of the missing required field
if (requiredAndMissing) {
fieldInErrorObject.requiredAndMissing = true;
}
// Add the error to the errors array
// Set the errors array if no array exists yet
if (fieldInErrorObject.errors) {
fieldInErrorObject.errors.push(err);
}
else {
fieldInErrorObject.errors = [err];
}
return;
};
const errorFn = (opts === null || opts === void 0 ? void 0 : opts.returnErrors) ? setErrorInReturnObject : throwError;
// Check to make sure each required key is present
// In general, it sucks that we have to put the function here
// It'd serve much better not here, but we won't have access to the full Schema in the function below
// Some fields may not be present, so we have to loop through the schema looking for missing keys
// Can't do that in reverse
requiredKeys.forEach((k) => {
if (!objectKeys.includes(k)) {
throwError(`Missing required key '${k}' in supplied object.`);
var _a, _b;
const val = obj[k];
if ((0, helpers_1.isEmpty)(val)) {
const errString = `Missing required key '${k}' in supplied object.`;
const targetKey = ((_a = opts === null || opts === void 0 ? void 0 : opts.recursiveOpts) === null || _a === void 0 ? void 0 : _a.path)
? `${(_b = opts === null || opts === void 0 ? void 0 : opts.recursiveOpts) === null || _b === void 0 ? void 0 : _b.path}.${k}`
: k;
if (opts === null || opts === void 0 ? void 0 : opts.returnErrors) {
errorFn(targetKey, errString, true);
}
else {
throwError(errString);
}
}
});
const determinedFullValueObject = fullValueObject ? fullValueObject : obj;
// Specify the wider object so that we may properly look for dependent fields
// This will be the full object, NOT a subset as we move recursively through
const determinedFullValueObject = (opts === null || opts === void 0 ? void 0 : opts.fullValueObject)
? opts === null || opts === void 0 ? void 0 : opts.fullValueObject
: obj;
// Validate each key and its value
objectKeys.forEach((k) => {
var _a, _b;
const schemaConfig = schema.keys[k];
const value = obj[k];
validateKeyConfiguration(k, value, schemaConfig, determinedFullValueObject);
/**
* Target key is how we handle nested keys so we can give a proper error
* We combine via the .
* So an object like { parent: { nested: Schema } }
* Will produce the key in the errorObj: parent.nested
*/
const targetKey = ((_a = opts === null || opts === void 0 ? void 0 : opts.recursiveOpts) === null || _a === void 0 ? void 0 : _a.path)
? `${(_b = opts === null || opts === void 0 ? void 0 : opts.recursiveOpts) === null || _b === void 0 ? void 0 : _b.path}.${k}`
: k;
validateKeyConfiguration(
/**
* Target key will be the name / key
*/
targetKey,
/**
* Value from our value object
*/
value,
/**
* Schema will be the schema config for this interface
*/
schemaConfig,
/**
* Full value object will be used for validating dependent fields
* Like the emptyIfPresent etc, they need to know the full value object
*/
determinedFullValueObject, {
/**
* Pass the target key and the error in
*/
onError: (err) => errorFn(targetKey, err),
/**
* Keep the same errors object floating back and forth so we handle recursive object checks with the same obj
*/
recursiveOpts: {
errorsObj: errorsReturnObject,
},
});
});
if (opts === null || opts === void 0 ? void 0 : opts.returnErrors) {
return errorsReturnObject;
}
}
exports.validate = validate;
const isValuePresent = (fullObject, target) => {
// Deep parse
const value = get(fullObject, target);
return !(typeof value === "undefined" || value === null || value === "");
};
/**

@@ -58,8 +147,10 @@ *

*/
function validateKeyConfiguration(key, value, config, fullValueObject) {
var _a, _b, _c, _d;
function validateKeyConfiguration(key, value, config, fullValueObject, opts) {
var _a, _b, _c, _d, _e, _f, _g;
// Override error function
const error = (opts === null || opts === void 0 ? void 0 : opts.onError) || throwError;
// Make sure required keys have a value
if (!config.schema.is_optional) {
if (value === null || value === undefined || value === "") {
throwError(`Key '${key}' is a required field, but no value was received.`);
error(`Key '${key}' is a required field, but no value was received.`);
}

@@ -72,7 +163,7 @@ }

const values = arr
.map((target) => isValuePresent(fullValueObject, target))
.map((target) => (0, helpers_1.isValuePresent)(fullValueObject, target))
.filter((v) => v === true);
const allPresent = values.length === arr.length;
if (allPresent && !value) {
throwError(`Key ${key} must have a value if keys ${arr.join(", ")} are present, received ${value}.`);
error(`Key ${key} must have a value if keys ${arr.join(", ")} are present, received ${value}.`);
}

@@ -84,7 +175,7 @@ }

const values = arr
.map((target) => isValuePresent(fullValueObject, target))
.map((target) => (0, helpers_1.isValuePresent)(fullValueObject, target))
.filter((v) => v === true);
const anyPresent = values.length > 0;
if (anyPresent && !value) {
throwError(`Key ${key} must have a value if any of the keys ${arr.join(", ")} are present, received ${value}.`);
error(`Key ${key} must have a value if any of the keys ${arr.join(", ")} are present, received ${value}.`);
}

@@ -96,7 +187,7 @@ }

const values = arr
.map((target) => isValuePresent(fullValueObject, target))
.map((target) => (0, helpers_1.isValuePresent)(fullValueObject, target))
.filter((v) => v === true);
const anyEmpty = values.length !== arr.length;
if (anyEmpty && !value) {
throwError(`Key ${key} must have a value if any of the keys ${arr.join(", ")} are empty, received ${value}.`);
error(`Key ${key} must have a value if any of the keys ${arr.join(", ")} are empty, received ${value}.`);
}

@@ -108,7 +199,7 @@ }

const values = ensurePresentIfAllEmpty
.map((target) => isValuePresent(fullValueObject, target))
.map((target) => (0, helpers_1.isValuePresent)(fullValueObject, target))
.filter((v) => v === true);
const allEmpty = values.length === 0;
if (allEmpty && !value) {
throwError(`Key ${key} must have a value if all of the keys ${ensurePresentIfAllEmpty.join(", ")} are empty, received ${value}.`);
error(`Key ${key} must have a value if all of the keys ${ensurePresentIfAllEmpty.join(", ")} are empty, received ${value}.`);
}

@@ -120,7 +211,7 @@ }

const values = arr
.map((target) => isValuePresent(fullValueObject, target))
.map((target) => (0, helpers_1.isValuePresent)(fullValueObject, target))
.filter((v) => v === true);
const noneEmpty = values.length === arr.length;
if (noneEmpty && !value) {
throwError(`Key ${key} must have a value if none of the keys ${arr.join(", ")} are empty, received ${value}.`);
error(`Key ${key} must have a value if none of the keys ${arr.join(", ")} are empty, received ${value}.`);
}

@@ -130,15 +221,18 @@ }

// If it's optional and not present, lets move on
if (config.schema.is_optional &&
(typeof value === "undefined" || value === null)) {
if (config.schema.is_optional && (0, helpers_1.isEmpty)(value)) {
return true;
}
// Make sure value is an array if it was specified
if (config.schema.is_array) {
const expectsArray = config.schema.is_array;
if (expectsArray) {
if (!Array.isArray(value)) {
throwError(`Key '${key}' was specified as an array field, but no array was received.`);
error(`Key '${key}' was specified as an array field, but no array was received.`);
}
}
// Make sure value objects are configured correctly
if (config.schema.type !== typeof value) {
throwError(`Key '${key}' was specified as type ${config.schema.type} but received ${typeof value}.`);
const validArrayType = expectsArray && Array.isArray(value);
if (config.schema.type !== typeof value && !validArrayType) {
const schemaType = expectsArray ? "array" : config.schema.type;
// Handle config.schema saying array instad of object
error(`Key '${key}' was specified as type ${schemaType} but received ${typeof value}.`);
}

@@ -148,3 +242,13 @@ // Make sure objects and their keys obey the rules

// Exclamation point because we assume a correct configuration
validate(value, config.schema.object_schema, fullValueObject);
const path = ((_a = opts === null || opts === void 0 ? void 0 : opts.recursiveOpts) === null || _a === void 0 ? void 0 : _a.path)
? `${(_b = opts === null || opts === void 0 ? void 0 : opts.recursiveOpts) === null || _b === void 0 ? void 0 : _b.path}.${key}`
: key;
validate(value, config.schema.object_schema, {
recursiveOpts: {
path,
errorsObj: (_c = opts === null || opts === void 0 ? void 0 : opts.recursiveOpts) === null || _c === void 0 ? void 0 : _c.errorsObj,
},
returnErrors: true,
fullValueObject: fullValueObject,
});
}

@@ -154,3 +258,3 @@ // Make sure value is part of the enum if supplied

if (config.schema.enum.length === 0 && value) {
throwError(`Key ${key} specified an enum with no values, preventing this value from being set. Please delete the enum field, add values to the enum, or make sure this key is null or undefined.`);
error(`Key ${key} specified an enum with no values, preventing this value from being set. Please delete the enum field, add values to the enum, or make sure this key is null or undefined.`);
}

@@ -163,6 +267,6 @@ // Lets make sure all the values in the array are in the enum

if (!config.schema.enum.includes(v)) {
throwError(`Value '${v}' specified in array for key '${key}' is not an allowed value according to the supplied enum.`);
error(`Value '${v}' specified in array for key '${key}' is not an allowed value according to the supplied enum.`);
}
if (config.schema.enum.length === 0 && value.length > 0) {
throwError(`Key ${key} specified an enum with no values, preventing this value from being set. Please delete the enum field, add values to the enum, or make sure this key is null or undefined.`);
error(`Key ${key} specified an enum with no values, preventing this value from being set. Please delete the enum field, add values to the enum, or make sure this key is null or undefined.`);
}

@@ -173,3 +277,3 @@ }

if (!config.schema.enum.includes(value)) {
throwError(`Key '${key}' expected a specfic value from the specified enum. Instead received '${value}'.`);
error(`Key '${key}' expected a specfic value from the specified enum. Instead received '${value}'.`);
}

@@ -183,3 +287,3 @@ }

if (value < config.validation.min) {
throwError(`Key '${key}' has a minimum value of '${config.validation.min}' but received '${value}'.`);
error(`Key '${key}' has a minimum value of '${config.validation.min}' but received '${value}'.`);
}

@@ -189,3 +293,3 @@ }

if (value > config.validation.max) {
throwError(`Key '${key}' has a maximum value of '${config.validation.max}' but received '${value}'.`);
error(`Key '${key}' has a maximum value of '${config.validation.max}' but received '${value}'.`);
}

@@ -197,14 +301,14 @@ }

if (config.validation.length) {
if (value.length !== config.validation.length) {
throwError(`Key '${key}' should have an exact length of ${config.validation.length}, but received ${value.length}.`);
if ((value === null || value === void 0 ? void 0 : value.length) !== config.validation.length) {
error(`Key '${key}' should have an exact length of ${config.validation.length}, but received ${value.length}.`);
}
}
if (config.validation.max_length) {
if (value.length > config.validation.max_length) {
throwError(`Key '${key}' should not exceed a length of ${config.validation.max_length}, but received ${value.length}.`);
if ((value === null || value === void 0 ? void 0 : value.length) > config.validation.max_length) {
error(`Key '${key}' should not exceed a length of ${config.validation.max_length}, but received ${value.length}.`);
}
}
if (config.validation.min_length) {
if (value.length < config.validation.min_length) {
throwError(`Key '${key}' should not have a length less than ${config.validation.max_length}, but received ${value.length}.`);
if ((value === null || value === void 0 ? void 0 : value.length) < config.validation.min_length) {
error(`Key '${key}' should not have a length less than ${config.validation.max_length}, but received ${value.length}.`);
}

@@ -215,3 +319,3 @@ }

if (value !== config.validation.equals) {
throwError(`Key '${key}' should equal '${config.validation.equals}'.`);
error(`Key '${key}' should equal '${config.validation.equals}'.`);
}

@@ -221,3 +325,3 @@ }

if (value === config.validation.equals) {
throwError(`Key '${key}' should not equal '${config.validation.is_not}'.`);
error(`Key '${key}' should not equal '${config.validation.is_not}'.`);
}

@@ -228,23 +332,23 @@ }

if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
throwError(`Key '${key}' was specified to be an email but received '${value}'.`);
error(`Key '${key}' was specified to be an email but received '${value}'.`);
}
// Make sure the @ comes before the .
if (value.indexOf("@") > value.indexOf(".")) {
throwError(`Key '${key}' was passed an invalid email '${value}'.`);
if ((value === null || value === void 0 ? void 0 : value.indexOf("@")) > (value === null || value === void 0 ? void 0 : value.indexOf("."))) {
error(`Key '${key}' was passed an invalid email '${value}'.`);
}
const domain = value.substring(value.indexOf("@") + 1, value.lastIndexOf("."));
const tld = value.substring(value.lastIndexOf(".") + 1);
const domain = value === null || value === void 0 ? void 0 : value.substring((value === null || value === void 0 ? void 0 : value.indexOf("@")) + 1, value === null || value === void 0 ? void 0 : value.lastIndexOf("."));
const tld = value === null || value === void 0 ? void 0 : value.substring((value === null || value === void 0 ? void 0 : value.lastIndexOf(".")) + 1);
// No characters in between @ and .
if (!domain) {
throwError(`Key '${key}' was passed an invalid email '${value}'.`);
error(`Key '${key}' was passed an invalid email '${value}'.`);
}
if (typeof config.validation.is_email === "object") {
if ((_a = config.validation.is_email) === null || _a === void 0 ? void 0 : _a.forbidden_domains) {
if ((_b = config.validation.is_email) === null || _b === void 0 ? void 0 : _b.forbidden_domains.includes(domain)) {
throwError(`Key '${key}' was passed an email with a forbidden domain '${domain}'.`);
if ((_d = config.validation.is_email) === null || _d === void 0 ? void 0 : _d.forbidden_domains) {
if ((_e = config.validation.is_email) === null || _e === void 0 ? void 0 : _e.forbidden_domains.includes(domain)) {
error(`Key '${key}' was passed an email with a forbidden domain '${domain}'.`);
}
}
if ((_c = config.validation.is_email) === null || _c === void 0 ? void 0 : _c.forbidden_tlds) {
if ((_d = config.validation.is_email) === null || _d === void 0 ? void 0 : _d.forbidden_tlds.includes(tld)) {
throwError(`Key '${key}' was passed an email with an invalid TLD '${tld}'.`);
if ((_f = config.validation.is_email) === null || _f === void 0 ? void 0 : _f.forbidden_tlds) {
if ((_g = config.validation.is_email) === null || _g === void 0 ? void 0 : _g.forbidden_tlds.includes(tld)) {
error(`Key '${key}' was passed an email with an invalid TLD '${tld}'.`);
}

@@ -258,3 +362,3 @@ }

if (!/^\+?\d+$/.test(value) || value.charAt(0) !== "+") {
throwError(`Phone number must be a string consisting of numbers and a country code denoted with a '+' at the beginning, received '${value}'.`);
error(`Phone number must be a string consisting of numbers and a country code denoted with a '+' at the beginning, received '${value}'.`);
}

@@ -265,3 +369,3 @@ }

if (!/^\d+$/.test(value)) {
throwError(`Phone number must be a string consisting only of numbers, received '${value}'.`);
error(`Phone number must be a string consisting only of numbers, received '${value}'.`);
}

@@ -273,109 +377,2 @@ }

}
function fieldMustNotBeSet(key, fieldName, value, why) {
if (Array.isArray(value)) {
if (value.length > 0) {
return throwError(`'${fieldName}' must not be set in key '${key}' ${why}.`);
}
return true;
}
if (typeof value === "undefined" ||
value === null ||
value === "" ||
value === false) {
return true;
}
return throwError(`'${fieldName}' must not be set in key '${key}' ${why}.`);
}
function fieldMustBeSet(key, fieldName, value, why) {
if (Array.isArray(value)) {
if (value.length <= 0) {
return throwError(`'${fieldName}' must be set in key '${key}' ${why}.`);
}
}
if (typeof value === "undefined" ||
value === null ||
value === "" ||
value === false) {
return throwError(`'${fieldName}' must be set in key '${key}' ${why}.`);
}
return true;
}
/**
* Recursively traverse an object's keys and ensure each is okay
*/
function validateSchema(schema) {
// Loop through each key and validate its schema
for (const [k, keySchema] of Object.entries(schema.keys)) {
validateKeySchema(k, keySchema);
}
return true;
}
exports.validateSchema = validateSchema;
/**
* Function we can check a keys configuration object
*/
function validateKeySchema(key, config) {
var _a;
const { schema, validation } = config;
// Lets set the key so we don't have to keep doing it
const ensureFieldNotSet = (fieldName, value, why) => fieldMustNotBeSet(key, fieldName, value, why);
const ensureFieldSet = (fieldName, value, why) => fieldMustBeSet(key, fieldName, value, why);
ensureFieldSet("schema.type", schema === null || schema === void 0 ? void 0 : schema.type, "- field is required");
if (!schema.is_optional) {
const reason = "if schema.is_optional is false";
ensureFieldNotSet("validation.ensure_empty_if_any_present", validation === null || validation === void 0 ? void 0 : validation.ensure_empty_if_any_present, reason);
ensureFieldNotSet("validation.ensure_empty_if_all_present", validation === null || validation === void 0 ? void 0 : validation.ensure_empty_if_all_present, reason);
ensureFieldNotSet("validation.ensure_empty_if_all_empty", validation === null || validation === void 0 ? void 0 : validation.ensure_empty_if_all_empty, reason);
ensureFieldNotSet("validation.ensure_empty_if_any_empty", validation === null || validation === void 0 ? void 0 : validation.ensure_empty_if_any_empty, reason);
ensureFieldNotSet("validation.ensure_empty_if_none_empty", validation === null || validation === void 0 ? void 0 : validation.ensure_empty_if_none_empty, reason);
}
if (schema.type === "string") {
const reason = "if schema.type is string";
ensureFieldNotSet("validation.min", validation === null || validation === void 0 ? void 0 : validation.min, reason);
ensureFieldNotSet("validation.max", validation === null || validation === void 0 ? void 0 : validation.max, reason);
}
if (schema.type === "number") {
const reason = "if schema.type is number";
ensureFieldNotSet("validation.is_email", validation === null || validation === void 0 ? void 0 : validation.is_email, reason);
if (typeof (validation === null || validation === void 0 ? void 0 : validation.is_phone) !== "undefined") {
if (typeof validation.is_phone === "object") {
ensureFieldNotSet("validation.is_phone.include_country_code", (_a = validation === null || validation === void 0 ? void 0 : validation.is_phone) === null || _a === void 0 ? void 0 : _a.include_country_code, reason);
}
}
ensureFieldNotSet("validation.is_phone", validation === null || validation === void 0 ? void 0 : validation.is_phone, reason);
}
if (schema.type === "object") {
// Check recursive fields inside the nested Schema
if (schema.object_schema) {
validateSchema(schema.object_schema);
}
const reason = "if schema.type is number";
ensureFieldNotSet("schema.enum", schema === null || schema === void 0 ? void 0 : schema.enum, reason);
ensureFieldNotSet("validation.min", validation === null || validation === void 0 ? void 0 : validation.min, reason);
ensureFieldNotSet("validation.max", validation === null || validation === void 0 ? void 0 : validation.max, reason);
ensureFieldNotSet("validation.equals", validation === null || validation === void 0 ? void 0 : validation.equals, reason);
ensureFieldNotSet("validation.is_email", validation === null || validation === void 0 ? void 0 : validation.is_email, reason);
ensureFieldNotSet("validation.is_phone", validation === null || validation === void 0 ? void 0 : validation.is_phone, reason);
ensureFieldSet("schema.object_schema", schema === null || schema === void 0 ? void 0 : schema.object_schema, reason);
}
if (schema.type === "boolean") {
const reason = "if schema.type is boolean";
ensureFieldNotSet("schema.enum", schema === null || schema === void 0 ? void 0 : schema.enum, reason);
ensureFieldNotSet("validation.min", validation === null || validation === void 0 ? void 0 : validation.min, reason);
ensureFieldNotSet("validation.max", validation === null || validation === void 0 ? void 0 : validation.max, reason);
ensureFieldNotSet("validation.is_email", validation === null || validation === void 0 ? void 0 : validation.is_email, reason);
ensureFieldNotSet("validation.is_phone", validation === null || validation === void 0 ? void 0 : validation.is_phone, reason);
}
// Let's make sure the default value obeys our rules
if (schema.default_value) {
validateKeyConfiguration(key, schema.default_value, config, {});
}
// length, min_length, and max_length only available on strings and arrays
if (schema.type !== "string" && !schema.is_array) {
const reason = "if type isn't string and is not an array";
ensureFieldNotSet("validation.length", validation === null || validation === void 0 ? void 0 : validation.length, reason);
ensureFieldNotSet("validation.min_length", validation === null || validation === void 0 ? void 0 : validation.min_length, reason);
ensureFieldNotSet("validation.max_length", validation === null || validation === void 0 ? void 0 : validation.max_length, reason);
}
return true;
}
exports.validateKeyConfiguration = validateKeyConfiguration;
{
"name": "@interweave/interweave",
"version": "0.0.6",
"version": "0.0.7",
"description": "",

@@ -17,6 +17,10 @@ "main": "dist/index.js",

"devDependencies": {
"@types/is-empty": "^1.2.1",
"jest": "^29.4.3",
"ts-jest": "^29.0.5",
"typescript": "^4.9.5"
},
"dependencies": {
"is-empty": "^1.2.0"
}
}
# Interweave
Interweave is a platform for creating user interfaces from static JSON. The Interweave configuration objects are meant to be checked into version control and tied closely to database and API changes.
## `validate`
The `validate()' function is a good way to test an object against your configuration. This is useful for validating user input from forms and requests.
```js
import { validate } from "@interweave/interweave";
// Validate and throw any errors to a console immediately
// Good for using in a build pipeline
validate(object, schema);
// Run and collect errors to an object
// Good for forms and handling form errors
const errorsObject = validate(object, schema, { returnErrors: true });
```
## Architecture
### Flattening and expanding
We will take nested objects, flatten them down into their keys
```js
{
title: "Some Title",
description: "some description",
author: {
name: "mike"
}
}
```
Error and form object becomes:
```js
{
title: "Some Title",
description: "some description",
"author.name": "mike"
}
```
Then we use those keys to expand back into a wider object before submission:
```js
{
title: "Some Title",
description: "some description",
author: {
name: "mike"
}
}
```

@@ -6,7 +6,23 @@ import {

type Error,
type SchemaKeys,
} from "./interfaces";
import { validate, validateSchema } from "./validate";
import {
validate,
type ExternalValidateOptions as ValidateOptions,
type ErrorsReturnObject,
} from "./validate";
import { validateSchema } from "./validateSchema";
import { buildInterface } from "./sync";
export { type Schema, type KeyConfiguration, type Request, type Error };
export {
// From interfaces
type Schema,
type KeyConfiguration,
type Request,
type Error,
type SchemaKeys,
// From validate()
type ValidateOptions,
type ErrorsReturnObject,
};
export { buildInterface, validate, validateSchema };

@@ -216,6 +216,8 @@ export interface Error {

export interface SchemaKeys {
[key: string]: KeyConfiguration;
}
export interface Schema {
keys: {
[key: string]: KeyConfiguration;
};
keys: SchemaKeys;
/**

@@ -222,0 +224,0 @@ * Instructions for utilizing this data via the API

import { type Schema } from "./interfaces";
import { validateSchema } from "./validate";
import { validateSchema } from "./validateSchema";

@@ -4,0 +4,0 @@ const API_URL = "https://api.interweave.studio/api/v1/projects";

@@ -0,1 +1,2 @@

import { isEmpty, isValuePresent } from "./helpers";
import { type Schema, type KeyConfiguration } from "./interfaces";

@@ -7,24 +8,37 @@

function get(object: object, path: string, defaultValue = null): any {
// Convert dot notation to bracket notation
path = path.replace(/\[(\w+)\]/g, ".$1");
path = path.replace(/^\./, "");
interface ValidateOptions {
/**
* Whether to return an object of keys and their errors
*/
returnErrors?: boolean;
// Split path into an array of keys
const keys = path.split(".");
/**
* Specify the wider object so that we may properly look for dependent fields
*/
fullValueObject?: Record<string, unknown>;
// Iterate over the keys to retrieve the value
let result = object;
for (const key of keys) {
if (
result != null &&
Object.prototype.hasOwnProperty.call(result, key)
) {
result = result[key as keyof typeof result];
} else {
return defaultValue;
}
}
/**
* recursive
*/
recursiveOpts?: {
/**
* Errors object for recursively handling the store
*/
errorsObj?: ErrorsReturnObject;
/**
* Path of the key
*/
path?: string;
};
}
export type ExternalValidateOptions = Omit<ValidateOptions, "recursiveOpts">;
return result === undefined ? defaultValue : result;
export interface ErrorsReturnObject {
didError: boolean;
keys: {
[key: string]: {
errors: string[];
requiredAndMissing?: boolean;
};
};
}

@@ -37,3 +51,3 @@

schema: Schema,
fullValueObject?: { [key: string]: any }
opts?: ValidateOptions
) {

@@ -46,10 +60,84 @@ const schemaKeys = Object.keys(schema.keys);

// Handle storing all errors
// This object doesn't get passed around but the function that sets this does
// We pass the errors object back and forth to maintain the store as we pass through recursive steps
const errorsReturnObject: ErrorsReturnObject = opts?.recursiveOpts
?.errorsObj
? opts.recursiveOpts.errorsObj
: {
didError: false,
keys: {},
};
// Controls setting the errors in our store
const setErrorInReturnObject = (
/**
* Key to set in the object
*/
key: string,
/**
* Error that was encountered
*/
err: string,
/**
* Whether or not this field was required AND missing
* This will help us control frontend errors
*/
requiredAndMissing?: boolean
) => {
// Let it be known that this validation has failed
errorsReturnObject.didError = true;
// Set to an object if the key doesn't have anything set yet
if (typeof errorsReturnObject.keys[key] !== "object") {
errorsReturnObject.keys[key] = {
errors: [],
requiredAndMissing: false,
};
}
// Shorten access to path
// Can't move this above becuase we get yelled at for resetting a const
const fieldInErrorObject = errorsReturnObject.keys[key];
// Keep track of the missing required field
if (requiredAndMissing) {
fieldInErrorObject.requiredAndMissing = true;
}
// Add the error to the errors array
// Set the errors array if no array exists yet
if (fieldInErrorObject.errors) {
fieldInErrorObject.errors.push(err);
} else {
fieldInErrorObject.errors = [err];
}
return;
};
const errorFn = opts?.returnErrors ? setErrorInReturnObject : throwError;
// Check to make sure each required key is present
// In general, it sucks that we have to put the function here
// It'd serve much better not here, but we won't have access to the full Schema in the function below
// Some fields may not be present, so we have to loop through the schema looking for missing keys
// Can't do that in reverse
requiredKeys.forEach((k) => {
if (!objectKeys.includes(k)) {
throwError(`Missing required key '${k}' in supplied object.`);
const val = obj[k];
if (isEmpty(val)) {
const errString = `Missing required key '${k}' in supplied object.`;
const targetKey = opts?.recursiveOpts?.path
? `${opts?.recursiveOpts?.path}.${k}`
: k;
if (opts?.returnErrors) {
errorFn(targetKey, errString, true);
} else {
throwError(errString);
}
}
});
const determinedFullValueObject = fullValueObject ? fullValueObject : obj;
// Specify the wider object so that we may properly look for dependent fields
// This will be the full object, NOT a subset as we move recursively through
const determinedFullValueObject = opts?.fullValueObject
? opts?.fullValueObject
: obj;

@@ -60,17 +148,57 @@ // Validate each key and its value

const value = obj[k];
/**
* Target key is how we handle nested keys so we can give a proper error
* We combine via the .
* So an object like { parent: { nested: Schema } }
* Will produce the key in the errorObj: parent.nested
*/
const targetKey = opts?.recursiveOpts?.path
? `${opts?.recursiveOpts?.path}.${k}`
: k;
validateKeyConfiguration(
k,
/**
* Target key will be the name / key
*/
targetKey,
/**
* Value from our value object
*/
value,
/**
* Schema will be the schema config for this interface
*/
schemaConfig,
determinedFullValueObject
/**
* Full value object will be used for validating dependent fields
* Like the emptyIfPresent etc, they need to know the full value object
*/
determinedFullValueObject,
{
/**
* Pass the target key and the error in
*/
onError: (err) => errorFn(targetKey, err),
/**
* Keep the same errors object floating back and forth so we handle recursive object checks with the same obj
*/
recursiveOpts: {
errorsObj: errorsReturnObject,
},
}
);
});
if (opts?.returnErrors) {
return errorsReturnObject;
}
}
const isValuePresent = (fullObject: object, target: string) => {
// Deep parse
const value = get(fullObject, target);
return !(typeof value === "undefined" || value === null || value === "");
};
interface ValidateKeyConfigurationOptions {
onError?: (err: string) => any;
recursiveOpts?: {
path?: string;
errorsObj?: ErrorsReturnObject;
};
}
/**

@@ -82,12 +210,16 @@ *

*/
function validateKeyConfiguration(
export function validateKeyConfiguration(
key: string,
value: any,
config: KeyConfiguration,
fullValueObject: object
fullValueObject: Record<string, unknown>,
opts?: ValidateKeyConfigurationOptions
) {
// Override error function
const error = opts?.onError || throwError;
// Make sure required keys have a value
if (!config.schema.is_optional) {
if (value === null || value === undefined || value === "") {
throwError(
error(
`Key '${key}' is a required field, but no value was received.`

@@ -107,3 +239,3 @@ );

if (allPresent && !value) {
throwError(
error(
`Key ${key} must have a value if keys ${arr.join(

@@ -124,3 +256,3 @@ ", "

if (anyPresent && !value) {
throwError(
error(
`Key ${key} must have a value if any of the keys ${arr.join(

@@ -141,3 +273,3 @@ ", "

if (anyEmpty && !value) {
throwError(
error(
`Key ${key} must have a value if any of the keys ${arr.join(

@@ -159,3 +291,3 @@ ", "

if (allEmpty && !value) {
throwError(
error(
`Key ${key} must have a value if all of the keys ${ensurePresentIfAllEmpty.join(

@@ -176,3 +308,3 @@ ", "

if (noneEmpty && !value) {
throwError(
error(
`Key ${key} must have a value if none of the keys ${arr.join(

@@ -187,6 +319,3 @@ ", "

// If it's optional and not present, lets move on
if (
config.schema.is_optional &&
(typeof value === "undefined" || value === null)
) {
if (config.schema.is_optional && isEmpty(value)) {
return true;

@@ -196,5 +325,6 @@ }

// Make sure value is an array if it was specified
if (config.schema.is_array) {
const expectsArray = config.schema.is_array;
if (expectsArray) {
if (!Array.isArray(value)) {
throwError(
error(
`Key '${key}' was specified as an array field, but no array was received.`

@@ -206,7 +336,8 @@ );

// Make sure value objects are configured correctly
if (config.schema.type !== typeof value) {
throwError(
`Key '${key}' was specified as type ${
config.schema.type
} but received ${typeof value}.`
const validArrayType = expectsArray && Array.isArray(value);
if (config.schema.type !== typeof value && !validArrayType) {
const schemaType = expectsArray ? "array" : config.schema.type;
// Handle config.schema saying array instad of object
error(
`Key '${key}' was specified as type ${schemaType} but received ${typeof value}.`
);

@@ -218,3 +349,13 @@ }

// Exclamation point because we assume a correct configuration
validate(value, config.schema.object_schema!, fullValueObject);
const path = opts?.recursiveOpts?.path
? `${opts?.recursiveOpts?.path}.${key}`
: key;
validate(value, config.schema.object_schema!, {
recursiveOpts: {
path,
errorsObj: opts?.recursiveOpts?.errorsObj,
},
returnErrors: true,
fullValueObject: fullValueObject,
});
}

@@ -225,3 +366,3 @@

if (config.schema.enum.length === 0 && value) {
throwError(
error(
`Key ${key} specified an enum with no values, preventing this value from being set. Please delete the enum field, add values to the enum, or make sure this key is null or undefined.`

@@ -236,3 +377,3 @@ );

if (!(config.schema.enum as any[]).includes(v)) {
throwError(
error(
`Value '${v}' specified in array for key '${key}' is not an allowed value according to the supplied enum.`

@@ -242,3 +383,3 @@ );

if (config.schema.enum.length === 0 && value.length > 0) {
throwError(
error(
`Key ${key} specified an enum with no values, preventing this value from being set. Please delete the enum field, add values to the enum, or make sure this key is null or undefined.`

@@ -251,3 +392,3 @@ );

if (!(config.schema.enum as any[]).includes(value)) {
throwError(
error(
`Key '${key}' expected a specfic value from the specified enum. Instead received '${value}'.`

@@ -264,3 +405,3 @@ );

if (value < config.validation.min) {
throwError(
error(
`Key '${key}' has a minimum value of '${config.validation.min}' but received '${value}'.`

@@ -272,3 +413,3 @@ );

if (value > config.validation.max) {
throwError(
error(
`Key '${key}' has a maximum value of '${config.validation.max}' but received '${value}'.`

@@ -283,4 +424,4 @@ );

if (config.validation.length) {
if (value.length !== config.validation.length) {
throwError(
if (value?.length !== config.validation.length) {
error(
`Key '${key}' should have an exact length of ${config.validation.length}, but received ${value.length}.`

@@ -291,4 +432,4 @@ );

if (config.validation.max_length) {
if (value.length > config.validation.max_length) {
throwError(
if (value?.length > config.validation.max_length) {
error(
`Key '${key}' should not exceed a length of ${config.validation.max_length}, but received ${value.length}.`

@@ -299,4 +440,4 @@ );

if (config.validation.min_length) {
if (value.length < config.validation.min_length) {
throwError(
if (value?.length < config.validation.min_length) {
error(
`Key '${key}' should not have a length less than ${config.validation.max_length}, but received ${value.length}.`

@@ -310,3 +451,3 @@ );

if (value !== config.validation.equals) {
throwError(
error(
`Key '${key}' should equal '${config.validation.equals}'.`

@@ -318,3 +459,3 @@ );

if (value === config.validation.equals) {
throwError(
error(
`Key '${key}' should not equal '${config.validation.is_not}'.`

@@ -328,3 +469,3 @@ );

if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
throwError(
error(
`Key '${key}' was specified to be an email but received '${value}'.`

@@ -334,17 +475,13 @@ );

// Make sure the @ comes before the .
if (value.indexOf("@") > value.indexOf(".")) {
throwError(
`Key '${key}' was passed an invalid email '${value}'.`
);
if (value?.indexOf("@") > value?.indexOf(".")) {
error(`Key '${key}' was passed an invalid email '${value}'.`);
}
const domain = value.substring(
value.indexOf("@") + 1,
value.lastIndexOf(".")
const domain = value?.substring(
value?.indexOf("@") + 1,
value?.lastIndexOf(".")
);
const tld = value.substring(value.lastIndexOf(".") + 1);
const tld = value?.substring(value?.lastIndexOf(".") + 1);
// No characters in between @ and .
if (!domain) {
throwError(
`Key '${key}' was passed an invalid email '${value}'.`
);
error(`Key '${key}' was passed an invalid email '${value}'.`);
}

@@ -358,3 +495,3 @@ if (typeof config.validation.is_email === "object") {

) {
throwError(
error(
`Key '${key}' was passed an email with a forbidden domain '${domain}'.`

@@ -368,3 +505,3 @@ );

) {
throwError(
error(
`Key '${key}' was passed an email with an invalid TLD '${tld}'.`

@@ -381,3 +518,3 @@ );

if (!/^\+?\d+$/.test(value) || value.charAt(0) !== "+") {
throwError(
error(
`Phone number must be a string consisting of numbers and a country code denoted with a '+' at the beginning, received '${value}'.`

@@ -389,3 +526,3 @@ );

if (!/^\d+$/.test(value)) {
throwError(
error(
`Phone number must be a string consisting only of numbers, received '${value}'.`

@@ -400,175 +537,1 @@ );

}
function fieldMustNotBeSet(
key: string,
fieldName: string,
value: unknown,
why: string
) {
if (Array.isArray(value)) {
if (value.length > 0) {
return throwError(
`'${fieldName}' must not be set in key '${key}' ${why}.`
);
}
return true;
}
if (
typeof value === "undefined" ||
value === null ||
value === "" ||
value === false
) {
return true;
}
return throwError(`'${fieldName}' must not be set in key '${key}' ${why}.`);
}
function fieldMustBeSet(
key: string,
fieldName: string,
value: unknown,
why: string
) {
if (Array.isArray(value)) {
if (value.length <= 0) {
return throwError(
`'${fieldName}' must be set in key '${key}' ${why}.`
);
}
}
if (
typeof value === "undefined" ||
value === null ||
value === "" ||
value === false
) {
return throwError(`'${fieldName}' must be set in key '${key}' ${why}.`);
}
return true;
}
/**
* Recursively traverse an object's keys and ensure each is okay
*/
export function validateSchema(schema: Schema) {
// Loop through each key and validate its schema
for (const [k, keySchema] of Object.entries(schema.keys)) {
validateKeySchema(k, keySchema);
}
return true;
}
/**
* Function we can check a keys configuration object
*/
function validateKeySchema(key: string, config: KeyConfiguration) {
const { schema, validation } = config;
// Lets set the key so we don't have to keep doing it
const ensureFieldNotSet = (
fieldName: string,
value: unknown,
why: string
) => fieldMustNotBeSet(key, fieldName, value, why);
const ensureFieldSet = (fieldName: string, value: unknown, why: string) =>
fieldMustBeSet(key, fieldName, value, why);
ensureFieldSet("schema.type", schema?.type, "- field is required");
if (!schema.is_optional) {
const reason = "if schema.is_optional is false";
ensureFieldNotSet(
"validation.ensure_empty_if_any_present",
validation?.ensure_empty_if_any_present,
reason
);
ensureFieldNotSet(
"validation.ensure_empty_if_all_present",
validation?.ensure_empty_if_all_present,
reason
);
ensureFieldNotSet(
"validation.ensure_empty_if_all_empty",
validation?.ensure_empty_if_all_empty,
reason
);
ensureFieldNotSet(
"validation.ensure_empty_if_any_empty",
validation?.ensure_empty_if_any_empty,
reason
);
ensureFieldNotSet(
"validation.ensure_empty_if_none_empty",
validation?.ensure_empty_if_none_empty,
reason
);
}
if (schema.type === "string") {
const reason = "if schema.type is string";
ensureFieldNotSet("validation.min", validation?.min, reason);
ensureFieldNotSet("validation.max", validation?.max, reason);
}
if (schema.type === "number") {
const reason = "if schema.type is number";
ensureFieldNotSet("validation.is_email", validation?.is_email, reason);
if (typeof validation?.is_phone !== "undefined") {
if (typeof validation.is_phone === "object") {
ensureFieldNotSet(
"validation.is_phone.include_country_code",
validation?.is_phone?.include_country_code,
reason
);
}
}
ensureFieldNotSet("validation.is_phone", validation?.is_phone, reason);
}
if (schema.type === "object") {
// Check recursive fields inside the nested Schema
if (schema.object_schema) {
validateSchema(schema.object_schema);
}
const reason = "if schema.type is number";
ensureFieldNotSet("schema.enum", schema?.enum, reason);
ensureFieldNotSet("validation.min", validation?.min, reason);
ensureFieldNotSet("validation.max", validation?.max, reason);
ensureFieldNotSet("validation.equals", validation?.equals, reason);
ensureFieldNotSet("validation.is_email", validation?.is_email, reason);
ensureFieldNotSet("validation.is_phone", validation?.is_phone, reason);
ensureFieldSet("schema.object_schema", schema?.object_schema, reason);
}
if (schema.type === "boolean") {
const reason = "if schema.type is boolean";
ensureFieldNotSet("schema.enum", schema?.enum, reason);
ensureFieldNotSet("validation.min", validation?.min, reason);
ensureFieldNotSet("validation.max", validation?.max, reason);
ensureFieldNotSet("validation.is_email", validation?.is_email, reason);
ensureFieldNotSet("validation.is_phone", validation?.is_phone, reason);
}
// Let's make sure the default value obeys our rules
if (schema.default_value) {
validateKeyConfiguration(key, schema.default_value, config, {});
}
// length, min_length, and max_length only available on strings and arrays
if (schema.type !== "string" && !schema.is_array) {
const reason = "if type isn't string and is not an array";
ensureFieldNotSet("validation.length", validation?.length, reason);
ensureFieldNotSet(
"validation.min_length",
validation?.min_length,
reason
);
ensureFieldNotSet(
"validation.max_length",
validation?.max_length,
reason
);
}
return true;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc