Skip to content

Commit

Permalink
Feat/json node (#36)
Browse files Browse the repository at this point in the history
* Add features

* udpdate changeset

* fix: test errors
  • Loading branch information
SorsOps authored Jul 18, 2023
1 parent 5863582 commit 745e1a2
Show file tree
Hide file tree
Showing 23 changed files with 623 additions and 32 deletions.
8 changes: 8 additions & 0 deletions .changeset/curvy-planets-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@tokens-studio/graph-engine": minor
---

- Adds a parse Unit node.
- Adds `align-items` to the exposed UI
- Adds native supports for tokenSets in input
- Adds Json node (alpha)
1 change: 1 addition & 0 deletions packages/graph-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"types": "./dist/esm/index.d.ts",
"dependencies": {
"@tokens-studio/types": "^0.2.3",
"ajv": "^8.12.0",
"apca-w3": "^0.1.9",
"chroma-js": "^2.4.2",
"color-blind": "^0.1.3",
Expand Down
53 changes: 53 additions & 0 deletions packages/graph-engine/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,59 @@ const output = await execute({
});
```

## Examples

Provide token sets as part of your input

```ts
import {
execute,
nodes,
MinimizedFlowGraph,
} from "@tokens-studio/graph-engine";

//Note that the references are not resolved automatically. The graph is responsible for resolving if it wants to.
const tokens = {
dimension: {
scale: {
value: "2",
type: "dimension",
},
xs: {
value: "4",
type: "dimension",
},
sm: {
value: "{dimension.xs} * {dimension.scale}",
type: "dimension",
},
md: {
value: "{dimension.sm} * {dimension.scale}",
type: "dimension",
},
lg: {
value: "{dimension.md} * {dimension.scale}",
type: "dimension",
},
xl: {
value: "{dimension.lg} * {dimension.scale}",
type: "dimension",
},
},
};

const output = await execute({
graph: myGraph,
inputValues: {
myTokens: {
name: "My tokens",
tokens,
},
},
nodes,
});
```

## Documentation

See our documentation site [here](https://tokens-studio.github.io/graph-engine/)
42 changes: 38 additions & 4 deletions packages/graph-engine/src/nodes/generic/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
* @packageDocumentation
*/

import { IResolvedToken, flatten } from "#/utils/index.js";
import { NodeDefinition, NodeTypes } from "../../types.js";
import { DeepKeyTokenMap } from "@tokens-studio/types";

const type = NodeTypes.INPUT;

Expand All @@ -17,12 +19,28 @@ const defaults = {
definition: {},
};

export type TokenSet = {
name: string;
tokens: DeepKeyTokenMap;
};

export type TypeDefinition = {
type: "string" | "number" | "boolean" | "integer";
type: "string" | "number" | "boolean" | "integer" | "tokenSet" | "json";
enum?: string[];
/**
* Json schema for the type. Only used if the type is json
*/
schema?: string;
modifier?: boolean;
};

export type State = {
values: Record<string, any>;
definition: Record<string, TypeDefinition>;
};

export type Input = Record<string, any>;

/**
* Optional validation function.
* @param inputs
Expand All @@ -40,6 +58,10 @@ const validateInputs = (inputs, state) => {
*/
if (typeof definition == "object") {
switch (definition.type) {
case "tokenSet":
if (typeof inputs[key] !== "object") {
throw new Error("Expected object for input: " + key);
}
case "string":
if (typeof inputs[key] !== "string") {
throw new Error("Expected string for input: " + key);
Expand All @@ -57,19 +79,31 @@ const validateInputs = (inputs, state) => {
* @param state
* @returns
*/
const process = (input, state) => {
const process = (input: Input, state: State) => {
const final = {
...state.values,
...input,
};
return final;

//We need to process the values
return Object.fromEntries(
Object.entries(final).map(([key, value]) => {
const definition = state.definition[key];
switch (definition.type) {
case "tokenSet":
return [key, flatten(value.tokens || {}, [])];
default:
return [key, value];
}
})
);
};

const mapOutput = (input, state, processed) => {
return processed;
};

export const node: NodeDefinition = {
export const node: NodeDefinition<Input, State> = {
type,
defaults,
validateInputs,
Expand Down
10 changes: 9 additions & 1 deletion packages/graph-engine/src/nodes/input/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@ import { node as enumeratedConstant } from "./enumeratedConstant.js";
import { node as objectify } from "./objectify.js";
import { node as slider } from "./slider.js";
import { node as spread } from "./spread.js";
import { node as json } from "./json.js";

export const nodes = [constant, enumeratedConstant, objectify, slider, spread];
export const nodes = [
constant,
enumeratedConstant,
objectify,
slider,
spread,
json,
];
95 changes: 95 additions & 0 deletions packages/graph-engine/src/nodes/input/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Provides a defined constant for the graph
*
* @packageDocumentation
*/

import { NodeDefinition, NodeTypes } from "../../types.js";
import Ajv, { DefinedError } from "ajv";

export class ValidationError extends Error {
errors: DefinedError[] = [];
constructor(message: string) {
super(message);
this.name = "ValidationError";
}
}

export const type = NodeTypes.JSON;

/**
* Defines the starting state of the node
*/
export const defaults = {
input: "",
schema: "",
};

export type Input = {
/**
* The string representation of the JSON object
*/
input: string;
/**
* The string representation of the JSON schema
*/
schema: string;
};

/**
* Core logic for the node. Will only be called if all inputs are valid.
* Return undefined if the node is not ready to execute.
* Execution can also be optionally delayed by returning a promise.
* @param input
* @param state
* @returns
*/
export const process = (input: Input, state: Input) => {
const final = {
...state,
...input,
};

let inputObject: Record<string, any>;
let schema: Record<string, any>;

//Attempt to parse the input
try {
inputObject = JSON.parse(final.input);
} catch (e) {
throw new ValidationError("Invalid JSON for input");
}

if (!final.schema) {
return inputObject;
}

//Attempt to parse the schema
try {
schema = JSON.parse(final.schema);
} catch (e) {
throw new ValidationError("Invalid JSON for schema");
}

// @ts-ignore This is a weird error with typing
const ajv = new Ajv();
const validate = ajv.compile(schema);

//Call the validation function
validate(inputObject);

if (validate.errors) {
const error = new ValidationError("Validation errors");
console.log(error);
error.errors = validate.errors as unknown as DefinedError[];
throw error;
}

return inputObject;
};

export const node: NodeDefinition<Input, Input> = {
type,
defaults,
process,
};
3 changes: 2 additions & 1 deletion packages/graph-engine/src/nodes/typing/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { node as passUnit } from "./passUnit.js";
import { node as parseUnit } from "./parseUnit.js";

export const nodes = [passUnit];
export const nodes = [passUnit, parseUnit];
39 changes: 39 additions & 0 deletions packages/graph-engine/src/nodes/typing/parseUnit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NodeDefinition, NodeTypes } from "../../types.js";

import valueParser from "postcss-value-parser";
const type = NodeTypes.PARSE_UNIT;

type Input = {
input: string;
};
type Output = {
unit?: string;
number?: string;
};

/**
* Core logic for the node. Will only be called if all inputs are valid.
* Return undefined if the node is not ready to execute.
* Execution can also be optionally delayed by returning a promise.
* @param input
* @param state
* @returns
*/
const process = (input: Input) => {
const x = valueParser.unit("" + input.input);
if (!x) {
return {};
}

return x;
};

const mapOutput = (input: Input, state: any, output: Output) => {
return output;
};

export const node: NodeDefinition<Input, any, Output> = {
type,
mapOutput,
process,
};
2 changes: 2 additions & 0 deletions packages/graph-engine/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export enum NodeTypes {
SLIDER = "studio.tokens.input.slider",
SPREAD = "studio.tokens.input.extract",
OBJECTIFY = "studio.tokens.input.objectify",
JSON = "studio.tokens.input.json",

CSS_MAP = "studio.tokens.css.map",

Expand Down Expand Up @@ -140,6 +141,7 @@ export enum NodeTypes {
LOWER = "studio.tokens.string.lowercase",
REGEX = "studio.tokens.string.regex",
PASS_UNIT = "studio.tokens.typing.passUnit",
PARSE_UNIT = "studio.tokens.typing.parseUnit",

//Accessibility
CONTRAST = "studio.tokens.accessibility.contrast",
Expand Down
22 changes: 18 additions & 4 deletions packages/graph-engine/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,19 @@ export interface IResolvedToken {
description?: string;
}

/**
* Takes in a nested object of tokens and returns a flattened array of tokens
* @param nested
* @param keyPath Optional key path to prefix the name of the token
* @returns
*/
export const flatten = (
nested: Record<string, IResolvedToken>,
keyPath: string[] = []
): IResolvedToken[] => {
return Object.entries(nested).reduce((acc, [key, val]) => {
//Check if leaf node
if (
val &&
typeof val.value !== "undefined" // && typeof val.type !== "undefined"
) {
if (val && typeof val.value !== "undefined") {
acc.push({
name: [...keyPath, key].join("."),
value: val.value,
Expand All @@ -73,13 +76,24 @@ export const flatten = (
}, [] as IResolvedToken[]);
};

/**
* Takes in an array of tokens and returns a map of tokens.
* This only works if the tokens are flat, meaning they do not have nested tokens
* @param tokens
* @returns
*/
export const flatTokensToMap = (tokens: IResolvedToken[]) => {
return tokens.reduce((acc, token) => {
acc[token.name] = token;
return acc;
}, {} as Record<string, IResolvedToken>);
};

/**
* Takes an array of tokens and returns a map of tokens. This does result in nested tokens
* @param tokens
* @returns
*/
export const flatTokensRestoreToMap = (tokens: IResolvedToken[]) => {
const returning = {};
tokens.forEach((token) => {
Expand Down
Loading

0 comments on commit 745e1a2

Please sign in to comment.