Skip to content

Utilities & Patterns

Utilities and patterns used throughout Max.

Batch<V, K>

Container for batch operation results. Maps keys to values with tracking.

// Build from list with key extractor
const batch = Batch.buildFrom(users).withKey(user => user.id);
// Access
batch.get("u1"); // User | undefined
batch.getOrThrow("u1"); // User (throws if missing)
batch.values(); // User[] (self-contained)
// Tracking
batch.isFullyResolved; // boolean
batch.unresolvableKeys; // Set<string>

Key principle: Values should be self-contained (include their key).

Common patterns:

// From EntityInputs
Batch.buildFrom(inputs).withKey(i => i.ref)
// From items with .id
Batch.byId(users)
// From record
Batch.fromRecord({ a: 1, b: 2 })

Page<T>

Pagination wrapper for collection results.

interface Page<T> {
items: T[];
hasMore: boolean;
cursor?: string;
total?: number;
}
// Create
const page = Page.from(items, hasMore, cursor);
// Use
if (page.hasMore) {
const next = await loadNext(page.cursor);
}

Brand

Type-safe nominal typing without runtime overhead.

SoftBrand

Allows naked assignment - use for IDs.

type UserId = Id<"user-id">; // Id<N> = SoftBrand<string, N>
const id: UserId = "u123"; // ✅ Works
const id2: string = id; // ✅ Works
type OrderId = Id<"order-id">;
const orderId: OrderId = id; // ❌ Error - different brands

HardBrand

Requires factory - use for validated values.

type RefKey = HardBrand<string, "ref-key">;
const key: RefKey = "..."; // ❌ Error
const key = RefKey.from(...); // ✅ Must use factory

Common branded types:

  • EntityType, EntityId, InstallationId - SoftBrand
  • RefKey - HardBrand (requires parsing)
  • LoaderName - SoftBrand

Type + Companion Object Pattern

Infrastructure types use namespace merging - one name for both type and value.

// Definition
export interface Ref<E, S> { ... }
export const Ref = StaticTypeCompanion({
local(def, id) { ... },
create(def, id, scope) { ... },
})
// Usage - one name works as both
const ref: Ref<AcmeUser> = Ref.local(AcmeUser, "u1");

Applies to: Ref, EntityDef, EntityResult, EntityInput, Page, Scope, Fields, Loader, Resolver, Batch

Import: Use bare import (no type modifier) to get both type and value.

// ✅ Good - gets both
import { Ref, EntityDef } from "@max/core";
// ❌ Avoid - only gets type
import type { Ref } from "@max/core";

StaticTypeCompanion is a zero-runtime marker function that documents the pattern.


Fields Selector

Explicit field selection for partial loading.

// Load specific fields
await engine.load(ref, Fields.select("name", "email"));
// Load all fields
await engine.load(ref, Fields.ALL);

Keyable

Objects that can be used as batch keys implement toKey().

interface Keyable {
toKey(): string;
}
// Ref implements this
const ref = AcmeUser.ref("u1");
ref.toKey(); // "local:AcmeUser:u1"
// Use in batches
Batch.buildFrom(items).withKey(item => item.ref); // Works!

Inspect

Custom console.log rendering for classes. Assigns once to the prototype via a static {} block - no per-instance cost.

import { inspect, Inspect } from "@max/core";
class TaskRunner {
name: string;
status: string;
config: object;
static {
Inspect(this, (self) => ({
format: "TaskRunner( %s | status=%s | %O )",
params: [self.name, self.status, self.config],
}));
}
}

console.log(runner) outputs:

TaskRunner( my-runner | status=running | { retries: 3, timeout: 5000 } )

The format string uses util.formatWithOptions specifiers:

  • %s - string (no quotes, no color)
  • %O - object (colored, depth-aware, auto-expands when large)
  • %d - number

MaxError

Composable error system with boundaries and cause chains. Each domain defines a boundary that owns its errors, provides wrap() for automatic cause chains, and is() for domain-level matching.

// Define a boundary and its errors
const Linear = MaxError.boundary("linear");
const ErrSyncFailed = Linear.define("sync_failed", { ... });
// Wrap at domain entry points - builds cause chains automatically
await Linear.wrap(ErrSyncFailed, async () => { ... });
// Catch by exact type, facet, or boundary
if (ErrSyncFailed.is(err)) { ... }
if (MaxError.has(err, NotFound)) { ... }
if (Linear.is(err)) { ... }

See Error System for the full guide: designing errors, choosing facets, wrapping boundaries, shared boundaries, and conventions.


Quick Reference

UtilityPurposeExample
Batch<V, K>Batched resultsBatch.buildFrom(items).withKey(fn)
Page<T>PaginationPage.from(items, hasMore, cursor)
Id<Name>Soft-branded IDstype UserId = Id<"user-id">
FieldsField selectionFields.select("name", "email")
ScopeInstallation contextScope.local(), Scope.system(id)
StaticTypeCompanionType+Value patternDocuments dual-use names
InspectCustom console.logInspect(this, self => ({ format, params }))
MaxErrorComposable errorsMaxError.boundary("linear"), full guide