How to abort an update operation with beforeUpdate Hook Sequelize - node.js

How can i abort an update operation using beforeUpdate hook on sequelize and return a object as the result from a aborted update,
if i have something like:
User.beforeUpdate(function(user, options) {
if (user.name == "example_name") {
// abort operation here
// return object to the update caller
}
}

throw from before hooks does prevent the update
E.g.:
beforeUpdate: (integerName, options) => {
if (integerName.value === 5) {
throw new Error('beforeUpdate')
}
},
and throws on .update caller.
But remember from why sequelize beforeUpdate hook doesn't work? that the before only fires if the caller uses:
Model.update({}, {individualHooks: true})
which would be annoying to remember to pass every time.
The beforeValidate hook however fires even without individualHooks, so maybe that's the way to go.
The fact that throwing works for create is documented at: https://sequelize.org/master/manual/hooks.html#instance-hooks
User.beforeCreate(user => {
if (user.accessLevel > 10 && user.username !== "Boss") {
throw new Error("You can't grant this user an access level above 10!");
}
});
The following example will throw an error:
try {
await User.create({ username: 'Not a Boss', accessLevel: 20 });
} catch (error) {
console.log(error); // You can't grant this user an access level above 10!
};
The following example will be successful:
const user = await User.create({ username: 'Boss', accessLevel: 20 });
console.log(user); // user object with username 'Boss' and accessLevel of 20
Also mentioned at: https://github.com/sequelize/sequelize/issues/11298
Minimal runnable example:
main.js
#!/usr/bin/env node
const assert = require('assert')
const path = require('path')
const { DataTypes, Sequelize } = require('sequelize')
let sequelize
if (process.argv[2] === 'p') {
sequelize = new Sequelize('tmp', undefined, undefined, {
dialect: 'postgres',
host: '/var/run/postgresql',
})
} else {
sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.sqlite',
})
}
function assertEqual(rows, rowsExpect) {
assert.strictEqual(rows.length, rowsExpect.length)
for (let i = 0; i < rows.length; i++) {
let row = rows[i]
let rowExpect = rowsExpect[i]
for (let key in rowExpect) {
assert.strictEqual(row[key], rowExpect[key])
}
}
}
;(async () => {
const IntegerNames = sequelize.define('IntegerNames',
{
value: { type: DataTypes.INTEGER },
name: { type: DataTypes.STRING },
},
{
hooks: {
beforeCreate: (integerName, options) => {
if (integerName.value === 42) {
throw new Error('beforeCreate')
}
},
beforeValidate: (integerName, options) => {
if (integerName.value === 43) {
throw new Error('beforeValidate')
}
},
beforeUpdate: (integerName, options) => {
if (integerName.value === 5) {
throw new Error('beforeUpdate')
}
},
}
},
)
await IntegerNames.sync({ force: true })
async function reset() {
await sequelize.truncate({ cascade: true })
await IntegerNames.create({ value: 2, name: 'two' })
await IntegerNames.create({ value: 3, name: 'three' })
await IntegerNames.create({ value: 5, name: 'five' })
}
async function assertUnchanged() {
const rows = await IntegerNames.findAll()
assertEqual(rows, [
{ id: 1, value: 2, name: 'two', },
{ id: 2, value: 3, name: 'three', },
{ id: 3, value: 5, name: 'five', },
])
}
await reset()
let rows, exc
await assertUnchanged()
// beforeCreate
exc = undefined
try {
await IntegerNames.create({ value: 42, name: 'forty-two' })
} catch (e) {
exc = e
}
assert.strictEqual(exc.message, 'beforeCreate')
await assertUnchanged()
// beforeValidate
exc = undefined
try {
await IntegerNames.create({ value: 43, name: 'forty-three' })
} catch (e) {
exc = e
}
assert.strictEqual(exc.message, 'beforeValidate')
await assertUnchanged()
// beforeUpdate
exc = undefined
try {
await IntegerNames.update(
{ name: 'five hacked', },
{
where: { value: 5 },
individualHooks: true,
},
);
} catch (e) {
exc = e
}
assert.strictEqual(exc.message, 'beforeUpdate')
await assertUnchanged()
// using the beforeValidate
exc = undefined
try {
await IntegerNames.update(
{ value: 43, },
{
where: { value: 5 },
},
);
} catch (e) {
exc = e
}
assert.strictEqual(exc.message, 'beforeValidate')
await assertUnchanged()
})().finally(() => { return sequelize.close() })
package.json
{
"name": "tmp",
"private": true,
"version": "1.0.0",
"dependencies": {
"pg": "8.5.1",
"pg-hstore": "2.3.3",
"sequelize": "6.14.0",
"sql-formatter": "4.0.2",
"sqlite3": "5.0.2"
}
}
GitHub upstream. Tested on Ubuntu 21.10, PostgreSQL 13.5.

