inhibitOnNull
Returns a step that yields the same values as the passed in step, but downstream
work is inhibited when a yielded value is null or undefined.
inhibitOnNull enables Grafast to optimize away unnecessary downstream work;
it does not guarantee that the work will not execute. Do not use it for
authorization or to prevent security-sensitive operations. Enforce those
requirements inside the protected operation or data source.
The inhibition only affects steps that depend on the wrapper step (and those that depend on those, and so on); steps depending on the original (unwrapped) step are not otherwise impacted.
For those curious only, you don't need to understand this.
The execute method of a step class is passed
the batched executionValue (EV) for each step it depends on to provide the
data needed for execution.
If there is any inhibition, the step will instead be passed a derived set of new EVs which have the indices (indexes) that are inhibited in any EV removed from all EVs - i.e. those specific indices are "skipped" and the "batch size" is adjusted downwards to match. Once the step has executed, the result has these indices re-populated (with an inhibited value) such that the batch size remains consistent with other steps in the same layer plan.
Should all indices be inhibited, the step will not execute at all.
Example
const $parentId = get($post, "parentId");
// If $parentId is null, Gra*fast* can avoid executing `loadOne`
const $parent = loadOne(inhibitOnNull($parentId), batchGetPostById);
In the example above, when the $parentId step yields null, the associated
value for the loadOne step will automatically yield null (and be inhibited)
and Grafast can avoid executing the load for that entry. When a value for
$parentId is not nullish, execution for that value proceeds as normal.
Trapping
Combine inhibitOnNull with trap if you want to convert an
inhibited value back into a regular null (or another value) further down the
plan.
Plan diagrams
Internally, inhibitOnNull currently creates a __FlagStep, but that step is
usually converted into a dependency constraint when another step depends on it.
Thus in plan diagrams it's more common to see it on the dependency edge:
instead of a visible node, you will see the dependency arrow annotated with
labels such as rejectNull.
The label signals that downstream work will be inhibited if the value coming
from Access is null.
Advanced
inhibitOnNull accepts an optional condition step via { if: $cond } (see
condition for common conditions). When provided, the value is
only inhibited if both the wrapped step yields null and $cond yields a
truthy value:
// Decode `$id` assuming it is the Node `ID` for a user:
const spec = specFromNodeId(userHandler, $id);
// This will represent the value `null` if the ID is null or invalid
const $userIdOrNull = spec.id;
// If `$id` is `null`, we want a literal `null` here.
// However, if `$id` is non-null, but `$userIdOrNull` yields `null`
// (because, for example, an invalid ID or ID for a different type was passed),
// then we want to inhibit all proceeding steps.
const $validUserIdOrNull = inhibitOnNull($userIdOrNull, {
if: condition("not null", $id),
});