Get list of unique entity keys defined on nested schema - javascript

I'm trying to obtain a list of all the keys defined on a normalizr schema, & have written a function that does what I need for a simple schema:
export const collectAttributes = target => {
const schemaKeys = []
if (target.hasOwnProperty('_key')) {
schemaKeys.push(target._key)
}
const definitions = Object.keys(target).filter(key => key[0] !== '_')
definitions.forEach(key => {
collectAttributes(target[key]).forEach(attribute => schemaKeys.push(attribute))
})
return schemaKeys
}
However, this fails on a nested schema definition with a Maximum call stack size exceeded error, as illustrated with this test case:
describe('collectAttributes', () => {
it('should collect all unique collections defined on a recursive schema', () => {
const nodeSchema = new schema.Entity('nodes', {})
const nodeListSchema = new schema.Array(nodeSchema)
nodeSchema.define({ children: nodeListSchema })
expect(collectAttributes(nodeSchema)).toEqual(['nodes'])
})
})
If anyone has ideas on how to collect the already visited schemas such that the recursive function halts, they would be much appreciated.

I figured it out in the end - solution below:
export const isSchema = target => {
if (Array.isArray(target)) {
return target.length ? isSchema(target[0]) : false
} else {
return target.hasOwnProperty('schema') || target instanceof schema.Entity || target instanceof schema.Array
}
}
const recursiveCollect = (target, visited = []) => {
const entities = []
const visitedSchemas = [...visited]
if (isSchema(target)) {
entities.push(target.key)
visitedSchemas.push(target)
}
if (Array.isArray(target) || target instanceof schema.Array) {
/*
* If the current target is an ArraySchema, call `recursiveCollect`
* on the underlying entity schema
*/
return recursiveCollect(target.schema, visitedSchemas)
}
Object.keys(target.schema).filter(x => x[0] !== '_').forEach(definition => {
const childSchema= target.schema[definition]
const alreadyVisited = visitedSchemas.includes(childSchema)
if (isSchema(childSchema) && !alreadyVisited) {
/* Only call `recursiveCollect` on the child schema if it hasn't
* already been encountered
*/
const result = recursiveCollect(childSchema, visitedSchemas)
if (result.entities) {
result.entities.forEach(x => entities.push(x))
}
if (result.visitedSchemas) {
result.visitedSchemas.forEach(x => visitedSchemas.push(x))
}
}
})
return { entities, visitedSchemas }
}
export const collectAttributes = target => {
const { entities } = recursiveCollect(target)
return entities
}

Related

how to export a variable from a constant in javascript

I'm currently converting code from Common JS to ES6 and ran into an issue I'm unable to exprort columnset and values variables. Any tips or tricks for this?
export const multipleColumnSet = (object) => {
if (typeof object !== 'object') {
throw new Error('Invalid input');
}
const keys = Object.keys(object);
const values = Object.values(object);
columnSet = keys.map(key => `${key} = ?`).join(', ');
return {
columnSet,
values
}
};
However When I try to use it says undefined.
import { multipleColumnSet } from '../utils/common.utils.js';
class UserModel {
tableName = 'user'
findUser = async (params) => {
const { columnSet, values } = multipleColumnSet(params)
const sql = `SELECT * FROM ${this.tableName} WHERE ${columnSet}`
const result = await query(sql, [...values])
return result[0]
}
}
I have added the Common JS I've converted from.
exports.getPlaceholderStringForArray = (arr) => {
if (!Array.isArray(arr)) {
throw new Error('Invalid input');
}
// if is array, we'll clone the arr
// and fill the new array with placeholders
const placeholders = [...arr];
return placeholders.fill('?').join(', ').trim();
}
exports.multipleColumnSet = (object) => {
if (typeof object !== 'object') {
throw new Error('Invalid input');
}
const keys = Object.keys(object);
const values = Object.values(object);
columnSet = keys.map(key => `${key} = ?`).join(', ');
return {
columnSet,
values
}
}

How do I pass index number to a react class?

