lambda
lambda is an escape hatch!Lambda does not perform batching (see Warning: no
batching!). It's highly suitable for usage when you just
want to synchronously transform data (e.g. concatenating strings, mapping over
arrays, etc) but is almost never well suited to asynchronous topics — you
likely want loadOne() or loadMany() instead
in those cases.
Accepts a step (or multistep) and a callback; returns a step that represents calling the callback for each runtime value of the step.
Usage
function lambda<TIn extends Multistep, TOut>(
$input: TIn,
callback: (input: UnwrapMultistep<TIn>) => TOut | Promise<TOut>,
isSyncAndSafe = false,
): Step<TOut>;
$inputis a step or multistepcallbackis a function that takes the runtime values from$inputand returns a derivativeisSyncAndSafeshould only be specified if you are certain (see note below)
Don't define callback inline, otherwise it cannot be deduplicated. Instead define it at the top scope of your file (or import from another file):
function fullname([firstName, lastName]: [string, string]): string {
return `${firstName} ${lastName}`;
}
const objects = {
User: {
plans: {
fullName($user) {
const $firstName = $user.get("firstName");
const $lastName = $user.get("lastName");
// Similar calls to lambda can be deduplicated because `fullname`
// function is not redefined each call, instead the same reference
// persists between calls
return lambda([$firstName, $lastName], fullname, true);
},
},
},
};
The callback must not have side effects. If you need to do something with side
effects use sideEffect()
instead (which has a very similar API).
isSyncAndSafeIf you are 100% certain that your callback function:
- does not do any asynchronous work (no
Promise/async), - does not have any side effects,
- will not throw an error
then for the very best performance, you can pass the third argument,
isSyncAndSafe, as the value true. Do
not do this unless you are certain!
Warning: no batching!
lambda is an escape hatch that breaks you out of Grafast's batching;
you should only use it for the most trivial of operations (i.e. synchronous JS
code that doesn't perform heavy computation and wouldn't benefit from
batching).
In most cases, you should use loadOne instead. Here's what the
examples above might look like with loadOne - note that these callbacks
are now called with all the values at once, rather than one at a time:
const $oneBasedIndex = loadOne($zeroBasedIndex, (allN) =>
allN.map((n) => n + 1),
);
const $aPlusB = loadOne([$a, $b], (allAsAndBs) =>
allAsAndBs.map(([a, b]) => a + b),
);
lambda does not break batching of dependentsEven though lambda() itself does not perform batching, Grafast will wait for
all values to be processed before running dependents. From the dependents
perspective it's as if lambda did batch, and they will batch as normal.
In other words: usage of lambda() does not cause Grafast to go into the same
exponential query explosion that native GraphQL.js resolvers frequently suffer
from.
Examples
Single step
Typical usage passes a step and gets a new step back:
// Takes a step representing a zero-based index, and converts it to a one-based
// index by adding one to it.
const $oneBased = lambda($zeroBased, (zeroBased) => zeroBased + 1, true);
No input
Sometimes you don't need dependencies (though in many cases sideEffect may be
better suited here - e.g. Math.random() and new Date() don't require
dependencies but do change each time you call them, so you may not want to
deduplicate.
const $hostname1 = lambda(null, () => os.hostname());
// The above is equivalent to
const $hostname2 = lambda(constant(null), () => os.hostname());
Multistep - tuple
If you need to pass multiple steps, you can use the
list() or object() steps to do so.
To save a few characters you can pass the list of steps directly and we'll
automatically wrap it in list() for you via multistep():
// Passing an array of steps gives you the values as an array:
const $aPlusB1 = lambda([$a, $b], ([a, b]) => a + b, true);
// The above is equivalent to:
const $aPlusB2 = lambda(list([$a, $b]), ([a, b]) => a + b, true);
Multistep - object
Objects can be clearer than tuples by allowing you to label each value:
// Passing an object of steps gives you the values as an object:
const $aPlusB1 = lambda({ a: $a, b: $b }, ({ a, b }) => a + b, true);
// The above is equivalent to:
const $aPlusB2 = lambda(object({ a: $a, b: $b }), ({ a, b }) => a + b, true);