Skip to content

Function Naming

Language: JavaScript

Every exported function in a Superloom module begins with a verb from the catalog below. The verb determines what the function does and what it returns. A function whose name does not begin with a cataloged verb is a defect, unless an Exceptions row in conventions-registry.md names it and records why.

This document is the single source of truth for function naming. The registry carries the settled rows; this page carries the reasoning and the full verb catalog. Module naming, parameter naming, and public field naming live in code-formatting.md; this page covers function verbs and return shapes only.

On This Page


The Rule

Every exported function begins with a verb from the catalog. The verb is chosen by what the function does, not by what it returns. Two functions that do the same thing begin with the same verb, even if one returns a String and the other returns an Object. Two functions that do different things begin with different verbs, even if both return an Object.

Exception: React component factories. A React component factory is PascalCase and noun-named, because the framework's own contract requires it (JSX requires a capitalized identifier to distinguish a component from an HTML tag). The verb rule applies to non-component exported functions. A component library's PascalCase exports are exempt by design, not by registry row.

When naming a new function, search the catalog for the verb that matches the operation. If no cataloged verb fits, the operation may be two operations in one function, and the fix is to split the function, not to invent a verb. If the operation is genuinely new, add it to the catalog here and to the registry, with evidence, before using it.


Verb Catalog

Each row names the verb, what it does, what it returns, one real example from a shipped module, and the verb it is most often confused with.

VerbDoesReturnsExampleConfused with
buildComposes several independent parts into a new artifactThe artifact, in whatever shape it naturally takes: String for text (buildQuery), Object for structured (buildResponseEnvelope)sqlite.buildQuery(sql, params)format (input shape differs), create (no persistence)
createProduces a resource or a durable identity that did not exist beforeThe created resource or identityauth.createAuthId() returns a String idgenerate (no persistence), build (parts versus identity)
generateProduces a fresh derived value with no persistence and no identityThe derived values3.generateUploadUrlPut(options) returns a signed URL Stringcreate (resource or identity), build (parts composed)
formatRenders one logical value into a different notation of that same valueAlways a String, carrying the same information as the inputmoney.formatCurrency(amount, code)build (one value versus many parts), parse (reverse direction)
parseReads a notation back into its structured formThe structured form, or null/envelope on failureauth.parseAuthId(id) returns an Objectformat (reverse direction), disjoin (no build direction)
disjoinDecomposes an external format that has no build directionThe decomposed partsutils.disjoinUrl(url)parse (has a build direction)
getReads a value from a source that can fail (I/O, engine, external state)Envelope { success, data, error } when I/O can fail; bare value when it cannotmongodb.getRecord(instance, collection, filter) returns an envelopelist (plural), load (bulk into memory)
listReads a collection of values from a source that can failEnvelope with a plural payload (records, keys, values, results)mongodb.query(instance, collection, filter, options)get (singular)
loadReads a bulk resource into memory for later useEnvelope, or bare value when the load cannot failfont.loadManifest(manifest)get (single value)
setWrites a value to a slot, overwriting whatever was thereEnvelopemongodb.setRecord(instance, collection, record, options)update (partial mutation), write (synonym in some domains)
updateMutates part of an existing record without replacing the wholeEnvelopemongodb.updateRecord(instance, collection, filter, patch, options)set (full replace), write (synonym in some domains)
writePersist a record to a store, the domain-neutral verb for "put a record"Envelopesqlite.write(instance, table, row)set (slot overwrite), update (partial mutation)
deleteRemoves a record or resource from a store permanentlyEnvelope with a deleted_count fieldmongodb.deleteRecord(instance, collection, filter, options)remove (in-memory), clear (wipe all), cleanup (expired only)
removeTakes an item out of an in-memory collection, not a persistent storeEnvelope with a removed_count field, or bare when in-memory onlyidle.removeIdleHandlers()delete (persistent), clear (all items)
clearWipes an entire collection or registry, leaving the container intactEnvelope with a cleared_count fieldlocalstorage.clear()delete (one record), cleanup (selective)
cleanupRemoves expired or stale items from a collectionEnvelope with a cleaned_count or deleted_count fieldauth.cleanupExpiredSessions(instance)clear (all items), delete (specific record)
isAnswers a yes-or-no question about a value or stateBare Boolean. Bad input throws TypeError; an operation that can fail at runtime is get, not isutils.isNumber(arg) returns true or falsehas (existence), validate (returns errors)
hasAnswers whether a key or slot exists, as a pure in-memory check that cannot failBare Boolean. Same throw rule as isregistry.hasToken(token) returns true or falseis (state of a value), get (existence check that can fail)
validateChecks config or an adapter contract at load time, throwing on failurefalse on success, Error[] on failure (the model-layer convention), or throws at load time for configfont.validators.validateConfig(config)assert (throws on programmer error), check (domain logic)
assertThrows TypeError on a programmer error, synchronously, never returns an envelopeNever returns; throws or falls throughauth.validators.assertOptionsObject(options)validate (returns errors), check (domain logic)
checkRuns a domain-specific check that returns a Boolean or a result object, not an error arrayBoolean or a domain result objectpolicy.checkTotal(instance, options)validate (error array), assert (throws)
runExecutes a command or a prepared operation against an engineEnvelopedynamodb.runQueryCommand(command)build (constructs the command)
sendDispatches a message to an external service for deliveryEnvelope with provider response fieldsemail.sendEmail(instance, message)write (no persistence), run (not an engine command)
signProduces a cryptographic signature or signed token from a valueEnvelope { success, token, error }email.signUnsubscribeToken(instance, email)generate (no crypto), build (no signature)
verifyChecks a cryptographic signature or token and extracts the original valueEnvelope { success, data, error }email.verifyUnsubscribeToken(instance, token)check (no crypto), validate (config-time)