Need help passing data "locationpos"= index of my Locations[] from function to class. I'm very new to React and I'm not sure what I'm doing wrong.
ERROR
Failed to compile
./src/components/data.js
Line 20:30: 'locationpos' is not defined no-undef
Search for the keywords to learn more about each error.
This error occurred during the build time and cannot be dismissed.
class Data {
constructor(locationpos) {
this.locationpos=locationpos;
this.updateData();
}
getTimes(date = null) {
date = date === null ? moment().format('DD/MM/YYYY') : date;
var data = this.getData();
return data ? data[date] : [];
}
getSpeadsheetUrl() {
return config.myData[locationpos];
}
function Daily({ locationProps = 1, root }) {
const context = useContext(ThemeContext);
const localization = useCallback(() => {
if (root && cookies.get("location") !== undefined) {
return cookies.get("location");
}
return locationProps;
}, [locationProps, root]);
const [locationState] = useState(localization());
const handleClick = event => {
window.focus();
notification.close(event.target.tag);
};
const openNav = () => {
document.getElementById("sidenav").style.width = "100%";
};
const closeNav = e => {
e.preventDefault();
document.getElementById("sidenav").style.width = "0";
};
// eslint-disable-next-line
const locationpos = locations.indexOf(locations[locationState]);
const _data = useRef(new Data(locationpos));
const getTimes = () => _data.current.getTimes();
Inside your data class, you need to use the instance variable as this.locationPos
getSpeadsheetUrl() {
return config.myData[this.locationpos];
}

How to prevent a race condition in javascript?

I am working on a Resource Manager and some jobs must be atomic, because there may be race condition.
I see a solution is provided in a similar post
Javascript semaphore / test-and-set / lock?
which says implement a basic integer semaphore, just add the variable into the DOM and lock/unlock it and make sure your functions keep checking it, else timeout them
Can someone help me how can I implement it for below code ? and How can I add a variable to DOM ?
In the code below retrievePartners methods returns partners based on what user asked in capabilities object, and retrievePartners method may have been called same time from another user, asking for same partners (browsers.) So this method should be atomic, means should deal only with one capabilities at a same time.
async retrievePartners (capabilities) {
const appropriatePartners = { chrome: [], firefox: [], safari: [], ie: [] }
const partners = await this.getAllPartners()
allTypeNumber = 0
// first check if there is available appropriate Partners
Object.keys(capabilities.type).forEach(key => {
let typeNumber = parseInt(capabilities.type[key])
allTypeNumber = allTypeNumber + typeNumber
for (let i = 0; i < typeNumber; i++) {
partners.forEach((partner, i) => {
if (
key === partner.value.type &&
partner.value.isAvailable &&
appropriatePartners[key].length < typeNumber
) {
appropriatePartners[key].push(partner)
}
})
if (appropriatePartners[key].length < typeNumber) {
throw new Error(
'Sorry there are no appropriate Partners for this session'
)
}
}
})
if (allTypeNumber === 0) {
throw new Error('Please mention at least 1 type of browser !')
} else {
Object.keys(appropriatePartners).forEach(key => {
appropriatePartners[key].forEach(partner => {
this.instructorPeer.set('/partners/' + partner.id + '/states/', {
isAvailable: false
})
})
})
return appropriatePartners
}
}
getAllPartners method
async getAllPartners (capabilities) {
const partners = []
const paths = await this.instructorPeer.get({
path: { startsWith: '/partners/' }
})
paths.forEach((path, i) => {
if (path.fetchOnly) {
let obj = {}
obj.value = path.value
obj.id = path.path.split('/partners/')[1]
obj.value.isAvailable = paths[i + 1].value.isAvailable
partners.push(obj)
}
})
return partners
}
Here is how I call the method
async function startTest () {
const capabilities = {
type: {
chrome: 1
}
}
test('Example test', async t => {
try {
session = await workout.createSession(capabilities)
const partners = session.partners
const partner = partners.chrome[0]
...

How to log the program flow of a very deep and complex functional JavaScript code?

I'm trying to understand the flow of the functions in this reselect library. I currently use console.log to log the output. Still, it is hard to understand the flow. Make me think how complex functional programming is!!!
How can I intercept the calls and log the function name and the parameters to the console with ES6 decorator or Proxy or any other language features?
function defaultEqualityCheck(a, b) {
return a === b
}
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
if (prev === null || next === null || prev.length !== next.length) {
return false
}
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
const length = prev.length
for (let i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false
}
}
return true
}
function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
console.log("Entering defaultMemoize");
console.log("###INPUT### defaultMemoize argument func type: " + typeof func);
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
}
lastArgs = arguments
return lastResult
}
}
function getDependencies(funcs) {
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs
if (!dependencies.every(dep => typeof dep === 'function')) {
const dependencyTypes = dependencies.map(
dep => typeof dep
).join(', ')
throw new Error(
'Selector creators expect all input-selectors to be functions, ' +
`instead received the following types: [${dependencyTypes}]`
)
}
return dependencies
}
function createSelectorCreator(memoize, ...memoizeOptions) {
console.log("Entering createSelectorCreator");
console.log("#INPUT# argument memoize name: " + memoize.name);
console.log("#INPUT# argument memoize options: ");
console.log(memoizeOptions);
return (...funcs) => {
let recomputations = 0
const resultFunc = funcs.pop()
const dependencies = getDependencies(funcs)
console.log("##INPUT## argument funcs: ");
console.log(resultFunc);
const memoizedResultFunc = memoize(
function () {
recomputations++
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments)
},
...memoizeOptions
)
console.log("memoizedResultFunc: " + typeof memoizedResultFunc);
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
const selector = defaultMemoize(function () {
const params = []
const length = dependencies.length
if (arguments != null)
{
console.log("***INPUT*** arguments: ");
console.log(arguments);
}
for (let i = 0; i < length; i++) {
// apply arguments instead of spreading and mutate a local list of params for performance.
params.push(dependencies[i].apply(null, arguments))
}
// apply arguments instead of spreading for performance.
return memoizedResultFunc.apply(null, params)
})
selector.resultFunc = resultFunc
selector.recomputations = () => recomputations
selector.resetRecomputations = () => recomputations = 0
return selector
}
}
const createSelector = createSelectorCreator(defaultMemoize)
function createStructuredSelector(selectors, selectorCreator = createSelector) {
if (typeof selectors !== 'object') {
throw new Error(
'createStructuredSelector expects first argument to be an object ' +
`where each property is a selector, instead received a ${typeof selectors}`
)
}
const objectKeys = Object.keys(selectors)
return selectorCreator(
objectKeys.map(key => selectors[key]),
(...values) => {
return values.reduce((composition, value, index) => {
composition[objectKeys[index]] = value
return composition
}, {})
}
)
}
const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
const taxSelector = createSelector(
subtotalSelector,
taxPercentSelector,
(subtotal, taxPercent) => subtotal * (taxPercent / 100)
)
const totalSelector = createSelector(
subtotalSelector,
taxSelector,
(subtotal, tax) => ({ total: subtotal + tax })
)
let exampleState = {
shop: {
taxPercent: 8,
items: [
{ name: 'apple', value: 1.20 },
{ name: 'orange', value: 0.95 },
]
}
}
console.log(subtotalSelector(exampleState))// 2.15
//console.log(taxSelector(exampleState))// 0.172
//console.log(totalSelector(exampleState))// { total: 2.322 }

