|
@@ -3030,6 +3030,47 @@ not: with the marker, exactly one output carries data and everything downstream
|
|
|
of the others is skipped. Without it, every named output the node returns is
|
|
of the others is skipped. Without it, every named output the node returns is
|
|
|
live at once, which is how `filter` sends kept and discarded items down two
|
|
live at once, which is how `filter` sends kept and discarded items down two
|
|
|
paths in the same run.
|
|
paths in the same run.
|
|
|
|
|
+
|
|
|
|
|
+## Conventions These Nodes Follow
|
|
|
|
|
+
|
|
|
|
|
+Four rules emerged while building the Tier 1 set. New nodes should follow them.
|
|
|
|
|
+
|
|
|
|
|
+**Fail loudly rather than drop data.** A node that cannot do what was asked
|
|
|
|
|
+throws, with a message naming what it received. `set-fields` throws when
|
|
|
|
|
+keep-all mode is handed an array instead of an object; its dotted paths refuse
|
|
|
|
|
+to overwrite a value they would otherwise destroy; `merge` throws when an item
|
|
|
|
|
+lacks the key it was told to combine on. The alternative - returning an empty
|
|
|
|
|
+object, or bucketing everything under the string `undefined` - produces a
|
|
|
|
|
+workflow that keeps running and quietly produces wrong data.
|
|
|
|
|
+
|
|
|
|
|
+**Guard a configurable output name against your own reserved keys.** A node that
|
|
|
|
|
+returns fixed keys alongside a user-named field must reject a name that would
|
|
|
|
|
+collide, throwing early:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+if (outputField === 'count' || outputField === 'groups') {
|
|
|
|
|
+ throw new Error('Aggregate: outputField cannot be "' + outputField +
|
|
|
|
|
+ '", which is a reserved output name for this node. Pick another name.');
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`aggregate`, `sort-limit-dedupe` and `datetime` all need this. `template` and
|
|
|
|
|
+`json` do not, because they return exactly one key and there is nothing to
|
|
|
|
|
+collide with - do not add the guard where it protects nothing.
|
|
|
|
|
+
|
|
|
|
|
+**Config values arrive already evaluated.** The engine resolves `{{...}}` in
|
|
|
|
|
+node config before `execute()` runs, and a config string that is exactly one
|
|
|
|
|
+expression keeps its native type. So a field typed as a string in the schema can
|
|
|
|
|
+legitimately hold an array at run time, which is why several nodes begin with
|
|
|
|
|
+`if (Array.isArray(inputField))`. That branch is not dead code. Nodes must not
|
|
|
|
|
+re-interpolate config values.
|
|
|
|
|
+
|
|
|
|
|
+**Read paths with the shared helper.** Use
|
|
|
|
|
+`smartbotic.utils.getFieldValue(data, path)` rather than writing a private path
|
|
|
|
|
+walker. Two older nodes, `if-condition` and `loop`, still carry their own
|
|
|
|
|
+divergent copies - `loop` silently skips a leading `data.` segment and
|
|
|
|
|
+`if-condition` does not - and that inconsistency is exactly what the shared
|
|
|
|
|
+helper exists to stop spreading.
|
|
|
````
|
|
````
|
|
|
|
|
|
|
|
- [ ] **Step 6: Commit**
|
|
- [ ] **Step 6: Commit**
|