Skip to main content

Unique Functor

The unique functor module (backend/src/unique_functor.js) provides a two-level identity system for creating collision-resistant, type-safe keys at module scope.


The Problem

Plain string keys are fragile. Suppose two unrelated modules both decide to call sleeper.withMutex("worker", ...). They will silently contend on the same lock even though they have nothing to do with each other. The bug only shows up as unexpected serialisation, and only when both code paths happen to run concurrently.

The unique-functor module makes this class of bug impossible: every lock key is derived from an identity object that is registered globally at startup, and the registration throws immediately if the name is already taken. The conflict becomes a loud crash at module load time rather than a silent race condition at runtime.


Concepts

UniqueFunctor

A UniqueFunctor is a named, module-scope singleton. Think of it as a named constructor for keys. Once created under a given name, no second functor can ever carry that same name inside the same process.

const myFunctor = makeUniqueFunctor("my-subsystem");

Calling makeUniqueFunctor twice with the same name throws:

Error: Unique functor with name "my-subsystem" already exists

This check fires at module initialisation time, before any request has been served, making it easy to catch.

UniqueTerm

A UniqueTerm is produced by instantiating a functor with a list of string arguments. It is the actual key object used at runtime.

const key = myFunctor.instantiate(["/home/user/repo"]);

A functor can be instantiated as many times as needed. The same functor with the same arguments always serializes to the same string; the same functor with different arguments produces different keys.


Serialization

UniqueTerm.serialize() returns a canonical string of the form:

<functorName>(<arg1>,<arg2>,...)

For example:

Functor nameArgumentsserialize() result
"gitstore-operation"["/home/user/repo"]"gitstore-operation(/home/user/repo)"
"migration"["v2", "v3"]"migration(v2,v3)"
"incremental-graph-operations"[]"incremental-graph-operations()"

This string is consumed internally by sleeper.withMutex as the map key for the promise chain. Application code never has to construct or compare these strings directly.


Nominal Typing

Both UniqueFunctor and UniqueTerm carry a __brand field that is declared but never assigned. This makes the types structurally opaque from the perspective of JSDoc type inference: a plain object { name, args, serialize } will not satisfy the UniqueTerm type even if it has the right structure. The only way to obtain a value that satisfies the type is to go through makeUniqueFunctor and .instantiate().


Usage Pattern

The recommended pattern is:

  1. At module scope — create the functor once.
  2. Per operation — instantiate a term with the runtime parameters.
const { makeUniqueFunctor } = require("../../unique_functor");

// Registered once when the module is first loaded.
const myFunctor = makeUniqueFunctor("my-subsystem");

async function doWork(capabilities, resourcePath) {
const key = myFunctor.instantiate([resourcePath]);
return capabilities.sleeper.withMutex(key, async () => {
// … exclusive access to resourcePath …
});
}

When no parameterisation is needed (a single global lock for a subsystem), pass an empty array:

const MUTEX_KEY = makeUniqueFunctor("my-global-lock").instantiate([]);

Type Guards

The module exports isUniqueTerm and isUniqueFunctor for instanceof-based narrowing:

const { isUniqueTerm, isUniqueFunctor } = require("./unique_functor");

if (isUniqueTerm(value)) {
// value is UniqueTermClass here
console.log(value.serialize());
}

Examples in the Codebase

LocationFunctor nameArgumentsPurpose
backend/src/gitstore/mutex.js"gitstore-operation"[workingPath]Serialises checkpoint and transaction per repository path
backend/src/generators/incremental_graph/lock.js"incremental-graph-operations"[]Global lock for incremental-graph invalidate, pull, and runMigration

API Reference

makeUniqueFunctor(name)

ParameterTypeDescription
namestringGlobally unique human-readable identifier.

Returns a UniqueFunctor. Throws if name was already registered.

UniqueFunctor.instantiate(args)

ParameterTypeDescription
argsstring[]Runtime parameters for this term.

Returns a UniqueTerm.

UniqueTerm.serialize()

Returns a string of the form "<name>(<arg1>,<arg2>,…)".

isUniqueTerm(object)

Returns true if object is a UniqueTerm instance.

isUniqueFunctor(object)

Returns true if object is a UniqueFunctor instance.