I'm trying to build an index on using flexsearch and nodejs and store it on a local disk as it take quite a bit of time to build. The export seems to work, but when trying to import the file again with a new document index I get this error:
TypeError: Cannot read property 'import' of undefined
at Q.t.import (/opt/hermetic/hermetic/server/node_modules/flexsearch/dist/flexsearch.bundle.js:33:330)
at Object.retrieveIndex (/opt/hermetic/hermetic/server/build/search.js:86:25)
at Object.search (/opt/hermetic/hermetic/server/build/search.js:96:32)
at init (/opt/hermetic/hermetic/server/build/server.js:270:27)
I'm running nodejs version 14 and flexsearch version 0.7.21. Below is the code I am using:
import fs from 'fs';
import Flexsearch from 'flexsearch';
const createIndex = async () => {
const { Document } = Flexsearch;
const index = new Document({
document: {
id: 'id',
tag: 'tag',
store: true,
index: [
'record:a',
'record:b',
'tag',
],
},
});
index.add({ id: 0, tag: 'category1', record: { a: '1 aaa', b: '0 bbb' } });
index.add({ id: 1, tag: 'category1', record: { a: '1 aaa', b: '1 bbb' } });
index.add({ id: 2, tag: 'category2', record: { a: '2 aaa', b: '2 bbb' } });
index.add({ id: 3, tag: 'category2', record: { a: '2 aaa', b: '3 bbb' } });
console.log('search', index.search('aaa'));
await index.export((key, data) => fs.writeFile(`./search_index/${key}`, data, err => !!err && console.log(err)));
return true;
}
const retrieveIndex = async () => {
const { Document } = Flexsearch;
const index = new Document({
document: {
id: 'id',
tag: 'tag',
store: true,
index: [
'record:a',
'record:b',
'tag',
],
},
});
const keys = fs
.readdirSync('./search_index', { withFileTypes: true }, err => !!err && console.log(err))
.filter(item => !item.isDirectory())
.map(item => item.name);
for (let i = 0, key; i < keys.length; i += 1) {
key = keys[i];
const data = fs.readFileSync(`./search_index/${key}`, 'utf8');
index.import(key, data);
}
return index;
}
await createIndex();
const index = await retrieveIndex();
console.log('cached search', index.search('aaa'));
I was trying to find a way to export the index properly too, originally trying to put everything into one file. While it worked, I didn't really like the solution.
Which brought me to your SO question, I've checked your code and managed to find out why you get that error.
Basically the export is a sync operation, while you also (randomly) use async. In order to avoid the issue, you need to remove all async code and only use sync node.fs operations. For my solution, I also only once created the Document store, to then just fill it via retrieveIndex() rather than creating new Document() per function.
I also added a .json extension to guarantee that node.fs reads the file properly and for sanity purposes - afterall it's json stored.
So thanks for giving me the idea to store each key as file #Jamie Nicholls 🤝
import fs from 'fs';
import { Document } from 'flexsearch'
const searchIndexPath = '/Users/user/Documents/linked/search-index/'
let index = new Document({
document: {
id: 'date',
index: ['content']
},
tokenize: 'forward'
})
const createIndex = () => {
index.add({ date: "2021-11-01", content: 'asdf asdf asd asd asd asd' })
index.add({ date: "2021-11-02", content: 'fobar 334kkk' })
index.add({ date: "2021-11-04", content: 'fobar 234 sffgfd' })
index.export(
(key, data) => fs.writeFileSync(`${searchIndexPath}${key}.json`, data !== undefined ? data : '')
)
}
createIndex()
const retrieveIndex = () => {
const keys = fs
.readdirSync(searchIndexPath, { withFileTypes: true })
.filter(item => !item.isDirectory())
.map(item => item.name.slice(0, -5))
for (let i = 0, key; i < keys.length; i += 1) {
key = keys[i]
const data = fs.readFileSync(`${searchIndexPath}${key}.json`, 'utf8')
index.import(key, data ?? null)
}
}
const searchStuff = () => {
retrieveIndex()
console.log('cached search', index.search('fo'))
}
searchStuff()
After looking into this further, the feature is not currently available for document type searches. See this issue in github for more information
Related
i want to do the following: get a random name with fetch from this website https://swapi.dev/api/people/, which i did and i can see it in my html page then i want also to get a random planet, here i need to access the homeworld key, and to return the link, before returning the link i formatted to get a random url and from this one i also have to show the name of the planet on my page. The first fetch works fine, at least i think but the 3rd .then() is not working or at least i don't know how to access the information from the homeworld url. This is my first time trying fetch() and it will be nice if you guys can help me telling where i did wrong in code and maybe different solutions but not so complicated :D tnks
let randomNumber = Math.floor(Math.random()*9)
const fetchPromise = fetch("https://swapi.dev/api/people/");
let test
let test2
let planets = document.querySelector('#age')
fetchPromise
.then((response) => {
if (!response.ok) {
throw new Error(`Http error: ${response.status}`);
}
return response.json();
})
.then((json) => {
console.log(json.results[randomNumber].name)
showRandomUserData(json)
test = json.results[0].homeworld
test = test.slice(0, -2)
// console.log(test + randomNumber + "/");
// console.log(test + "/" + randomNumber + "/");
test = test + randomNumber + "/";
return fetch(test)
// return fetch("https://swapi.dev/api/planets/2/");
})
.then(response => response.json()).then(json =>
{ test2=json.name
console.log(test2);
planets.innerHTML = test2
})
showRandomUserData = (randomUser) => {
document.querySelector("#name").innerHTML =
randomUser.results[randomNumber].name;
}
Solved
Here's a simple solution that uses fetch() to grab data from both those URLs and then insert all the people and the one planet that is returned into your web page:
function myFetch(...args) {
return fetch(...args).then(response => {
if (!response.ok) {
throw new Error(`fetch failed with status ${response.status}`);
}
return response.json();
});
}
Promise.all([
myFetch("https://swapi.dev/api/people/"),
myFetch("https://swapi.dev/api/planets/2/")
]).then(([people, planet]) => {
const peopleDiv = document.getElementById("people");
let peopleHTML = "";
for (let p of people.results) {
peopleHTML += `<div>${p.name}</div>`;
}
peopleDiv.innerHTML = peopleHTML;
const planetDiv = document.getElementById("planets");
let planetHTML = `<div>${planet.name}</div>`;
planetDiv.innerHTML = planetHTML;
}).catch(err => {
console.log(err);
});
<div id="people"></div>
<hr>
<div id="planets"></div>
As for using the results, the people URL returns a structure that looks like this:
{
count: 82,
next: 'https://swapi.dev/api/people/?page=2',
previous: null,
results: [
{
name: 'Luke Skywalker',
height: '172',
mass: '77',
hair_color: 'blond',
skin_color: 'fair',
eye_color: 'blue',
birth_year: '19BBY',
gender: 'male',
homeworld: 'https://swapi.dev/api/planets/1/',
films: [Array],
species: [],
vehicles: [Array],
starships: [Array],
created: '2014-12-09T13:50:51.644000Z',
edited: '2014-12-20T21:17:56.891000Z',
url: 'https://swapi.dev/api/people/1/'
},
{
name: 'C-3PO',
height: '167',
mass: '75',
hair_color: 'n/a',
skin_color: 'gold',
eye_color: 'yellow',
birth_year: '112BBY',
gender: 'n/a',
homeworld: 'https://swapi.dev/api/planets/1/',
films: [Array],
species: [Array],
vehicles: [],
starships: [],
created: '2014-12-10T15:10:51.357000Z',
edited: '2014-12-20T21:17:50.309000Z',
url: 'https://swapi.dev/api/people/2/'
}
}
So, you have people.results which is an array and you can access people.results[n] to get an item from that array. That item will be an object which has properties like .name, .height, etc...
The specific planet URL you show returns a single planet object like this:
{
name: 'Alderaan',
rotation_period: '24',
orbital_period: '364',
diameter: '12500',
climate: 'temperate',
gravity: '1 standard',
terrain: 'grasslands, mountains',
surface_water: '40',
population: '2000000000',
residents: [
'https://swapi.dev/api/people/5/',
'https://swapi.dev/api/people/68/',
'https://swapi.dev/api/people/81/'
],
films: [
'https://swapi.dev/api/films/1/',
'https://swapi.dev/api/films/6/'
],
created: '2014-12-10T11:35:48.479000Z',
edited: '2014-12-20T20:58:18.420000Z',
url: 'https://swapi.dev/api/planets/2/'
}
So, you access properties on that object as in planet.name.
Notice that the people results are paged. There are 82 total results, but only 10 come in this first result. The rest come with results for other pages such as https://swapi.dev/api/people/?page=2.
Similar to this answer but using async/await to avoid callback hell. If you can, try using this approach. Why?
Excellent recommendation in that answer by jfriend00 to use Promise.all instead of separate fetch calls, as that enables fetching to happen in parallel. To know more.
sandbox to test and try
const fetchData = async (...args) => {
try {
const response = await fetch(...args);
return response.json();
} catch (err) {
throw new Error(`fetch failed with status ${err?.message}`);
}
};
const updateDOM = (people, planet) => {
document.getElementById("people").innerHTML =
people.results.reduce((s, p) => s + `<div>${p.name}</div>`, "");
document.getElementById("planets").innerHTML = `<div>${planet.name}</div>`;
};
const populateData = async () => {
try {
const [people, planet] = await Promise.all([
fetchData("https://swapi.dev/api/people/"),
fetchData("https://swapi.dev/api/planets/2/"),
]);
// do stuff with 'people' or 'planet'
// example, get
// const firstPersonsHomeworld = people.results[0].homeworld;
// console.log(firstPersonsHomeworld);
// or
// const planetName = planet.name;
// console.log(planetName);
updateDOM(people, planet);
} catch (err) {
// errorHandler(err);
console.error(err);
}
};
// start app
populateData();
So I am importing the following things from a mongoDB database, on a node.js app I'm building, the block sections I'm going to post are all inside an asynchronous function, I'll try to post a console log for each of the objects I'm working with to see if anyone can explain this to me, but basically I have 2 array iterations, that build an object to be sent to the front-end as an array of objects, however the code sends the first object that matches my iterations and after that it returns a blank object, meaning that if I try to add more than object to the expected output it will only return one of the first element that the expression finds and it does not check for the rest of them.
Thanks in advance to anyone that can help me with this!
The objects I'm working with:
const about = await About.find(); //always has one document only so no conflict here
const allSkills = await Skill.find(); //grabs all the skills in the database
const skills = about[0].skillBag.filter((name) => name); //selects all the skills the user saved
Console logs:
console.log(allSkills) -> [
{
_id: 5fa59424f3d6532ae63fad26,
category: 'Web Development',
name: 'wordpress',
path: 'wordpress.svg',
__v: 0
},
{
_id: 5fa5979c9e84032b92d3e9ee,
category: 'DevOps',
name: 'nginx',
path: 'nginx.svg',
__v: 0
},
{
_id: 5fa597b79e84032b92d3e9ef,
category: 'Web Development',
name: 'javascript',
path: 'javascript.svg',
__v: 0
},
.
.
.
]
console.log(skills) -> ["javascript","nginx"]
Since I've been trying to debug this for a while now I've built even separated the expressions to try to iterate on one array at a time:
//This lists all the skills with the category 'DevOps'
//and builds an object from them with the output {name: 'nginx', path: 'nginx.svg'}
const allDevOpsSkills = allSkills
.filter((e) => e.category == "DevOps")
.map((n) => ({ name: n.name, path: n.path }));
// Does the same for the category 'Web Development'
const allWebDevSkills = allSkills
.filter((e) => e.category == "Web Development")
.map((n) => ({ name: n.name, path: n.path }));
These 2 expressions always return an array with the objects .map() builds
Then I grab these 2 expressions and do:
const userDevOpsSkills = allDevOpsSkills.filter((n, i) => n.name == `${skills[i]}`);
// returns [ { name: 'nginx', path: 'nginx.svg' } ]
const userWebDevSkills = allWebDevSkills.filter((n, i) => n.name == `${skills[i]}`);
// returns [] and not [ { name: 'javascript', path: 'javascript.svg' } ]
I know I can do this in one expression instead of 2, that is the final version of my expressions however if I execute my code like the example above or if I run it like the example bellow the output is always the same
//Same expressions as above but in a more short way
const userWebDevSkills = allSkills
.filter((e) => e.category == "Web Development")
.map((n) => ({ name: n.name, path: n.path }))
.filter((n, i) => n.name == `${skills[i]}`);
const userDevOpsSkills = allSkills
.filter((e) => e.category == "DevOps")
.map((n) => ({ name: n.name, path: n.path }))
.filter((n, i) => n.name == `${skills[i]}`);
Extra info:
-The arguments that my current skills constant on the first code block returns are all stored in the database, but so that the post does not get any longer I redacted it, I want to do multiple checks on the same array.
I'm sorry if I can't explain myself any better, english is not my first language and I haven't been coding for that long to understand what exactly is going wrong in here.
Again thanks in advance to anyone that can help me with this!
EDIT
Bellow is the code without been split into individual code blocks
const express = require("express");
const About = require("../models/aboutSchema");
const Skill = require("../models/skillSchema");
const { logger } = require("../controllers/logs");
const router = express.Router();
router.get("/", async (req, res) => {
try {
const path = req.originalUrl;
const about = await About.find();
const allSkills = await Skill.find();
const skills = about[0].skillBag.filter((name) => name);
const userWebDevSkills = allSkills
.filter((e) => e.category == "Web Development")
.map((n) => ({ name: n.name, path: n.path }))
.filter((n, i) => n.name == `${skills[i]}`);
const userDevOpsSkills = allSkills
.filter((e) => e.category == "DevOps")
.map((n) => ({ name: n.name, path: n.path }))
.filter((n, i) => n.name == `${skills[i]}`);
console.log(userWebDevSkills);
res.status(200).render("home.ejs", {
title: "Homepage | FilipeDev",
user: req.user,
path,
about: about[0],
webDev: userWebDevSkills,
devOps: userDevOpsSkills,
});
} catch (e) {
console.log(e);
logger.error(e);
}
});
module.exports = router;
If I understand your question, you are expecting:
const userWebDevSkills = allWebDevSkills.filter((n, i) => n.name == `${skills[i]}`);
to give you:
[ { name: 'javascript', path: 'javascript.svg' } ]
When I run this code:
const skills = ["javascript", "nginx"];
const allSkills = [{
_id: 'fa59424f3d6532ae63fad26',
category: 'Web Development',
name: 'wordpress',
path: 'wordpress.svg',
__v: 0
},
{
_id: 'fa5979c9e84032b92d3e9ee',
category: 'DevOps',
name: 'nginx',
path: 'nginx.svg',
__v: 0
},
{
_id: 'fa597b79e84032b92d3e9ef',
category: 'Web Development',
name: 'javascript',
path: 'javascript.svg',
__v: 0
},
];
const allDevOpsSkills = allSkills
.filter((e) => e.category == "DevOps")
.map((n) => ({ name: n.name, path: n.path }));
const allWebDevSkills = allSkills
.filter((e) => e.category == "Web Development")
.map((n) => ({ name: n.name, path: n.path }));
const userWebDevSkills = allWebDevSkills.filter((n, i) => n.name == `${skills[i]}`);
I do, indeed, get an empty array for userWebDevSkills and the issue is that allWebDevSkills ends up being this:
[
{ name: 'wordpress', path: 'wordpress.svg' },
{ name: 'javascript', path: 'javascript.svg' }
]
And, if skills is this:
["javascript","nginx"]
Then, this condition:
n.name == `${skills[i]}`
is never true so you never keep any items from the allWebDevSkills.filter() operation and thus end up with an empty array. I don't know exactly what you're attempting to do with the
n.name == `${skills[i]}`
comparison so I'm' not sure exactly what to suggest to fix it, but this is demanding an exact position in the array match between allWebDevSkills and skills for the .name property and that doesn't happen. The javascript name property is in position 1 of allWebDevSkills, but in position 0 for the skills array and thus will never match your filter condition.
Also, code like this:
n.name == `${skills[i]}`
can just be this:
n.name === skills[i]
There is no need to put a single string into a ${} template by itself and === is favored when comparing strings (and not wanting to enable type conversion).
If what you're trying to do with the .filter() is to remove any items that don't have a .name property that is ANYWHERE in the skills array, then I would suggest this:
const skills = ["javascript", "nginx"];
const skillsSet = new Set(skills);
const allSkills = [{
_id: 'fa59424f3d6532ae63fad26',
category: 'Web Development',
name: 'wordpress',
path: 'wordpress.svg',
__v: 0
},
{
_id: 'fa5979c9e84032b92d3e9ee',
category: 'DevOps',
name: 'nginx',
path: 'nginx.svg',
__v: 0
},
{
_id: 'fa597b79e84032b92d3e9ef',
category: 'Web Development',
name: 'javascript',
path: 'javascript.svg',
__v: 0
},
];
const allDevOpsSkills = allSkills
.filter((e) => e.category == "DevOps")
.map((n) => ({ name: n.name, path: n.path }));
const allWebDevSkills = allSkills
.filter((e) => e.category == "Web Development")
.map((n) => ({ name: n.name, path: n.path }));
const userWebDevSkills = allWebDevSkills.filter(n => skillsSet.has(n.name));
console.log(userWebDevSkills);
In my React application I have created a custom hook which returns a set of data wherever used.
It's used like const projects = useProjects();
It's an array of objects , we can assume it looks like this :
[{project_id : 123 , name : 'p1'} , {project_id : 1234 , name : 'p2'} ]
Now I need to enrich this data with data from an API. So i have to loop through projects and basically add a new field to each object, so the new array of objects will look like this :
[{project_id : 123 , name : 'p1' field3: 'api data'} , {project_id : 1234 , name : 'p2' , field3: 'api data1' } ]
How can I achieve this ?
What I did was loop through the projects data and directly added the field inside the loop. But I dont know if i should be mutating this data like that ? I was hoping to see if this is good practice or not.
There's a few ways you could solve this - it all depends on how you're getting the data back from the API. If you want to have that injected into the hook, you could do something like this -
const DEFAULT_PROJECTS = [
{ project_id : 123, name: 'p1' },
{ project_id : 1234, name: 'p2' },
];
const useProjects = (dataFromApi) => {
// Assuming that dataFromApi is what you got back from your API call,
// and it's a dictionary keyed on the project_id.
return useMemo(() => {
return DEFAULT_PROJECTS.map(proj => {
const extraData = dataFromApi.get(proj.project_id) || {};
return {
...proj,
...extraData,
};
});
}, [dataFromApi]);
};
The useMemo here isn't super helpful if the dataFromApi is always changing - it will just rebuild the returned object every time.
If you wanted to get the data as part of your hook, you do something like this -
import { useEffect, useMemo, useState } from 'react';
const DEFAULT_PROJECTS = [
{ project_id : 123, name: 'p1' },
{ project_id : 1234, name: 'p2' },
];
const useProjects = () => {
const [dataFromApi, setDataFromApi] = useState(null);
useEffect(() => {
if (!!dataFromApi) return;
// Simulate the data fetch
const fetchData = async () => {
return new Promise(resolve => {
setTimeout(() => {
const map = new Map();
map.set(123, {
field3: 'api data',
field4: 'other data',
});
setDataFromApi(map);
}, 2000);
});
};
fetchData();
}, [dataFromApi]);
return useMemo(() => {
let extraData = dataFromApi || new Map();
return DEFAULT_PROJECTS.map(proj => {
const extraFields = extraData.get(proj.project_id) || {};
return {
...proj,
...extraFields,
};
});
}, [dataFromApi]);
}
export default useProjects;
Here's a code sandbox that shows it in action.
I have two different mutations that I have saved the coordinates in the database createCoordinate, createCoordinateMultiple. In the first, I can easily record a single object and get the response with its relations.
But I can't get the mutation I'm trying to write for multiple operations correctly. Can you help me please ?
#Authorized('ADMIN')
#Mutation(() => Coordinate)
async createCoordinate(
#Arg('coordinate') input: CreateCoordinateInput
): Promise<Coordinate> {
const coordinate = new Coordinate();
coordinate.lat = input.lat;
coordinate.lng = input.lng;
const insects = input.insectList.map(id => ({ id })) as Insect[];
coordinate.insects = Promise.resolve(insects);
const response = await this.coordinateRepo.save(coordinate);
return response;
}
#Authorized('ADMIN')
#Mutation(() => [Coordinate])
async createCoordinateMultiple(
#Arg('coordinates', () => [CreateCoordinateInput]) coordinates: CreateCoordinateInput[]
): Promise<Coordinate[]> {
const response = await this.coordinateRepo.save(coordinates);
const data = response.map(coordinate => ({
id: coordinate.id,
lat: coordinate.lat,
lng: coordinate.lng,
title: coordinate.title,
description: coordinate.description,
sourceId: coordinate.sourceId,
image: coordinate.image,
insects: coordinate.insectList.map(id => ({
insectId: id,
coordinateId: coordinate.id,
})),
}));
console.log(data);
return response;
}
console.log(data) output;
[
{
id: 'c2f6b8f3-3d1a-4497-a492-ace7d58d217e',
lat: 36.7812360767751,
lng: 34.5795541012578,
title: 'B - konum',
description: 'B - bilgi',
sourceId: '7c94ce2d-e5a6-4a07-a272-369b3b34a61b',
image: '1.jpg',
insects: [ [Object], [Object] ]
},
{
id: 'b555f620-e9fd-4fdf-81ef-f355cb151969',
lat: 36.7819407011663,
lng: 34.580841561585,
title: 'D - konum',
description: 'D - bilgi',
sourceId: '7c94ce2d-e5a6-4a07-a272-369b3b34a61b',
image: '2.jpg',
insects: [ [Object], [Object] ]
}
]
https://github.com/typeorm/typeorm/issues/6713
I found a different solution. Although I don't know a clean use. At least it works for now while I wait for the answer from TypeORM developers.
I'm creating another entity for the coordinate and insect relationship.
I then save the coordinate assets. I match the coordinate ids from the returned answer and the insect ids that need to be written later into an array object.
#Authorized('ADMIN')
#Mutation(() => [Coordinate])
async createCoordinateMultiple(
#Arg('createCoordinateInputs', () => [CreateCoordinateInput]) inputs: CreateCoordinateInput[]
): Promise<Coordinate[]> {
const response = await this.coordinateRepo.save(inputs);
const coordinatesInsects = [];
response.forEach(coordinate => {
coordinate.insectList.forEach(insect => {
coordinatesInsects.push({ coordinateId: coordinate.id, insectId: insect })
})
});
await this.coordinateInsectRepo.save(coordinatesInsects);
return response;
}
I have a simple (I guess) mapping for an array inside a loop with multiple HTTP requests. In this case with VUE and axios.
In the first request I'm getting all the ProductGroups and then for each Distributor I make another call. I have 6 ProductGroups and 32 Distributors.
axios.get('/product-groups').then((res) => {
res.data.forEach(pg => {
this.distributor.forEach(d => {
axios.get('/distributors/'+ d.id + '/product-data/' + pg.id).then((res) => {
res.data.product_group = pg.name;
this.pushToDataIn(res.data) //how this should look?
});
})
})
})
A ProductGroup is:
{
id: 1,
name: 'PG1'
}
The result (res.data) is multiple objects coming in like:
{
comments: "Something"
distributor_id: 1
last_year: 250938.74
potential: 4549061.26
product_group: "PG1"
product_group_id: 107
}
Now, I want to push this data to an array based on product_group concatenating or adding some of the properties (in the same product group).
I'm not 100% sure about the end-result you're aiming at, but to at least resolve the data, you can start with the following. Mapping that data to the required format shouldn't be too hard then.
The trick is always the same: map data to a list of promises and then await Promise.all(...) of that.
// async fake product groups
// your: axios.get('/product-groups')
const getProductGroups = () => new Promise(resolve => {
setTimeout(resolve, 200, [
{ id: 1, name: 'PG1' },
{ id: 2, name: 'PG2' },
{ id: 3, name: 'PG3' },
{ id: 4, name: 'PG4' },
]);
});
// async fake distributor data
// axios.get('/distributors/'+ d.id + '/product-data/' + pg.id)
const getDistributorData = (dId, pgId) => new Promise(resolve => {
setTimeout(resolve, 200, [{
comments: "Something",
distributor_id: dId,
product_group_id: pgId,
}]);
});
(async () => {
// distributors are given
const distributors = [
{id: 1, name: 'Distributor 1'},
{id: 2, name: 'Distributor 2'},
{id: 3, name: 'Distributor 3'},
];
// await the product groups
const groups = await getProductGroups();
// map groups to promises
const groupPromises = groups.map(({id: pgId}) => {
// map distributors to promises
const distributorPromises = distributors.map(({id: dId}) => getDistributorData(dId, pgId));
// resolve
return Promise.all(distributorPromises).then(data => data.flat());
});
// await
const data = await Promise.all(groupPromises);
console.log(data);
})();