Related

How do I test a sequelize database updating function like this?

Hi I can I test a function like this? I cannot test it properly because it says that relation "facilities" does not exist, however I now it exists because I can add a new record.
I should I test this code:
export async function postMemberController(req, res) {
try {
if (req.decodedToken.user_id || req.decodedToken.id) {
const facility = await db.facility.findOne({
where: { id: req.body.facility_id },
raw: true,
});
if (!facility?.id) {
res.send(
"no facilities"
);
} else {
let member = await db.member.findOne({
where: { id_no: Number(req.body.id_no) },
raw: true,
});
const admin = await db.user.findOne({
where: { facility_id: req.body.facility_id },
raw: true,
});
const data = await db.sequelize.transaction(async t => {
if (member?.id_no) {
await db.member
.update(
{
...req.body,
facility_id: [...new Set([...member.facility_id, req.body.facility_id])],
},
{ where: { id_no: member.id_no } },
{ transaction: t }
)
.catch(e => console.log('error : creation MEMBER??? ', e));
const adminToken = await tokenCreator({ ...admin }, 3);
!req.body?.from &&
(await axios
.put(
`${process.env.CENTRAL_URL}/members`,
{
body: {
...req.body,
facility_id: [...new Set([...member.facility_id, req.body.facility_id])],
from: 'web',
},
where: { id: member.id },
},
{
headers: { authorization: 'Bearer ' + adminToken },
}
)
.catch(e => console.log('erorr ', e.response.data, e.config.url)));
} else {
const auth = adminFirebase.auth;
const firebaseToken = await auth.createCustomToken(generateUUID());
member = await db.member
.create(
{
...req.body,
firebase_token: firebaseToken,
facility_id: [req.body.facility_id],
creator: admin.user_id,
updater: admin.user_id,
},
{ transaction: t }
)
.catch(e => console.log('error : creation MEMBER??? ', e));
if (member) {
const card = await db.card.findOne({
where: { card_id: member.dataValues.card_id },
raw: true,
});
if (card) {
await db.card.update(
{ card_number: req.body.card_number },
{
where: {
card_id: member.dataValues ? member.dataValues.card_id : member.card_id,
},
},
{ transaction: t }
);
} else {
const newCard = await db.card.create(
{
card_number: req.body?.card_number,
card_id: member?.dataValues?.card_id,
creator: admin.user_id,
updater: admin.user_id,
facility_id: req.body.facility_id,
credits: 0,
},
{ transaction: t }
);
}
}
const adminToken = await tokenCreator({ ...admin }, 3);
!req.body?.from &&
(await axios
.post(
`${process.env.CENTRAL_URL}/members`,
{
...req.body,
id: member.dataValues.id,
card_id: member.dataValues ? member.dataValues.card_id : member.card_id,
facility_id: req.body.facility_id,
from: 'web',
},
{
headers: { authorization: 'Bearer ' + adminToken },
}
)
.catch(e => console.log('erorr ', e, e?.response?.data, e.config.url)));
!req.body?.from &&
(await axios
.put(
`${process.env.CENTRAL_URL}/cards`,
{
body: {
card_number: req.body.card_number.toString(),
facility_id: req.body.facility_id,
from: 'web',
},
where: {
card_id: member.dataValues ? member.dataValues.card_id : member.card_id,
},
},
{
headers: { authorization: 'Bearer ' + adminToken },
}
)
.catch(e => console.log('erorr ', e.response.data, e.config.url)));
}
return member;
});
delete data.dataValues?.password;
delete data?.password;
const memberToken = await tokenCreator({ ...data.dataValues }, 3);
return res
.status(200)
.json({ data, session_id: data.id_no || data.dataValues.id_no, token: memberToken });
}
} else {
return res.sendStatus(401);
}
} catch (error) {
res.status(500).send(error.message);
}
}
this is how I try to test it:
it('post member from web terminal', async () => {
try {
jest.mock('../db/models/index.js', () => {
const SequelizeMock = require('sequelize-mock');
const dbMock = new SequelizeMock();
const facility = dbMock.define('facilities', {
id: '6-30189',
})
const member = dbMock.define('members', {});
const card = dbMock.card('cards', {});
});
const req = {
decodedToken: { user_id: makeId() },
body: member,
};
const res = {
sendStatus(num) {},
status(num) {
return { json(payload) {}, send(payload) {} };
},
};
const request = await postMemberController(req, res);
delete member.from;
expect(request).toHaveBeenCalledWith({ ...member });
} catch (error) {
console.log('ERROR post member ', error);
throw new Error(error);
}
});
How do I write a test for this???? when I call postMemberController inside jest it seems that can't find the relations in the table, I tried to console.log db.facility object from jest test output, there is nothing, not even empty object or null or undefined. I don't understand why? How can I solve this??
Here is the object I am sending:
const card_number = 11111111111111;
const card_id = makeId();
const schedule = makeId();
const club = makeId();
const trainer = makeId();
const user = makeId();
const facility_id = '6-30189';
const member = {
from: 'central',
card_number: card_number,
id: makeId(),
first_name: 'Demo',
last_name: 'Demo',
id_no: card_number,
card_id,
home_phone: null,
urgent_phone: null,
email: null,
registered: true,
schedule,
club,
trainer,
birthplace: 'Gölbaşı/Ankara',
birthdate: new Date().toISOString(),
father_name: null,
mother_name: null,
gender: null,
profession: null,
address: null,
phone_number: null,
hes_code: null,
blood_type: null,
nationality: null,
profile_photo: null,
is_employee: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
firebase_token: null,
file: null,
session_ids: null,
facility_id,
following: null,
creator: user,
updater: user,
};