The Sync suffix

A function that has both an asynchronous and a synchronous variant carries the Sync suffix on the synchronous one. The asynchronous variant has no suffix. The suffix is used only when both variants exist for the same operation; a synchronous-only function never carries it.

VariantNameWhen used
AsyncgetRecord(key)Default; returns a Promise
SyncgetRecordSync(key)First-render reads where awaiting is not possible

Return Shape by Verb Class

Predicates: is and has

A predicate answers yes or no. It returns a bare Boolean. It never carries an error slot.

  • Bad input is a programmer error: throw TypeError synchronously, the same as every other validation
  • An operation that can fail at runtime is not a predicate. It is get and returns an envelope
javascript
// Correct: pure predicate, bare Boolean
isRegistered: function (familyName) {
  Validators.assertFamilyName(familyName);
  return Object.prototype.hasOwnProperty.call(registry.families, familyName);
},

// Wrong: predicate returning an envelope
isRegistered: function (familyName) {
  if (badInput) { return { success: false, registered: false, error: ... }; }
  return { success: true, registered: ..., error: null };
},

A has that checks existence against an engine that can be unavailable is not a predicate either. It is a get operation that checks existence, and it keeps its envelope under the name getRecordExists:

javascript
// Correct: existence check that can fail, envelope, get verb
getRecordExists(key) -> { success, exists, error }

// Wrong: same operation named as a predicate
hasRecord(key) -> { success, exists, error }

Getters: get, list, load

A get returns a bare value when the call cannot fail for any reason other than "not found", and an envelope when it can fail for an operational reason (I/O, driver, engine):

SituationReturn shapeExample
Pure computation or a read of data already in handBare value or nullgetAge(instance) returns a Number
I/O against a driver, engine, or external stateEnvelope { success, data, error }getRecord(instance, collection, filter)

The "not found" case is not an operational error. A get that returns null for not-found and an envelope for I/O failure is correct. A get that wraps a not-found in { success: false, error } is treating a normal outcome as an error, which is the failure the envelope exists to prevent.

list and load follow the same rule: envelope when I/O can fail, bare when it cannot.

Mutators: set, update, write, delete, remove, clear, cleanup

Every mutator that touches a persistent store or an external engine returns an envelope. A mutator that operates on in-memory state only, and cannot fail, may return a bare value or a count.

The count field in a mutator envelope is the past participle of that operation's own verb: clear returns cleared_count, remove returns removed_count, delete returns deleted_count. See registry rows 18 and 19.

Constructors: build, create, generate

These return the constructed artifact directly, never an envelope. They are pure functions of their inputs; they have no operational error to report. Bad input throws TypeError.

Validators: validate, assert, check

VerbReturnsThrows
validatefalse on success, Error[] on failure (model layer); or throws at load time (config and adapter contracts)Only on internal misuse
assertNothing; throws TypeError on programmer errorYes, always, on bad input
checkA Boolean or a domain result objectNo

The split is semantic and already consistent across the catalog. validate is for config and contract validation at load time. assert is for programmer-error guards at call time. check is for domain-specific logic that does not fit either mold.

Cryptographic: sign, verify

VerbReturnsThrows
signEnvelope { success, token, error }TypeError on bad input (non-string, empty) or missing secret
verifyEnvelope { success, data, error } on failure; { success, data, error: null } on successTypeError on bad input (non-string, empty) or missing secret

sign produces a cryptographic signature or signed token from a value. verify checks a cryptographic signature or token and extracts the original value. Both return envelopes because they depend on a secret that may be misconfigured at runtime. Bad input (non-string, empty token) throws TypeError synchronously; a missing or empty secret throws TypeError because it is a configuration error the caller must fix before calling.


Confusable Pairs

build versus format

Both can return a String. The test is the input shape, not the return type.

  • format takes one logical value plus render modifiers, and the output carries the same information as the input, only in a different notation. formatCurrency(amount, code) takes one amount and renders it.
  • build takes several independent parts and composes them into a new artifact. buildQuery(sql, params) takes a SQL template and bind values and composes a query.

If removing one input argument still leaves a meaningful operation, it is build. If removing one input argument makes the operation meaningless, it is format.

