Skip to content
Published alpha · next channelRead as Markdown

Class facades and explicit paths ​

createBuilderClass(factory, options) creates a base class backed by the same immutable runtime as createBuilder. createSchemaBuilderClass(schema, factory, options) retains separate input/output types. builderClass(() => builder) also wraps a prepared native adapter or an already configured builder.

ts
class UserBuilder extends createBuilderClass((id: number) => ({ id, name: '' })) {
  withName(name: string) {
    return this.with({ name });
  }
}
const users = new UserBuilder().withName('Ada').with({ id: 1 });
const user = users.build(2);
const asynchronous = users.transformAsync(async (value) => value).withName('Grace');
const other = await asynchronous.buildAsync(3);

Ordinary and schema methods retain subclass fluent methods; asynchronous transitions remove synchronous build methods in the type system, including after custom fluent methods. Factories are not invoked when configuring a branch. Required, optional, and multiple factory arguments remain unchanged. Construction accepts an optional initial patch, where omitted/undefined means no initial patch.

Facades are for generated or method-only subclasses. Branches preserve the prototype and copy own property descriptors, without rerunning constructors. They do not clone subclass private fields or bind instance arrow functions to a new instance. Keep application state in the fixture/factory, not private subclass fields. The runtime state itself is held in a private WeakMap.

Typed nested changes ​

Use setPath(value, path, replacement) and omitPath(value, path) inside an explicit transform. Only the containers along the path are copied. Arrays are indexed numerically, tuples retain index bounds, and array omission is forbidden. Optional-property omission is distinct from null or undefined. An absent intermediate is an error: replace its complete parent instead. Unions and native values are atomic; a path cannot change only a discriminant and invent a complete variant. Paths are limited to eight segments, and type checking validates the supplied path without enumerating the entire application type graph.

ts
const users = createBuilder(() => ({ profile: { name: 'Ada' }, roles: ['reader'] }));
const admins = users.transform((value) => setPath(value, ['roles', 0], 'admin'));

Only own data properties of arrays and plain records are traversed. Inherited properties and accessors are rejected. Null-prototype records, symbol keys, and prototype-looking own keys are handled without invoking prototype setters. Unchanged nested values retain their identity; use the existing clone policy when complete fixture isolation is desired.

Mimlet · alpha · MIT licensed