How to send reply to several users at the same time?

I have a doubt!
I'm creating a robot for Cryptocurrencies, and I need to send orders to each user's account, how would I do that? I'm using async/await but this way it sends one at a time, having a huge delay, I'm using MongoDB and NodeJS, below I'll leave my code to be more exemplified:
exports.index = async (req, res) => {
const { position, price, token, target, stop, emailUserScalpSend } = req.body
if (position && price && token && target && stop) {
console.time()
if (emailUserScalpSend === 'fila#gmail.com') {
const users = await User.find({ isAtived: true, apiKey: { $nin: ["", null] }, apiSecret: { $nin: ["", null] }, email: 'gustavofmariano#gmail.com' })
for await (const user of users) {
const config = await ConfigsUser.findOne({ user: user.id, porcentagem: { $nin: ["", null] }, alavancagem: { $nin: ["", null] } })
try {
if (config) await createOrderScalp(token, position, price, stop, target, user.apiKey, user.apiSecret, config.porcentagem, config.alavancagem)
else continue
} catch (error) {
console.log(error.message + ` ERRO AO ENVIAR SINAL DE SCALP ${user.name}`)
continue
}
}
} else {
var users = await User.find({ isAtived: true, apiKey: { $nin: ["", null] }, apiSecret: { $nin: ["", null] } })
var configs = await ConfigsUser.find({ user: users[0]._id, statusBot: true, porcentagem: { $nin: ["", null] }, alavancagem: { $nin: ["", null] } })
for await (const user of users) {
for await (const config of configs) {
try {
if (config.user.toString() === user.id.toString()) {
if (config) createOrderScalp(token + 'USDT', position, price, stop, target, user.apiKey, user.apiSecret, config.porcentagem, config.alavancagem)
else continue
}
} catch (error) {
console.log(error.message + ` ERRO AO ENVIAR SINAL PARA USUÁRIO ${user.name}`)
continue
}
}
}
console.timeEnd()
return res.status(201).json({ message: 'Sinal enviado com sucesso!' })
}
}
else return res.status(404).json({ message: 'Enviar todos os dados necessários!' })
}
How would you go about improving performance?
I thank everyone!

API Only sends 1 chunk of metadata when called

