Take the following code:
const isWarning = () => { ... }
const setWarning = () => { ... }
const machine = Machine({
initial: "foo",
context: {
warning: null
},
states: {
foo: {
on: {
"": [
target: "bar",
action: "setWarning",
cond: "isWarning",
]
}
},
bar: {
on: {
FOO: "foo,
}
}
}
}, {
actions: {
setWarning
}
guards: {
isWarning
}
});
Is this the best way to go to "bar" and set a warning based on some quantitative data in "foo"?
Given the posted code example, I am not sure what you mean by "quantitative data in foo". Data relevant for machine's behavior can be stored in machine's context or state's meta property.
For getting into bar state and set a warning you might need something like:
const sm = Machine({
initial: 'baz',
context: { wasWarned: false },
on: {
'WARNING': {
target: 'bar',
action: 'setWarning'
}
},
states: {
baz: {},
bar: {}
}
}, {
actions: {
setWarning: assign({ warning: true })
}
})
This means: When machine gets 'WARNING' event, go into bar state AND immediately, before anything else update the context.
Actions are not immediately triggered. Instead, the State object returned from machine.transition(...) will declaratively provide an array of .actions that an interpreter can then execute.
The transition will be enabled after the guards are passed.
Other code example that might prove useful depending on what you want to achieve:
const sm = Machine({
initial: 'pending',
context: { wasWarned: null },
states: {
pending: {
on: {
'': [
{target: 'bar', cond:'wasWarned'},
{target: 'baz', cond: 'otherCond'}
]
}
},
bar: {},
baz: {}
},
guards: {
wasWarned: (ctx) => ctx.wasWarned
}
})
Related
I would like to use the checkSchema method instead of what I am currently doing with all the checks on the post route in an array. The problem I have is I can't find good documentation on the syntax used in the object for each key. The doc page for schema validation (https://express-validator.github.io/docs/schema-validation.html) gives one example but no links to all the syntax that defines all the attributes you can use (isInt, toInt, isUppercase, rtrim, etc.) I have searched high and low for the docs that tell you everything you can use there but no luck. Can someone point me in the right place?
express-validator is a set of express.js middlewares that wraps validator.js validator and sanitizer functions.
you can find all the rules available in the link above.
here is a good example how it's used with checkSchema:
import { checkSchema, validationResult } from 'express-validator'
const schema = {
id: {
isFloat: true,
// Sanitizers can go here as well
toFloat: true,
errorMessage: "must be a valid number"
},
first_name: {
exists: {
errorMessage: "first_name is required"
},
isLength: {
errorMessage: "first_name has invalid length",
options: {
min: 1
}
}
},
middle_name: {
optional: {
options: { nullable: true, checkFalsy: true }
}
},
last_name: {
exists: {
errorMessage: "last_name is required"
},
isLength: {
errorMessage: "last_name has invalid length",
options: {
min: 1
}
}
},
date_of_birth: {
isISO8601: {
errorMessage: `date of birth is not a valid iso date`
},
isBefore: {
date: "01-01-2000",
errorMessage: `should be less than 01-01-2000`
},
isAfter: {
date: "01-01-1970",
errorMessage: `should be greater than 01-01-1970`
}
},
email: {
exists: {
errorMessage: "email is required"
},
isEmail: {
errorMessage: "email is invalid"
}
},
current_country_of_residence: {
exists: {
errorMessage: "current_country_of_residence is required",
options: {
checkNull: true,
checkFalsy: true
}
}
},
current_city_of_residence: {
exists: {
bail: true,
errorMessage: "current_city_of_residence is required"
},
isMongoId: {
errorMessage: "current_city_of_residence is has invalid id for"
}
},
email: {
isEmail: { errorMessage: 'email is not a valid email' },
isLength: { errorMessage: 'email has invalid length', options: { min: 1 } }
},
skills: {
isArray: {
errorMessage: `invalid value for skills`,
options: {
min: 3,
max: 5
}
},
isIn: {
options: ["java", "C++", "javascript"],
errorMessage: `allowed values for skills are: ${["java", "C++", "javascript"]}`
},
custom: {
options: (values) => {
const unique_values = new Set(values)
if (unique_values.size !== values.length) {
return Promise.reject()
}
return Promise.resolve()
},
errorMessage: `you can't add duplicated`
},
customSanitizer: {
options: async (value, { req }) => {
return value
}
}
}
}
const validate = () => {
return [
checkSchema(schema),
(req, res, next) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.mapped() })
}
next()
}
]
}
export default validate()
Given the following DynamoDB document:
{
"myobject" : {"foo" : "bar"},
"mylist" : [{"some" : "stuff}]
}
My goal is to update this document to get the following result:
{
"myobject" : {"foo" : "bar"},
"mylist" : [{"some" : "stuff}, {"foo" : "bar"}]
}
My request's params look like this:
let params = {
TableName: doctorSlotsTable,
Key: {
hashKey: hash,
rangeKey: range
},
UpdateExpression: 'SET mylist = list_append(if_not_exists(mylist, :empty_list), [myobject])',
ExpressionAttributeValues : {
':empty_list' : []
},
ReturnValues : "UPDATED_NEW"
};
This obviously does not work because the [ in the list_append triggers a syntax error.
Is there any solution to achieve that without having to get the data in a previous request and add it manually to the list ?
Unfortunately you cannot use an attribute name as an operand to list_append(...) unless that attribute is itself a list. The best you can do I believe would be to store myobject in the proper type up front, and then update it as expected.
Since storage is cheap & network/compute are expensive here, you could even duplicate the data to have one of them in the right form.
Here's a full example, where createTable() and deleteTable() do exactly what you think:
const PK = 'the item';
async function createObjAndList() {
const docClient = new DocumentClient();
const myObject = { foo: "bar" };
const theItem = {
PK,
myObject,
myObjectAsList: [ myObject ],
myList: [ { some : "stuff" } ],
};
const putParams = {
TableName,
Item: theItem
}
await docClient.put(putParams).promise();
console.log(`Put item ${util.inspect(theItem)}`);
}
async function updateListWithObject() {
const docClient = new DocumentClient();
const updateParams = {
TableName,
Key: { PK },
UpdateExpression: `SET #myList = list_append(if_not_exists(#myList, :emptyList), #myObjectAsList)`,
ExpressionAttributeNames: {
'#myList': 'myList',
'#myObjectAsList': 'myObjectAsList',
},
ExpressionAttributeValues: {
':emptyList': [],
}
}
await docClient.update(updateParams).promise();
console.log(`Updated list to include object`);
}
async function getObjAndList() {
const docClient = new DocumentClient();
const results = await docClient.get({ TableName, Key: { PK }}).promise();
console.log(`Item is now: ${util.inspect(results.Item)}`);
}
if (module === require.main) {
(async () => {
try {
await createTable();
await createObjAndList()
await updateListWithObject();
await getObjAndList();
} catch (err) {
console.log(`Error: ${err.message}`);
} finally {
await deleteTable();
}
})();
}
The output from this is:
Put item {
PK: 'the item',
myObject: { foo: 'bar' },
myObjectAsList: [ { foo: 'bar' } ],
myList: [ { some: 'stuff' } ]
}
Updated list to include object
Item is now: {
myList: [ { some: 'stuff' }, { foo: 'bar' } ],
myObject: { foo: 'bar' },
PK: 'the item',
myObjectAsList: [ { foo: 'bar' } ]
}
It seems to me that you can fire an action in one of the following ways:
Explicitly
{
...
states: {
foo: {
on: {
BAR: {
actions: "performSomeAction",
target: "bar",
},
},
},
bar: {},
},
...
}
Implicitly with "entry"
{
...
states: {
foo: {
on: {
BAR: "bar",
}
},
bar: {
entry: "performSomeAction",
},
},
...
}
In what circumstances would you choose one over the other?
David Kourshid (creator of xstate) answered this on Spectrum:
They mean different things.
Transition actions mean "execute this action only on this transition"
Entry/exit actions mean "execute this action on any transition that enters/exits this state"
Datastore will not allow properties over 1500 bytes in size if they are indexed. So if I have an object
{foo : X, bar : Y}
where Y is longer than 1500 characters I can disable indexing on the individual property and store it by means of an array, thus:
[
{ name : 'foo', value: 'X'},
{ name: 'bar', value: 'Y', excludeFromIndexes: true}
]
But this does not work if the property is part of an Embedded Entity (i.e. an entity inside a property of another entity).
How do I store something like this?
{ foo : X, bar : { baz : Y } }
This does not work:
[
{ name : 'foo', value: 'X'},
{ name: 'bar', value:
{
name: 'baz',
value: 'Y',
excludeFromIndexes: true
},
excludeFromIndexes: true}
]
And this either:
[
{ name : 'foo', value: 'X'},
{ name: 'bar', value: {'baz', 'Y' }, excludeFromIndexes: true}
]
UPDATE:
Here it is an example snippet:
const DataStore = require('#google-cloud/datastore');
const datastore = DataStore({projectId});
const foo1 = { name : { forename: 'Dave', surname : 'Tong' }, colour : 'blue'}
const putAndGet = async data => {
return new Promise(async (resolve, reject) => {
try {
const key = await datastore.save({key: datastore.key([Kind]), data: data});
} catch (err) {
reject(err);
}
const results = [];
const query = datastore.createQuery(Kind);
query.runStream()
.on('error', (error) => {
reject(new Error(error));
})
.on('data', (entity) => {
results.push(entity);
})
.on('end', () => {
resolve(results);
});
});
}
// This will succeed
putAndGet(foo1).then(ret => {
for (var i = 0; i < ret.length; i++) {
console.log(ret[i].name.forename + " likes " + ret[i].colour);
}
const str = [];
for (var i = 0; i < 400; i++) str[i] = 'X';
const foo2 = {name: {forename: str.join('XXX'), surname: 'Tong'}, colour: 'blue'}
// This will fail
return putAndGet(foo2);
}).then(ret => {
for (var i = 0; i < ret.length; i++) {
console.log(ret[i].name.forename + " likes " + ret[i].colour);
}
}).catch(err => {
console.log(err.message);
});
After some testing, I was able to achieve what you want to do. I have also edited your question, in order to make it clearer for future readers of this post, as you refer to "property part of another property", but that is defined in Datastore as an EmbeddedEntity, so I will stick to that name for a clearer explanation.
Embedded entities can have subproperties which are longer than 1500 Bytes, but you must exclude those subproperties explicitly, or an error will show. To do so, you must declare each EmbeddedEntity as:
{
"properties": {
"surname": {
"stringValue": "A long surname which has more than 1500B",
"excludeFromIndexes": true
},
"forename": {
"stringValue": "David"
}
}
}
But declaring the "excludeFromIndexes": true programatically using NodeJS is not too straight-forward. However finally I managed to solve it. The key is the save() function, where you can declare the properties and subproperties that you want to exclude from indexing, like:
datastore.save({key: entity_key, data: entity_data, excludeFromIndexes: ['prop1', 'prop2.subprop1']});
Here I share a small piece of code (which is an MCV from the piece of code that you shared) that works and creates an entity with some properties containing long suproperties:
const DataStore = require('#google-cloud/datastore');
const projectId = "YOUR_PROJECT_ID";
const datastore = DataStore({projectId});
const data = {name: {forename: 'David', surname: '<YOUR_LONG_STRING>'}, colour: 'purple'}
const Kind = "<YOUR_ENTITY_KIND>";
datastore
.save({key: datastore.key([Kind]), data: data, excludeFromIndexes: ['name.surname']})
.then(() => {
console.log(`Entity saved`);
})
.catch(err => {
console.error('ERROR:', err);
});
Once you run this piece of code, if you inspect the entity in your Datastore dashboard, you will be able to see that your EmbeddedEntity is defined as I shared at the beginning of my answer.
When I call a mutation on my client I get the following warning:
writeToStore.js:111 Missing field updateLocale in {}
This is my stateLink:
const stateLink = withClientState({
cache,
resolvers: {
Mutation: {
updateLocale: (root, { locale }, context) => {
context.cache.writeData({
data: {
language: {
__typename: 'Language',
locale,
},
},
});
},
},
},
defaults: {
language: {
__typename: 'Language',
locale: 'nl',
},
},
});
And this is my component:
export default graphql(gql`
mutation updateLocale($locale: String) {
updateLocale(locale: $locale) #client
}
`, {
props: ({ mutate }) => ({
updateLocale: locale => mutate({
variables: { locale },
}),
}),
})(LanguagePicker);
What am I missing?
I was getting the same warning and solved it by returning the data from the mutation method.
updateLocale: (root, { locale }, context) => {
const data = {
language: {
__typename: 'Language',
locale,
}
};
context.cache.writeData({ data });
return data;
};
At the moment, apollo-link-state requires you to return any result. It can be null too. This might be changed in the future.