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 extractorconst batch = Batch.buildFrom(users).withKey(user => user.id);
// Accessbatch.get("u1"); // User | undefinedbatch.getOrThrow("u1"); // User (throws if missing)batch.values(); // User[] (self-contained)
// Trackingbatch.isFullyResolved; // booleanbatch.unresolvableKeys; // Set<string>Key principle: Values should be self-contained (include their key).
Common patterns:
// From EntityInputsBatch.buildFrom(inputs).withKey(i => i.ref)
// From items with .idBatch.byId(users)
// From recordBatch.fromRecord({ a: 1, b: 2 })Page<T>
Pagination wrapper for collection results.
interface Page<T> { items: T[]; hasMore: boolean; cursor?: string; total?: number;}
// Createconst page = Page.from(items, hasMore, cursor);
// Useif (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"; // ✅ Worksconst id2: string = id; // ✅ Works
type OrderId = Id<"order-id">;const orderId: OrderId = id; // ❌ Error - different brandsHardBrand
Requires factory - use for validated values.
type RefKey = HardBrand<string, "ref-key">;const key: RefKey = "..."; // ❌ Errorconst key = RefKey.from(...); // ✅ Must use factoryCommon branded types:
EntityType,EntityId,InstallationId- SoftBrandRefKey- HardBrand (requires parsing)LoaderName- SoftBrand
Type + Companion Object Pattern
Infrastructure types use namespace merging - one name for both type and value.
// Definitionexport interface Ref<E, S> { ... }export const Ref = StaticTypeCompanion({ local(def, id) { ... }, create(def, id, scope) { ... },})
// Usage - one name works as bothconst 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 bothimport { Ref, EntityDef } from "@max/core";
// ❌ Avoid - only gets typeimport 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 fieldsawait engine.load(ref, Fields.select("name", "email"));
// Load all fieldsawait engine.load(ref, Fields.ALL);Keyable
Objects that can be used as batch keys implement toKey().
interface Keyable { toKey(): string;}
// Ref implements thisconst ref = AcmeUser.ref("u1");ref.toKey(); // "local:AcmeUser:u1"
// Use in batchesBatch.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 errorsconst Linear = MaxError.boundary("linear");const ErrSyncFailed = Linear.define("sync_failed", { ... });
// Wrap at domain entry points - builds cause chains automaticallyawait Linear.wrap(ErrSyncFailed, async () => { ... });
// Catch by exact type, facet, or boundaryif (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
| Utility | Purpose | Example |
|---|---|---|
Batch<V, K> | Batched results | Batch.buildFrom(items).withKey(fn) |
Page<T> | Pagination | Page.from(items, hasMore, cursor) |
Id<Name> | Soft-branded IDs | type UserId = Id<"user-id"> |
Fields | Field selection | Fields.select("name", "email") |
Scope | Installation context | Scope.local(), Scope.system(id) |
StaticTypeCompanion | Type+Value pattern | Documents dual-use names |
Inspect | Custom console.log | Inspect(this, self => ({ format, params })) |
MaxError | Composable errors | MaxError.boundary("linear"), full guide |