I have a problem with my API that sends metadata when called from my smart contract of website. Its NFT tokens and my database is postgres and API is node.js
The problem is when I mint 1 NFT metadata works perfect, but if I mint 2 or more it will only ever send 1 chunk of data? So only 1 NFT will mint properly and the rest with no data?
Do I need to set a loop function or delay? Does anyone have any experience with this?
Any help would be much appreciated.
Below is the code from the "controller" folder labeled "nft.js"
const models = require("../../models/index");
const path = require("path");
const fs = require("fs");
module.exports = {
create_nft: async (req, res, next) => {
try {
const dir = path.resolve(__dirname + `../../../data/traitsfinal.json`);
const readCards = fs.readFileSync(dir, "utf8");
const parsed = JSON.parse(readCards);
console.log("ya data ha final ??", parsed);
parsed.forEach(async (item) => {
// return res.json(item)
let newNft = await models.NFT.create({
name: item.Name,
description: item.Description,
background: item.Background,
body: item.Body,
mouth: item.Mouth,
eyes: item.Eyes,
head_gear: item.Head_Gear,
tokenId: item.tokenId,
image: item.imagesIPFS,
});
});
return res.json({
data: "nft created",
error: null,
success: true,
});
} catch (error) {
console.log("server error", error.message);
next(error);
}
},
get_nft: async (req, res, next) => {
try {
const { id } = req.params;
// console.log("id ?????????",id)
// console.log("type of ",typeof(id))
// const n=Number(id)
// console.log("type of ",typeof(id))
const nft = await models.NFT.findByPk(id);
if (!nft) {
throw new Error("Token ID invalid");
}
if (!nft.isMinted) {
throw new Error("Token not minted");
}
console.log(nft);
// }
const resObj = {
name: nft.name,
description: nft.description,
image: `https://gateway.pinata.cloud/ipfs/${nft.image}`,
attributes: [
{ trait_type: "background", value: `${nft.background}` },
{ trait_type: "body", value: `${nft.body}` },
{ trait_type: "mouth", value: `${nft.mouth}` },
{ trait_type: "eyes", value: `${nft.eyes}` },
{ trait_type: "tokenId", value: `${nft.tokenId}` },
{
display_type: "number",
trait_type: "Serial No.",
value: id,
max_value: 1000,
},
],
};
return res.json(resObj);
} catch (error) {
console.log("server error", error.message);
next(error);
}
},
get_nft_all: async (req, res, next) => {
try {
// console.log("id ?????????",id)
// console.log("type of ",typeof(id))
// const n=Number(id)
// console.log("type of ",typeof(id))
const nft = await models.NFT.findAndCountAll({
limit: 10
});
// console.log(nft);
if (!nft) {
throw new Error("Token ID invalid");
}
// if (nft.isMinted) {
// throw new Error("Token not minted");
// }
// console.log(nft);
// }
var resObjarr = [];
for (var i = 0; i < nft.rows.length; i++) {
resObj = {
name: nft.rows[i].name,
description: nft.rows[i].description,
image: `https://gateway.pinata.cloud/ipfs/${nft.rows[i].image}`,
attributes: [
{ trait_type: "background", value: `${nft.rows[i].background}` },
{ trait_type: "body", value: `${nft.rows[i].body}` },
{ trait_type: "mouth", value: `${nft.rows[i].mouth}` },
{ trait_type: "eyes", value: `${nft.rows[i].eyes}` },
{ trait_type: "tokenId", value: `${nft.rows[i].tokenId}` },
{
display_type: "number",
trait_type: "Serial No.",
value: nft.rows[i].id,
max_value: 1000,
},
],
};
resObjarr.push(resObj);
}
console.log(JSON.stringify(resObjarr))
return res.json(resObjarr);
} catch (error) {
console.log("server error", error.message);
next(error);
}
},
mint: async (req, res, next) => {
try {
const { id } = req.params;
const updated = await models.NFT.findByPk(id);
if (!updated) {
throw new Error("NFT ID invalid");
}
if (updated.isMinted) {
throw new Error("NFT Already minted");
}
updated.isMinted = true;
updated.save();
return res.json({
data: "Token minted successfully",
error: null,
success: true,
});
} catch (error) {
console.log("server error", error.message);
next(error);
}
},
};
Below is from the routes folder.
const router = require("express").Router();
const auth=require("../middleware/auth")
const {
create_nft,
get_nft,
get_nft_all,
mint
} = require("../controller/nft");
router.post(
"/create",
create_nft
);
router.get(
"/metadata/:id",
get_nft
);
router.get(
"/metadata",
get_nft_all
);
router.put(
"/mint/:id",
mint
);
module.exports = router;
Looking your code,you may having some kind of asyncrhonous issue in this part:
parsed.forEach(async (item) => {
// return res.json(item)
let newNft = await models.NFT.create({
name: item.Name,
description: item.Description,
background: item.Background,
body: item.Body,
mouth: item.Mouth,
eyes: item.Eyes,
head_gear: item.Head_Gear,
tokenId: item.tokenId,
image: item.imagesIPFS,
});
});
Because .forEach is a function to be used in synchronous context and NFT.create returns a promise (that is async). So things happens out of order.
So one approach is to process the data first and then perform a batch operation using Promise.all.
const data = parsed.map(item => {
return models.NFT.create({
name: item.Name,
description: item.Description,
background: item.Background,
body: item.Body,
mouth: item.Mouth,
eyes: item.Eyes,
head_gear: item.Head_Gear,
tokenId: item.tokenId,
image: item.imagesIPFS,
})
})
const results = await Promise.all(data)
The main difference here is Promise.all resolves the N promises NFT.create in an async context in paralell. But if you are careful about the number of concurrent metadata that data may be too big to process in parallel, then you can use an async iteration provided by bluebird's Promise.map library.
const Promise = require('bluebird')
const data = await Promise.map(parsed, item => {
return models.NFT.create({
name: item.Name,
description: item.Description,
background: item.Background,
body: item.Body,
mouth: item.Mouth,
eyes: item.Eyes,
head_gear: item.Head_Gear,
tokenId: item.tokenId,
image: item.imagesIPFS,
})
})
return data