Create elements dynamically with CycleJS

I'm trying to learn some CycleJS and I ended up not knowing what to do exactly with this. The goal is to create inputs via configuration, instead of declaring them manually. The problem is I'm only getting rendered the last of the inputs from the array, instead of both. I'm assume the error is in view$ and how I'm dealing with the stream.My naive implementation is this:
Main.js
const sliderGunProps$ = xs.of({
value: 30,
id: 'gun'
});
const sliderCannonProps$ = xs.of({
value: 70,
id: 'cannon'
});
const propsConfig = [sliderGunProps$, sliderCannonProps$];
function view$(state$) {
return xs.fromArray(state$)
.map(state => {
return xs.combine(state.sliderVDom$, state.values)
.map(([sliderVDom, value]) =>
div([sliderVDom, h1(value)])
);
})
.flatten();
}
function model(actions$) {
return actions$.map((action) => {
const sliderVDom$ = action.DOM;
const sliderValue$ = action.value;
const values$ = sliderValue$.map(val => val);
return {
sliderVDom$: sliderVDom$,
values: values$
};
});
}
function intent(sources) {
return propsConfig.map(prop$ => Slider({
DOM: sources.DOM,
props$: prop$
}));
}
function main(sources) {
const actions$ = intent(sources);
const state$ = model(actions$);
const vdom$ = view$(state$);
const sink = {
DOM: vdom$
};
return sink;
}
Thanks!
I ended up figuring out how to solve it. The point was that I was not understanding how view$ handle the streams. The proper code:
function total(values) {
return xs.combine(...values)
.map(val => val.reduce((acc, x) => acc + x));
}
function view$(state$) {
const DOMElements = state$.map(slider => slider.sliderVDom$);
const values = state$.map(slider => slider.values);
const totalValue$ = total(values);
return xs.combine(totalValue$, ...DOMElements)
.map(([totalValue, ...elements]) => (
div([
...elements,
h1(totalValue)
])
));
}
function model(actions$) {
return actions$.map((action) => ({
sliderVDom$: action.DOM,
values: action.value.map(val => val)
}));
}
function intent(sources) {
return propsConfig.map(prop$ => Slider({
DOM: sources.DOM,
props$: prop$
}));
}

Categories