All notable changes to this project will be documented in this file.
- Source code refactored into
src/modules: Single-filebase.tssplit intosrc/typing.ts(types, type inference,Union),src/errors.ts(error classes,parsePath),src/utils.ts(helpers, Proxy handlers), andsrc/base.ts(Base class). Unified re-exports fromindex.ts. Import paths unchanged (@bufferpunk/modelcore), but users importing directly frombase.tsmust update toindex.ts. - Named exports:
Baseis now a named export —import { Base } from '@bufferpunk/modelcore'(was default exportimport Base from ...). - Container runtime type guards:
isArraynow requiresvalue instanceof Array;isSetrequiresvalue instanceof Set;isMaprequiresvalue instanceof Map;isObjectrequiresObject.prototype.toString.call(value) === "[object Object]". A schema field{ type: Array }will reject non-array inputs,{ type: Set }rejects non-Set inputs, etc. This prevents subtle bugs in Union schemas and improves type safety. normalizeConfvalidatestypeis a function:{ type: undefined },{ type: null }, or{ type: "string" }now throwsSchemaDefinitionErrorat construction time (was silently accepted).isNaNcheck for Date and Number: placed check after coercion to ensure invalid dates and numbers are rejected.
- Extended test coverage: 8 new tests (60 total) —
{ type: undefined/null/"string" }schema rejection,Union(Array, String)value discrimination, scalar-only coerce tests, andUnion(Object, String)skipping object-key validation for string values.
- Module structure: Clean separation of concerns — types, errors, and utils each in their own file with proper exports.
- Performance: Optimized container type-dispatch with early runtime-type checks to avoid unnecessary iteration on mismatched Union branches.
Setschema type: Define{ type: Set, values: { type: String } }fields. Backed by a Proxy handler that validates.add()calls. Accepts anyIterableat construction — converts viaArray.from(). Object values with nestedkeysare supported.Mapschema type: Define{ type: Map, keys: { score: { type: Number, min: 0 } } }fields. Backed by a Proxy handler that validates.set()calls per-key schema. Proxies.get(),.has(),.delete(),.forEach(),.keys(),.values(),.entries()to the raw Map.- Named validation handlers:
Base.addValidationHandler(name, fn)andBase.removeValidationHandler(name)replace the anonymous array-based API. Duplicate names are silently ignored. Handlers live onconstructor.validationHandlers(aMap<string, Function>) and are shared via prototype chain. - Extended test coverage: 11 new tests covering Set/Map construction and mutation, handler duplicate-name dedup, prototype-chain sharing, and error metadata (
buildError,expectedfield values).
- Validation pipeline order: handlers now run after
enumcheck and beforeafterChecks(was afterafterChecks/validate). - Error
expectedfield now carries meaningful values (conf.type,conf.max,conf.min,conf.enum,"Array") instead ofnull. static versionremoved entirely (class-level versioning dropped;updateno longer setsthis.version).base.jssynced withbase.tsfor Set/Map/handler changes.
Base.autorequireglobal toggle: Control whether missing non-optional fields throw aRequiredError. SetBase.autorequire = falseto silently allow missing fields — useful for gradual schema adoption. Explicitrequired: true/optional: falsealways take precedence over the global flag.- Extended test coverage: 13 new tests covering validation handler lifecycle (calls, rejection, ordering, args, nested, error metadata) and autorequire behavior (default, true, false, precedence, defaults).
- Validation handler / middleware system (
Base.addValidationHandler(handler)): Register functions(conf, value, path) => voidthat run during everyvalidateTypecall, after built-in checks (type, min/max, enum) and beforeafterChecks. Handlers can throw to reject a value. Registered once onBase, applied to every model and every field automatically. FieldConfigindex signature ([key: string]: any): Schemas can now carry arbitrary metadata properties (e.g.,regex,minWords) alongside standard config keys, consumed by custom validation handlers.buildError()is now exported: Enables custom handlers to throw typedModelCoreErrorsubclasses with propersource,path,expected,received, andcodeproperties.
versioninstance property removed from type declaration (was redundant —ctor.versionhandles versioning at the class level).- Updated README with validation handler documentation,
buildErrorreference, autorequire toggle docs, and expanded API docs. - Updated
examples/user.tsto demonstrate customregexvalidation with a handler. base.jssynced withbase.tsforautorequireand handler loop logic.
-
This version brings significant performance improvements to the core model construction and validation logic, as well as bulk updates. This was achieved by eliminating the javascript Object.defineProperty() calls that slowed down execution. Benchmark results (100K iterations, Node 24):
Operation v1.2.0 v1.3.0 construct + validate~85K ops/sec ~383K ops/sec Model.create()factory method~92K ops/sec ~383K ops/sec batch update~46K ops/sec ~399K ops/sec
Union(...)helper for typed union schema fields and runtime validation.- support shorthand constructors inside nested
keysandvalues, so nested fields likemake: Stringwork naturally. requiredalias support foroptional: falseand clearer required-field intent.propertiesalias support forObjectfield schemas in addition tokeys.
- improved TypeScript inference for union fields and nested schema shorthand.
- updated README and docs with union support and nested shorthand examples.
- corrected schema typings so
Union(String, Number)behaves correctly withcreateFromand compile-time inference.
- Rich and detailed error handling with descriptive error classes to enable programmatic error handling and clearer error semantics.
- Removed redundant checks
- Fixed loop on error thrown during construction to properly set all properties to the error object instead of just the enumerable ones.
- These changes improve runtime safety and the test coverage baseline; see tests in
test/base.test.jsfor usage patterns.
- Project renamed from
@bufferpunk/schemato@bufferpunk/modelcoreto better reflect its focus on runtime entities and validation rather than just schema definition. - Improve TypeScript ergonomics: recommend
as constschemas and providecreateFromfactory for single-source-of-truth typed instantiation. - Map runtime constructors (including custom classes) to instance types for better editor hovers and instance validation.
- Harden array behavior: non-writable index properties, guarded
push/unshift/splicethat validate items, and forbidfillto maintain integrity. - Preserve schema literal types and avoid broad index signatures that produced
anyin editor hovers. - Expand test coverage: added/merged comprehensive tests covering arrays, immutability, nested validation, defaults, and custom types.
- Add GitHub Actions CI workflow to run build, tests, and coverage.
- Rewrite README to focus on technical usage and TypeScript guidance; extract manifesto into
manifesto.mdfor positioning and goals. createFrom()factory for typed model instantiation from staticschema.- Improved TypeScript mapped types to infer instance shapes from
schemadefinitions when used withas const. - Custom constructor handling so class types (e.g.,
Email) map to their instances at the type level and are validated at runtime. - Extensive tests covering mutation semantics and validation rules.
.github/workflows/ci.ymlto run build and tests on push/PR.
- Fixed array mutation edge-cases (splice/delete-only behavior) and ensured index descriptors are rebuilt after guarded mutations.
- Removed class-level coerce which is dangerous and not commonly needed; coercion should be opt-in per field or via constructor config.
- Install the new package:
npm install @bufferpunk/modelcore - Update imports from
@bufferpunk/schemato@bufferpunk/modelcore - If using TypeScript, update schema definitions to use
as const satisfies SchemaDefinitionfor better type inference, and use thecreateFromfactory method for instantiation to get typed instances. - Review the new README and manifesto for updated usage patterns and design philosophy.
-
Immutability error messages: Error message wording changed for consistency
// Before: "Cannot change immutable property 'name'" // After: "Cannot update immutable property 'name'" -
Property setter enforcement: Class-level immutability now prevents direct property assignment (not just
.update())class ImmutableUser extends Base { static immutable = true; static schema = { name: { type: String } }; } const user = new ImmutableUser({ name: "John" }); user.name = "Jane"; // Error: Cannot update immutable object of type ImmutableUser
-
json()method: Serialize instance to JSON stringconst user = new User({ name: "John" }); const jsonStr = user.json();
-
parseConfigparameter: Passcoerceandsafeoptions to constructor and.update()// Coerce string to Date on construction const user = new User({ createdAt: "2020-01-01" }, { coerce: true }); // Silently ignore validation errors during construction const user = new User({}, { safe: true });
-
Property setter validation: Direct property assignment now validates and revalidates values
const user = new User({ name: "John" }); user.name = " Jane "; // Runs beforeChecks/afterChecks hooks
-
Fixed nested object property leak: Nested
keysproperties now correctly attach to their parent object, not the root instance
- Property setters now persist validated values instead of discarding them
- Nested object child properties no longer leak to the root object during initialization
- Immutability is now enforced on direct property assignment (not just
.update())
-
Constructor signature changed: Removed
addVersionparameter from constructor. Version is now automatically included ifstatic versionis defined on the class.// Before new User(data, true); // After new User(data);
-
Hook names renamed:
beforeValidate→beforeChecksafterValidate→afterChecks
-
Array field config changed:
child→values// Before cars: { type: Array, child: { type: Object, children: { ... } } } // After cars: { type: Array, values: { type: Object, keys: { ... } } }
-
Object field config changed:
children→keys// Before address: { type: Object, children: { street: {...}, city: {...} } } // After address: { type: Object, keys: { street: {...}, city: {...} } }
-
Immutability support: Mark fields or entire classes as immutable
class ImmutableUser extends Base { static immutable = true; // entire class cannot be updated static schema = { id: { type: String, immutable: true }, // this field cannot change name: { type: String } }; }
-
Update method: Safely update instance properties after creation
const user = new User({ name: 'John' }); user.update({ name: 'Jane' }); // returns void, modifies instance
-
Better error messages: Property paths now include quotes for clarity
// Before: "Invalid value for address.street, expected one of: ..." // After: "Invalid value for 'address.street', expected one of: ..."
If upgrading from v2.x:
- Replace all
beforeValidatewithbeforeChecks - Replace all
afterValidatewithafterChecks - Replace all
childwithvaluesin Array types - Replace all
childrenwithkeysin Object types - Remove the second parameter from all constructor calls (version now auto-applies)
- If using immutability, enable with
static immutable = trueor field-levelimmutable: true - For instance updates, use the new
update()method instead of reassigning properties