How to mock AWS TimestreamWrite by jest

This project is to record data by AWS Timestream, and it works well.
However, I'm failed to mock AWS TimestreamWrite by using jest. I tried some ways but not working. Can someone help me?
My files as below:
ledger-service.js
const AWS = require("aws-sdk");
const enums = require("./enums");
var https = require("https");
var agent = new https.Agent({
maxSockets: 5000,
});
const tsClient = new AWS.TimestreamWrite({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: agent,
},
});
module.exports = {
log: async function (audit) {
try {
if (Object.keys(audit).length !== 0) {
if (!isPresent(audit, "name")) {
throw new Error("Name shouldn't be empty");
}
if (!isPresent(audit, "value")) {
throw new Error("Value shouldn't be empty");
}
return await writeRecords(recordParams(audit));
} else {
throw new Error("Audit object is empty");
}
} catch (e) {
throw new Error(e);
}
},
};
function isPresent(obj, key) {
return obj[key] != undefined && obj[key] != null && obj[key] != "";
}
function recordParams(audit) {
const currentTime = Date.now().toString(); // Unix time in milliseconds
const dimensions = [
// { Name: "client", Value: audit["clientId"] },
{ Name: "user", Value: audit["userId"] },
{ Name: "entity", Value: audit["entity"] },
{ Name: "action", Value: audit["action"] },
{ Name: "info", Value: audit["info"] },
];
return {
Dimensions: dimensions,
MeasureName: audit["name"],
MeasureValue: audit["value"],
MeasureValueType: "VARCHAR",
Time: currentTime.toString(),
};
}
function writeRecords(records) {
try {
const params = {
DatabaseName: enums.AUDIT_DB,
TableName: enums.AUDIT_TABLE,
Records: [records],
};
return tsClient.writeRecords(params).promise();
} catch (e) {
throw new Error(e);
}
}
ledger-service.spec.js
const AWS = require("aws-sdk");
const audit = require("./ledger-service");
describe("ledger-service", () => {
beforeEach(async () => {
jest.resetModules();
});
afterEach(async () => {
jest.resetAllMocks();
});
it("It should write records when all success", async () => {
const mockAudit={
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
const mockWriteRecords = jest.fn(() =>{
console.log('mock success')
return { promise: ()=> Promise.resolve()}
});
const mockTsClient={
writeRecords: mockWriteRecords
}
jest.spyOn(AWS,'TimestreamWrite');
AWS.TimestreamWrite.mockImplementation(()=>mockTsClient);
//a=new AWS.TimestreamWrite();
//a.writeRecords(); //these two lines will pass the test and print "mock success"
await audit.log(mockAudit); //this line will show "ConfigError: Missing region in config"
expect(mockWriteRecords).toHaveBeenCalled();
});
});
I just think the the AWS I mocked doesn't pass into the ledger-service.js. Is there a way to fix that?
Thanks
updates: Taking hoangdv's suggestion
I am thinking jest.resetModules(); jest.resetAllMocks(); don't work. If I put the "It should write records when all success" as the first test, it will pass the test. However, it will fail if there is one before it.
Pass
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
it("It should throw error when audit is empty", async () => {
const mockAudit = {};
await expect(audit.log(mockAudit)).rejects.toThrow(`Audit object is empty`);
});
Failed
it("It should throw error when audit is empty", async () => {
const mockAudit = {};
await expect(audit.log(mockAudit)).rejects.toThrow(`Audit object is empty`);
});
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
In ledger-service.js you call new AWS.TimestreamWrite "before" module.exports, this means it will be called with actual logic instead of mock.
The solution is just mock AWS before you call require("./ledger-service");
ledger-service.spec.js
const AWS = require("aws-sdk");
describe("ledger-service", () => {
let audit;
let mockWriteRecords;
beforeEach(() => {
mockWriteRecords = jest.fn(() => {
return { promise: () => Promise.resolve() }
});
jest.spyOn(AWS, 'TimestreamWrite');
AWS.TimestreamWrite.mockImplementation(() => ({
writeRecords: mockWriteRecords
}));
audit = require("./ledger-service"); // this line
});
afterEach(() => {
jest.resetModules(); // reset module to update change for each require call
jest.resetAllMocks();
});
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
});

XState: Wait for response of invoked function

I am planning to use XState for managing states in the backend of my application. When an api is called, a function will be called on successful state change. The result of the function call has to be returned as response of the api.
// Returns a Promise, e.g.:
// {
// id: 42,
// name: 'David',
// friends: [2, 3, 5, 7, 9] // friend IDs
// }
function getUserInfo(context) {
return fetch('/api/users/#{context.userId}').then(response =>
response.json()
);
}
// Returns a Promise
function getUserFriends(context) {
const { friends } = context.user;
return Promise.all(
friends.map(friendId =>
fetch('/api/users/#{context.userId}/').then(response => response.json())
)
);
}
const friendsMachine = Machine({
id: 'friends',
context: { userId: 42, user: undefined, friends: undefined },
initial: 'gettingUser',
states: {
gettingUser: {
invoke: {
src: getUserInfo,
onDone: {
target: 'gettingFriends',
actions: assign({
user: (context, event) => event.data
})
}
}
},
gettingFriends: {
invoke: {
src: getUserFriends,
onDone: {
target: 'success',
actions: assign({
friends: (context, event) => event.data
})
}
}
},
success: {
type: 'final'
}
}
});
interpret(friendsMachine).start()
I want the output of this of getUserFriends sent as a response from my api. How to wait for the transition and all the invocations to be completed?
You can use onDone (read the docs on invoking promises 📖)
Here's an example Express app that waits sequentially for 2 promises to finish, and then sends that data:
function eventuallyGet(value) {
return new Promise(res => {
setTimeout(() => {
res(value);
}, 1000)
})
}
const getUserMachine = Machine({
initial: 'fetchingName',
context: {
user: undefined
},
states: {
fetchingName: {
invoke: {
src: () => eventuallyGet('David'),
onDone: {
target: 'fetchingDetails',
actions: assign({
user: (ctx, e) => ({
...ctx.user,
name: e.data
})
})
}
}
},
fetchingDetails: {
invoke: {
src: () => eventuallyGet({ location: 'Florida' }),
onDone: {
target: 'success',
actions: assign({
user: (ctx, e) => ({
...ctx.user,
...e.data
})
})
}
}
},
success: {
type: 'final',
data: {
user: ctx => ctx.user
}
}
}
});
app.get('/user', function(request, response) {
interpret(getUserMachine)
.onDone(e => {
response.json(e.data);
})
.start();
});
You can see the code here: https://glitch.com/~pleasant-relish

Resources