create versus generate versus build

  • create produces a resource or a durable identity. The output persists or is meant to persist. createAuthId produces an id that is stored.
  • generate produces a fresh derived value with no persistence and no identity. generateUploadUrlPut produces a signed URL that is consumed, not stored.
  • build composes several independent parts into a new artifact. buildAddress composes fields into a normalized address object.

createAddress returns a plain object with no persistence and no identity. It composes fields, so it is buildAddress, not createAddress.

is versus has versus validate

  • is answers a yes-or-no question about the state of a value. isReady() asks "is the loader ready?"
  • has answers whether a key or slot exists, as a pure in-memory check. hasToken(token) asks "is this token in the registry?"
  • validate returns an array of errors, not a Boolean. It is not a predicate; it is a check that collects everything wrong with an input.

Both is and has return a bare Boolean. An existence check that can fail at runtime is getRecordExists, not has.

get versus list versus load

  • get reads one value. getRecord(instance, collection, filter) returns one record or null.
  • list reads a collection of values. query(instance, collection, filter, options) returns an array of records.
  • load reads a bulk resource into memory for later use. loadManifest(manifest) reads a font manifest into the registry.

delete versus remove versus clear versus cleanup

  • delete removes a record or resource from a persistent store. deleteRecord(instance, collection, filter) deletes from the database.
  • remove takes an item out of an in-memory collection. removeIdleHandlers() removes handlers from an in-memory set.
  • clear wipes an entire collection or registry, leaving the container intact. clear() wipes the key-value store.
  • cleanup removes expired or stale items from a collection. cleanupExpiredSessions(instance) removes sessions past their TTL.

The same operation against two backends uses the same verb. dropCollection and deleteTable are the same operation against two backends, so both are deleteCollection.

set versus update versus write

  • set writes a value to a slot, overwriting whatever was there. setRecord(instance, collection, record, options) writes the full record.
  • update mutates part of an existing record without replacing the whole. updateRecord(instance, collection, filter, patch, options) applies a partial patch.
  • write is the domain-neutral verb for "put a record" when the store's native verb reads awkwardly. write(instance, table, row) writes a row to SQLite.

Domain-specific verbs that carry a semantic write does not are kept. addLog appends, and writeLog loses the append semantic. The split is recorded as an intentional exception in the registry, not unified away.


Banned Verbs

These verbs are banned from new code and enforced by the shared ESLint config. Each has a settled alternative.

Banned verbWhyUse instead
constructInvents a synonym for build with no semantic gainbuild
deconstructInvents a synonym for disjoin with no semantic gaindisjoin
readAmbiguous between get (one value) and load (bulk); the catalog has both, so read adds nothingget or load
ensureHides whether the function creates or checks; create and assert are both more specificcreate or assert
transformHides the direction; format and parse are both more specificformat or parse

Banned naming shape: xToY conversion names

A function named secondsToTimeString or dateStringToDataSet uses a xToY shape that does not begin with a verb. These are banned. The direction determines the verb: rendering to a notation is format, reading a notation back is parse.

BannedUse instead
secondsToTimeStringformatSeconds
dateStringToDataSetparseDateString

The xToY pattern cannot be enforced by a regex lint rule without false positives, so it is enforced by the module audit workflow's verb doctrine check, not by ESLint.


Naming Shape Rules

Verb first, always

Every exported function begins with a verb from the catalog. The noun follows the verb. buildQuery, not queryBuild. deleteRecord, not recordDelete.

No verb in the middle

A function name with a verb in the middle, such as absenteeKeysCheckObject, is banned. The verb goes first: checkAbsenteeKeys. The object the check runs against is the parameter, not part of the name.

No noun first

A function name that begins with a noun, such as commandAddRecord, is banned. The verb goes first. If the function builds a command, it is buildAddRecordCommand. If it runs a command, it is runAddRecordCommand.

Multi-HTTP-method suffix

When two or more functions do the same thing with different HTTP methods, the method is a suffix on the verb-noun name: generateUploadUrlPut, generateUploadUrlPost. A single-method function never carries the suffix. See pitfalls-migration.md for the failure modes this prevents.


Config Key Casing

Config keys are SCREAMING_SNAKE_CASE, including keys nested inside a SCREAMING parent. The one exception is injected live objects, which stay PascalCase because they name a capability, not a data value.

Key typeCasingExample
Data key (string, number, boolean)SCREAMING_SNAKE_CASETABLE_NAME, TOTAL_MAX, EVICT_OLDEST_ON_LIMIT
Nested data key inside a SCREAMING parentSCREAMING_SNAKE_CASELIMITS: { TOTAL_MAX: 20 }
Injected live objectPascalCaseStore, Adapter, Lib.SQL

The defence of lowercase nested keys was that lowercase marks per-instance data from the composition root. But a key like total_max is a module constant one level down, so the split does not track the distinction it claims to. PascalCase stays for live objects, where the capability-naming rule in composition-and-adapters.md already governs the name.

This rule governs config keys only. Public return fields stay snake_case per code-formatting.md - Public Data Field Naming. A return field is never SCREAMING-cased.

Released under the MIT License.