Loading and Validating Decisions
This guide explains how to load and validate decision records from a simple script.
Setup
Dependencies
The following packages are required:
npm install @noodlestan/designer-functions @noodlestan/designer-schemas
Configuring Designer Decisions
Add a Designer Decisions configuration file in the root of your project.
Directorydata/
- β¦
Directorysrc/
- β¦
- dd.config.mjs
- package.json
The minimum required configuration specifies only one or more DecisionSource from where to load decision data (DecisionInput).
In this example we configured the Sample and Demo Data decision sources and a local data/
directory for you to add more decisions, as well as the built-in schemas.
import { defineConfig } from '@noodlestan/designer-functions';
import { DEMO_DATA, SAMPLE_DATA } from '@noodlestan/designer-decisions';
export default defineConfig({ store: { decisions: [SAMPLE_DATA, DEMO_DATA, './data'], },});
In the API / Configuration section you can find all configuration options and helpers.
Loading input data
Add a script to load decisions and print eventual validation errors.
Directorydata/
- β¦
Directorysrc/
- β¦
Directoryscripts/
- validate-decisions.js
- dd.config.mjs
- package.json
We will use the loadConfig() helper to load and validate the dd.config.mjs
configuration first, and then the all-in-one staticStoreBuilder() to load and validate the decision data.
import path from 'path';import { createStoreContext, staticStoreBuilder, loadConfig } from '@noodlestan/designer-functions';
const config = await loadConfig();const context = createStoreContext(config.store);const build = staticStoreBuilder(context);
The build()
functions returns a pre-validated decision store asynchronously. The store exposes all input records along with any errors that may have occurred while loading schemas and data sources, or when validating input data.
Add the following code to execute the builder and list each recordsβ name
and model
.
We will also format any eventual errors to better assist us if something goes wrong.
import { createStoreContext, formatError, loadConfig, staticStoreBuilder,} from '@noodlestan/designer-functions';5 collapsed lines
const config = await loadConfig();const context = createStoreContext(config.store);const build = staticStoreBuilder(context);
const loadDecisions = async () => { const store = await build();
const records = store.records(); records.forEach(({ name, model }) => console.info(name, model)); console.info(`π ${records.length} records`); context.errors().forEach(error => console.error(formatError(error)));};
loadDecisions();
You can run the script directly from the command line:
node scripts/validate-decisions.js
If no errors occur, the output should look something like:
...Sizing Space Scale space-scale/anchoredCard Thumb Minimum Size space-value/explicitAvatar Minimum Size space-value/explicitAvatar Sizes space-scale/anchoredπ 67 records
Adding more decision data
As illustrated in the Decisions as Data guide, when using JSON files as data source, you are free to create sub-directories and name the files as you wish. Grouping decisions by type or scope is a good starting point.
Letβs start by adding a single color.json
file in the data/
directory, where we can define color related decisions.
Directorydata/
- color.json
Here, we created a couple of Color Value decisions based on the samples.
[ { "model": "color-value/explicit", "name": "Warm Color", "params": { "value": "orange" } }, { "model": "color-value/explicit", "name": "Cold Color", "params": { "value": "aqua" } }]
If you run node scripts/validate-decisions.js
again, you will see two new decisions at bottom of the list.
...Avatar Minimum Size space-value/explicitAvatar Sizes space-scale/anchoredWarm Color color-value/explicitCold Color color-value/explicitπ 68 records
If your records arenβt showing, lookout for error messages such as:
π 66 recordsπ₯ Invalid DecisionSource "./data". Decision file does not contain an array.Path: "./data/color.json"
If you see this, make sure your file contains an array of decisions [{ ... }, { ... }]
as opposed to a single {...}
decision record.
The following error can also appear if your data has a syntax error. A single misplaced }
or missing :
will cause the JSON data to be unreadable. We recommend using an editor such as VS Code for visual feedback and assistance.
π 66 recordsπ₯ Invalid DecisionSource "./data". Could not parse decision file. Expected ',' or ']' after array element in JSON at position 233. Path: "./data/color.json"
Validating input data
Decision data is automatically validated against JSON schemas as soon as it is loaded.
For instance, if the params
are incomplete or donβt match the expected data types for the decision model
, the decision will be marked with validation errors. In these cases, the decision will likely produce unexpected values so itβs good to know that there are issues in our data.
To inspect the validation errors we can format messages using the formatDecisionInputError()
utility function.
import { createStoreContext, formatError, formatDecisionInputError, loadConfig, staticStoreBuilder,} from '@noodlestan/designer-functions';5 collapsed lines
const config = await loadConfig();const context = createStoreContext(config.store);const build = staticStoreBuilder(context);
const loadDecisions = async () => { const store = await build();
const records = store.records(); records.forEach(({ name, model }) => console.info(name, model)); console.info(`π ${records.length} records`); store.inputErrors().forEach(error => console.error(formatDecisionInputError(error))); context.errors().forEach(error => console.error(formatError(error)));};
loadDecisions();
If we now introduce a couple of issues in or data, for instance deleting the value
parameter from one color and set it to an invalid value in the second oneβ¦
[ { "model": "color-value/explicit", "name": "Warm Color", "params": { "value": "orange" } }, { "model": "color-value/explicit", "name": "Cold Color", "params": { "value": false } }]
β¦ and run our script again, the validation errors will be displayed at the bottom.
π 68 recordsπ₯ Decision "Warm Color" /params (required) must have required property 'value'π₯ Decision "Cold Color" /params/value (type) must be object, string or number
Producing decision values
So far, the script only validates the input data against the schemas, guaranteeing that all the required attributes are present and that they are of the correct type.
Producing decision values may still result in errors if, for instance, a reference to another decision can not be resolved, or it resolves to a decision of the wrong type.
The following two utility functions help with this:
- produceDecisions(): aggregates all produced values, contexts, and errors.
- formatDecisionStatus(): returns a formatted decision, including its value and errors details.
Remove the store.inputErrors().forEach(...)
line, as it will become redundant, and add these 2 lines to the script:
import { createStoreContext, formatDecisionStatus, formatError, loadConfig, staticStoreBuilder, produceDecisions} from '@noodlestan/designer-functions';7 collapsed lines
const config = await loadConfig();const context = createStoreContext(config.store);const build = staticStoreBuilder(context);
const loadDecisions = async () => { const store = await build();
const records = store.records(); const produced = produceDecisions(store); produced.decisions().forEach(status => console.info(formatDecisionStatus(status))); console.info(`π ${records.length} records`); store.inputErrors().forEach(error => console.error(formatDecisionInputError(error))); context.errors().forEach(error => console.error(formatError(error)));
3 collapsed lines
};
loadDecisions();
Without any errors, the output will now display the decision status, the name and model, and the value produced by the decision.
...π© | Textarea Maximum Height | space-value/explicit | 300 |π© | Card Thumb Minimum Size | space-value/explicit | 175 |π© | Avatar Minimum Size | space-value/explicit | 50 |π© | Avatar Sizes | space-scale/anchored | [50, 100, 150, 200] |
If we introduce a validation issue in one of the items, as in the previous example, the output will also include details about the error.
...π© | Card Thumb Minimum Size | space-value/explicit | 175 |π© | Avatar Minimum Size | space-value/explicit | 50 |π© | Avatar Sizes | space-scale/anchored | [50, 100, 150, 200] |π¨ | Warm Color | color-value/explicit | #000000 | 1 errors, 1 warnings > π¨ Decision "Warm Color" /params (required) must have required property 'value' > π¨ Invalid input data for a ColorValue in {"$name":"Warm Color"}: undefined
Summarizing and catching errors
The produced
object reports counts for decision records, and different types of errors in a single string.
19 collapsed lines
import { createStoreContext, formatDecisionStatus, formatError, loadConfig, staticStoreBuilder, produceDecisions} from '@noodlestan/designer-functions';
const config = await loadConfig();const context = createStoreContext(config.store);const build = staticStoreBuilder(context);
const loadDecisions = async () => { const store = await build();
const records = store.records(); const produced = produceDecisions(store); produced.decisions().forEach(status => console.info(formatDecisionStatus(status)));
console.info(`π ${records.length} records`); console.info('π', produced.summary());
4 collapsed lines
context.errors().forEach(error => console.error(formatError(error)));};
loadDecisions();
You can use it to summarize all counts in one line.
π¨ Decision "Sizing Space Scale" /params/after (required) must have required property 'steps'π 59 records, 0 errors, 1 warnings
If you are running this as part of your build process you will probably want to exit the script with an error code.
20 collapsed lines
import { createStoreContext, formatDecisionStatus, formatError, loadConfig, staticStoreBuilder, produceDecisions} from '@noodlestan/designer-functions';
const config = await loadConfig();const context = createStoreContext(config.store);const build = staticStoreBuilder(context);
const loadDecisions = async () => { const store = await build();
const produced = produceDecisions(store); produced.decisions().forEach(status => console.info(formatDecisionStatus(status))); console.info('π', produced.summary());
context.errors().forEach(error => console.error(formatError(error))); if (produced.hasErrors()) { throw new Error(`Errors encountered producing decisions.`); }};
loadDecisions().catch(err => { console.error(err); process.exit(1);});
Next steps
The next guides provide step by step instructions on practical uses cases for your decision data: