🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@cerios/cerios-builder

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cerios/cerios-builder - npm Package Compare versions

Comparing version
1.1.0
to
1.2.0
+104
-3
dist/index.d.mts

@@ -18,2 +18,18 @@ /**

/**
* Helper type to represent a path through an object structure
*/
type PathImpl<T, K extends keyof T = keyof T> = K extends string | number ? T[K] extends Record<string, any> ? T[K] extends Array<any> ? K : K | `${K}.${PathImpl<T[K]> & string}` : K : never;
type Path<T> = PathImpl<T>;
type PathValue<T, P> = P extends keyof T ? T[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : never : never;
/**
* Type-safe template for defining required fields using an array of paths.
* Simply list the paths that are required.
*/
type RequiredFieldsTemplate<T> = ReadonlyArray<Path<T>>;
/**
* Cache the root key extraction to avoid repeated computation
* @internal
*/
type RootKey<P extends string> = P extends `${infer K}.${string}` ? K : P;
/**
* Abstract base class for creating type-safe builders with automatic property setters and compile-time validation of required fields.

@@ -28,2 +44,3 @@ *

* class MyTypeBuilder extends CeriosBuilder<MyType> {
* static requiredTemplate: RequiredFieldsTemplate<MyType> = ['foo'];
* setFoo(value: string) { return this.setProperty('foo', value); }

@@ -36,3 +53,3 @@ * addBar(value: number) { return this.addToArrayProperty('bar', value); }

* .addBar(42)
* .build();
* .buildSafe(); // Validates that 'foo' is set
* ```

@@ -45,8 +62,48 @@ *

/**
* Template defining which fields are required for this builder.
* Subclasses should override this to specify their required fields as an array of paths.
* The template is type-safe - only valid paths from type T can be used.
*/
static requiredTemplate?: ReadonlyArray<string>;
/**
* Instance-level required fields that can be populated dynamically.
* This allows adding required fields at runtime via the setRequiredFields method.
* @private
*/
private _requiredFields;
/**
* Sets the required fields for this builder instance.
* This allows you to dynamically define which fields are required.
*
* @param fields - Array of dot-notation paths to required fields
* @returns The builder instance for chaining
*
* @example
* ```typescript
* const builder = new MyBuilder({})
* .setRequiredFields(['path.to.field1', 'path.to.field2'])
* .setField1('value1')
* .setField2('value2')
* .buildSafe();
* ```
*/
setRequiredFields(fields: ReadonlyArray<Path<T>>): this;
/**
* Gets the combined required fields from both the static template and instance-level fields.
* @private
*/
private getRequiredTemplate;
/**
* Validates that all fields in the required template have been set.
* @private
*/
private validateRequiredFields;
/**
* Creates a new builder instance. Intended to be called by subclasses.
*
* @param _actual - The current partial state of the object being built
* @param _requiredFields - Optional array of required field paths to preserve across instances
* @protected
*/
protected constructor(_actual: Partial<T>);
protected constructor(_actual: Partial<T>, _requiredFields?: RequiredFieldsTemplate<T>);
/**

@@ -71,2 +128,23 @@ * Sets a property value and returns a new builder instance with updated type state.

/**
* Sets a deeply nested property value and returns a new builder instance with updated type state.
* This method uses dot notation to set nested properties in a type-safe way.
*
* @template P - The property path (e.g., "parent.child.grandchild")
* @param path - The dot-notation path to the property
* @param value - The value to assign to the nested property
* @returns A new builder instance with the nested property set
* @protected
*
* @example
* ```typescript
* builder.setNestedProperty('Order.Details.CustomerId', 'value')
* ```
*/
protected setNestedProperty<P extends Path<T>>(path: P, value: PathValue<T, P>): this & CeriosBrand<Pick<T, Extract<RootKey<P & string>, keyof T>>>;
/**
* Deep clone helper for nested objects
* @private
*/
private deepClone;
/**
* Adds a value to an array property and returns a new builder instance with updated type state.

@@ -96,2 +174,25 @@ * This method is intended to be wrapped by concrete builder methods in subclasses for array properties.

/**
* Builds the final object with runtime validation using the requiredTemplate.
* This method validates that all fields in the requiredTemplate array are present.
*
* @returns The fully built object of type T
* @throws {Error} If any required field is missing at runtime
*
* @example
* ```typescript
* class MyBuilder extends CeriosBuilder<MyType> {
* static requiredTemplate: RequiredFieldsTemplate<MyType> = [
* 'path.to.field1',
* 'path.to.field2'
* ];
* }
*
* const obj = new MyBuilder({})
* .setRequiredField1("value1")
* .setRequiredField2("value2")
* .buildSafe(); // Validates that both required fields are present
* ```
*/
buildSafe(): T;
/**
* Builds a partial object, which may not have all required fields set.

@@ -105,2 +206,2 @@ * This is useful for scenarios where you want to inspect or validate the current state before finalizing.

export { type CeriosBrand, CeriosBuilder };
export { type CeriosBrand, CeriosBuilder, type RequiredFieldsTemplate };

@@ -18,2 +18,18 @@ /**

/**
* Helper type to represent a path through an object structure
*/
type PathImpl<T, K extends keyof T = keyof T> = K extends string | number ? T[K] extends Record<string, any> ? T[K] extends Array<any> ? K : K | `${K}.${PathImpl<T[K]> & string}` : K : never;
type Path<T> = PathImpl<T>;
type PathValue<T, P> = P extends keyof T ? T[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : never : never;
/**
* Type-safe template for defining required fields using an array of paths.
* Simply list the paths that are required.
*/
type RequiredFieldsTemplate<T> = ReadonlyArray<Path<T>>;
/**
* Cache the root key extraction to avoid repeated computation
* @internal
*/
type RootKey<P extends string> = P extends `${infer K}.${string}` ? K : P;
/**
* Abstract base class for creating type-safe builders with automatic property setters and compile-time validation of required fields.

@@ -28,2 +44,3 @@ *

* class MyTypeBuilder extends CeriosBuilder<MyType> {
* static requiredTemplate: RequiredFieldsTemplate<MyType> = ['foo'];
* setFoo(value: string) { return this.setProperty('foo', value); }

@@ -36,3 +53,3 @@ * addBar(value: number) { return this.addToArrayProperty('bar', value); }

* .addBar(42)
* .build();
* .buildSafe(); // Validates that 'foo' is set
* ```

@@ -45,8 +62,48 @@ *

/**
* Template defining which fields are required for this builder.
* Subclasses should override this to specify their required fields as an array of paths.
* The template is type-safe - only valid paths from type T can be used.
*/
static requiredTemplate?: ReadonlyArray<string>;
/**
* Instance-level required fields that can be populated dynamically.
* This allows adding required fields at runtime via the setRequiredFields method.
* @private
*/
private _requiredFields;
/**
* Sets the required fields for this builder instance.
* This allows you to dynamically define which fields are required.
*
* @param fields - Array of dot-notation paths to required fields
* @returns The builder instance for chaining
*
* @example
* ```typescript
* const builder = new MyBuilder({})
* .setRequiredFields(['path.to.field1', 'path.to.field2'])
* .setField1('value1')
* .setField2('value2')
* .buildSafe();
* ```
*/
setRequiredFields(fields: ReadonlyArray<Path<T>>): this;
/**
* Gets the combined required fields from both the static template and instance-level fields.
* @private
*/
private getRequiredTemplate;
/**
* Validates that all fields in the required template have been set.
* @private
*/
private validateRequiredFields;
/**
* Creates a new builder instance. Intended to be called by subclasses.
*
* @param _actual - The current partial state of the object being built
* @param _requiredFields - Optional array of required field paths to preserve across instances
* @protected
*/
protected constructor(_actual: Partial<T>);
protected constructor(_actual: Partial<T>, _requiredFields?: RequiredFieldsTemplate<T>);
/**

@@ -71,2 +128,23 @@ * Sets a property value and returns a new builder instance with updated type state.

/**
* Sets a deeply nested property value and returns a new builder instance with updated type state.
* This method uses dot notation to set nested properties in a type-safe way.
*
* @template P - The property path (e.g., "parent.child.grandchild")
* @param path - The dot-notation path to the property
* @param value - The value to assign to the nested property
* @returns A new builder instance with the nested property set
* @protected
*
* @example
* ```typescript
* builder.setNestedProperty('Order.Details.CustomerId', 'value')
* ```
*/
protected setNestedProperty<P extends Path<T>>(path: P, value: PathValue<T, P>): this & CeriosBrand<Pick<T, Extract<RootKey<P & string>, keyof T>>>;
/**
* Deep clone helper for nested objects
* @private
*/
private deepClone;
/**
* Adds a value to an array property and returns a new builder instance with updated type state.

@@ -96,2 +174,25 @@ * This method is intended to be wrapped by concrete builder methods in subclasses for array properties.

/**
* Builds the final object with runtime validation using the requiredTemplate.
* This method validates that all fields in the requiredTemplate array are present.
*
* @returns The fully built object of type T
* @throws {Error} If any required field is missing at runtime
*
* @example
* ```typescript
* class MyBuilder extends CeriosBuilder<MyType> {
* static requiredTemplate: RequiredFieldsTemplate<MyType> = [
* 'path.to.field1',
* 'path.to.field2'
* ];
* }
*
* const obj = new MyBuilder({})
* .setRequiredField1("value1")
* .setRequiredField2("value2")
* .buildSafe(); // Validates that both required fields are present
* ```
*/
buildSafe(): T;
/**
* Builds a partial object, which may not have all required fields set.

@@ -105,2 +206,2 @@ * This is useful for scenarios where you want to inspect or validate the current state before finalizing.

export { type CeriosBrand, CeriosBuilder };
export { type CeriosBrand, CeriosBuilder, type RequiredFieldsTemplate };

@@ -33,8 +33,74 @@ "use strict";

* @param _actual - The current partial state of the object being built
* @param _requiredFields - Optional array of required field paths to preserve across instances
* @protected
*/
constructor(_actual) {
constructor(_actual, _requiredFields) {
this._actual = _actual;
/**
* Instance-level required fields that can be populated dynamically.
* This allows adding required fields at runtime via the setRequiredFields method.
* @private
*/
this._requiredFields = /* @__PURE__ */ new Set();
if (_requiredFields) {
this._requiredFields = /* @__PURE__ */ new Set([..._requiredFields]);
}
}
/**
* Sets the required fields for this builder instance.
* This allows you to dynamically define which fields are required.
*
* @param fields - Array of dot-notation paths to required fields
* @returns The builder instance for chaining
*
* @example
* ```typescript
* const builder = new MyBuilder({})
* .setRequiredFields(['path.to.field1', 'path.to.field2'])
* .setField1('value1')
* .setField2('value2')
* .buildSafe();
* ```
*/
setRequiredFields(fields) {
this._requiredFields = /* @__PURE__ */ new Set([...fields]);
return this;
}
/**
* Gets the combined required fields from both the static template and instance-level fields.
* @private
*/
getRequiredTemplate() {
const ctor = this.constructor;
const staticFields = ctor.requiredTemplate || [];
const instanceFields = Array.from(this._requiredFields);
return [.../* @__PURE__ */ new Set([...staticFields, ...instanceFields])];
}
/**
* Validates that all fields in the required template have been set.
* @private
*/
validateRequiredFields() {
const requiredPaths = this.getRequiredTemplate();
const missing = [];
for (const path of requiredPaths) {
const keys = path.split(".");
let current = this._actual;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (current === null || current === void 0 || !(key in current)) {
missing.push(path);
break;
}
current = current[key];
}
if (current === null || current === void 0) {
if (!missing.includes(path)) {
missing.push(path);
}
}
}
return missing;
}
/**
* Sets a property value and returns a new builder instance with updated type state.

@@ -51,6 +117,9 @@ * This method is intended to be wrapped by concrete builder methods in subclasses.

const BuilderClass = this.constructor;
return new BuilderClass({
...this._actual,
[key]: value
});
return new BuilderClass(
{
...this._actual,
[key]: value
},
Array.from(this._requiredFields)
);
}

@@ -65,8 +134,66 @@ /**

const BuilderClass = this.constructor;
return new BuilderClass({
...this._actual,
...props
});
return new BuilderClass(
{
...this._actual,
...props
},
Array.from(this._requiredFields)
);
}
/**
* Sets a deeply nested property value and returns a new builder instance with updated type state.
* This method uses dot notation to set nested properties in a type-safe way.
*
* @template P - The property path (e.g., "parent.child.grandchild")
* @param path - The dot-notation path to the property
* @param value - The value to assign to the nested property
* @returns A new builder instance with the nested property set
* @protected
*
* @example
* ```typescript
* builder.setNestedProperty('Order.Details.CustomerId', 'value')
* ```
*/
setNestedProperty(path, value) {
const BuilderClass = this.constructor;
const keys = path.split(".");
const newActual = this.deepClone(this._actual);
let current = newActual;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
current[key] = {};
} else {
current[key] = this.deepClone(current[key]);
}
current = current[key];
}
current[keys[keys.length - 1]] = value;
return new BuilderClass(
newActual,
Array.from(this._requiredFields)
);
}
/**
* Deep clone helper for nested objects
* @private
*/
deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => this.deepClone(item));
}
const cloned = {};
const hasOwn = Object.prototype.hasOwnProperty;
for (const key in obj) {
if (hasOwn.call(obj, key)) {
cloned[key] = this.deepClone(obj[key]);
}
}
return cloned;
}
/**
* Adds a value to an array property and returns a new builder instance with updated type state.

@@ -86,6 +213,9 @@ * This method is intended to be wrapped by concrete builder methods in subclasses for array properties.

const currentArray = (_a = this._actual[key]) != null ? _a : [];
return new BuilderClass({
...this._actual,
[key]: [...currentArray, value]
});
return new BuilderClass(
{
...this._actual,
[key]: [...currentArray, value]
},
Array.from(this._requiredFields)
);
}

@@ -105,2 +235,31 @@ /**

/**
* Builds the final object with runtime validation using the requiredTemplate.
* This method validates that all fields in the requiredTemplate array are present.
*
* @returns The fully built object of type T
* @throws {Error} If any required field is missing at runtime
*
* @example
* ```typescript
* class MyBuilder extends CeriosBuilder<MyType> {
* static requiredTemplate: RequiredFieldsTemplate<MyType> = [
* 'path.to.field1',
* 'path.to.field2'
* ];
* }
*
* const obj = new MyBuilder({})
* .setRequiredField1("value1")
* .setRequiredField2("value2")
* .buildSafe(); // Validates that both required fields are present
* ```
*/
buildSafe() {
const missing = this.validateRequiredFields();
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`);
}
return this._actual;
}
/**
* Builds a partial object, which may not have all required fields set.

@@ -107,0 +266,0 @@ * This is useful for scenarios where you want to inspect or validate the current state before finalizing.

+1
-1

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/index.ts","../src/cerios-builder.ts"],"sourcesContent":["export { CeriosBrand, CeriosBuilder } from \"./cerios-builder.js\";\n","/**\n * Unique symbol used internally to brand types and track which properties have been set in the builder's type.\n *\n * @internal\n */\ndeclare const __brand: unique symbol;\n\n/**\n * Type utility for branding builder types with information about which properties have been set.\n * This is used to enforce compile-time safety for required fields in the builder pattern.\n *\n * @template T - The type representing the set of properties that have been set\n * @internal\n */\nexport type CeriosBrand<T> = { [__brand]: T };\n\n/**\n * Abstract base class for creating type-safe builders with automatic property setters and compile-time validation of required fields.\n *\n * This class is intended to be extended by concrete builder implementations for your own types.\n * It provides utility methods for setting properties and building the final object, ensuring that all required fields are set at compile time.\n *\n * Example usage:\n * ```typescript\n * interface MyType { foo: string; bar: number[]; }\n * class MyTypeBuilder extends CeriosBuilder<MyType> {\n * setFoo(value: string) { return this.setProperty('foo', value); }\n * addBar(value: number) { return this.addToArrayProperty('bar', value); }\n * }\n * // Usage:\n * const obj = new MyTypeBuilder({})\n * .setFoo('hello')\n * .addBar(42)\n * .build();\n * ```\n *\n * @template T - The complete type being built\n */\nexport abstract class CeriosBuilder<T extends object> {\n\t/**\n\t * Creates a new builder instance. Intended to be called by subclasses.\n\t *\n\t * @param _actual - The current partial state of the object being built\n\t * @protected\n\t */\n\tprotected constructor(protected readonly _actual: Partial<T>) {}\n\n\t/**\n\t * Sets a property value and returns a new builder instance with updated type state.\n\t * This method is intended to be wrapped by concrete builder methods in subclasses.\n\t *\n\t * @template K - The property key being set\n\t * @param key - The property key to set\n\t * @param value - The value to assign to the property\n\t * @returns A new builder instance with the property set and type state updated\n\t * @protected\n\t */\n\tprotected setProperty<K extends keyof T>(key: K, value: T[K]): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any) => any;\n\t\treturn new BuilderClass({\n\t\t\t...this._actual,\n\t\t\t[key]: value,\n\t\t}) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Sets multiple property values at once and returns a new builder instance with updated type state.\n\t * @param props - An object with one or more properties to set.\n\t * @returns A new builder instance with the properties set and type state updated.\n\t * @protected\n\t */\n\tprotected setProperties<K extends keyof T>(props: Pick<T, K>): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any) => any;\n\t\treturn new BuilderClass({\n\t\t\t...this._actual,\n\t\t\t...props,\n\t\t}) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Adds a value to an array property and returns a new builder instance with updated type state.\n\t * This method is intended to be wrapped by concrete builder methods in subclasses for array properties.\n\t *\n\t * @template K - The property key (must be an array property)\n\t * @template V - The type of the array element\n\t * @param key - The array property key to add to\n\t * @param value - The value to add to the array\n\t * @returns A new builder instance with the array property updated and type state updated\n\t * @protected\n\t */\n\tprotected addToArrayProperty<\n\t\tK extends { [P in keyof T]: NonNullable<T[P]> extends Array<any> ? P : never }[keyof T],\n\t\tV extends T[K] extends Array<infer U> ? U : T[K] extends Array<infer U> | undefined ? U : never,\n\t>(key: K, value: V): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any) => any;\n\t\tconst currentArray = (this._actual[key] as Array<V> | undefined) ?? [];\n\t\treturn new BuilderClass({\n\t\t\t...this._actual,\n\t\t\t[key]: [...currentArray, value],\n\t\t}) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Builds the final object. This method uses TypeScript's contextual typing to ensure\n\t * all required fields are set before allowing build() to be called.\n\t *\n\t * The type constraint checks that all required properties are present.\n\t *\n\t * @returns The fully built object of type T\n\t * @throws {TypeError} If called without all required fields set (compile-time error)\n\t */\n\tbuild(this: this & CeriosBrand<T>): T {\n\t\treturn this._actual as T;\n\t}\n\n\t/**\n\t * Builds a partial object, which may not have all required fields set.\n\t * This is useful for scenarios where you want to inspect or validate the current state before finalizing.\n\t *\n\t * @returns The partially built object\n\t */\n\tbuildPartial(): Partial<T> {\n\t\treturn this._actual as Partial<T>;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACsCO,IAAe,gBAAf,MAA+C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO3C,YAA+B,SAAqB;AAArB;AAAA,EAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYrD,YAA+B,KAAQ,OAA6C;AAC7F,UAAM,eAAe,KAAK;AAC1B,WAAO,IAAI,aAAa;AAAA,MACvB,GAAG,KAAK;AAAA,MACR,CAAC,GAAG,GAAG;AAAA,IACR,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,cAAiC,OAAmD;AAC7F,UAAM,eAAe,KAAK;AAC1B,WAAO,IAAI,aAAa;AAAA,MACvB,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACJ,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaU,mBAGR,KAAQ,OAA0C;AA7FrD;AA8FE,UAAM,eAAe,KAAK;AAC1B,UAAM,gBAAgB,UAAK,QAAQ,GAAG,MAAhB,YAA8C,CAAC;AACrE,WAAO,IAAI,aAAa;AAAA,MACvB,GAAG,KAAK;AAAA,MACR,CAAC,GAAG,GAAG,CAAC,GAAG,cAAc,KAAK;AAAA,IAC/B,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,QAAsC;AACrC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAA2B;AAC1B,WAAO,KAAK;AAAA,EACb;AACD;","names":[]}
{"version":3,"sources":["../src/index.ts","../src/cerios-builder.ts"],"sourcesContent":["export { CeriosBrand, CeriosBuilder, RequiredFieldsTemplate } from \"./cerios-builder.js\";\n","/**\n * Unique symbol used internally to brand types and track which properties have been set in the builder's type.\n *\n * @internal\n */\ndeclare const __brand: unique symbol;\n\n/**\n * Type utility for branding builder types with information about which properties have been set.\n * This is used to enforce compile-time safety for required fields in the builder pattern.\n *\n * @template T - The type representing the set of properties that have been set\n * @internal\n */\nexport type CeriosBrand<T> = { [__brand]: T };\n\n/**\n * Helper type to represent a path through an object structure\n */\ntype PathImpl<T, K extends keyof T = keyof T> = K extends string | number\n\t? T[K] extends Record<string, any>\n\t\t? T[K] extends Array<any>\n\t\t\t? K\n\t\t\t: K | `${K}.${PathImpl<T[K]> & string}`\n\t\t: K\n\t: never;\n\nexport type Path<T> = PathImpl<T>;\n\ntype PathValue<T, P> = P extends keyof T\n\t? T[P]\n\t: P extends `${infer K}.${infer Rest}`\n\t\t? K extends keyof T\n\t\t\t? PathValue<T[K], Rest>\n\t\t\t: never\n\t\t: never;\n\n/**\n * Type-safe template for defining required fields using an array of paths.\n * Simply list the paths that are required.\n */\nexport type RequiredFieldsTemplate<T> = ReadonlyArray<Path<T>>;\n\n/**\n * Cache the root key extraction to avoid repeated computation\n * @internal\n */\ntype RootKey<P extends string> = P extends `${infer K}.${string}` ? K : P;\n\n/**\n * Abstract base class for creating type-safe builders with automatic property setters and compile-time validation of required fields.\n *\n * This class is intended to be extended by concrete builder implementations for your own types.\n * It provides utility methods for setting properties and building the final object, ensuring that all required fields are set at compile time.\n *\n * Example usage:\n * ```typescript\n * interface MyType { foo: string; bar: number[]; }\n * class MyTypeBuilder extends CeriosBuilder<MyType> {\n * static requiredTemplate: RequiredFieldsTemplate<MyType> = ['foo'];\n * setFoo(value: string) { return this.setProperty('foo', value); }\n * addBar(value: number) { return this.addToArrayProperty('bar', value); }\n * }\n * // Usage:\n * const obj = new MyTypeBuilder({})\n * .setFoo('hello')\n * .addBar(42)\n * .buildSafe(); // Validates that 'foo' is set\n * ```\n *\n * @template T - The complete type being built\n */\nexport abstract class CeriosBuilder<T extends object> {\n\t/**\n\t * Template defining which fields are required for this builder.\n\t * Subclasses should override this to specify their required fields as an array of paths.\n\t * The template is type-safe - only valid paths from type T can be used.\n\t */\n\tstatic requiredTemplate?: ReadonlyArray<string>;\n\n\t/**\n\t * Instance-level required fields that can be populated dynamically.\n\t * This allows adding required fields at runtime via the setRequiredFields method.\n\t * @private\n\t */\n\tprivate _requiredFields: Set<string> = new Set();\n\n\t/**\n\t * Sets the required fields for this builder instance.\n\t * This allows you to dynamically define which fields are required.\n\t *\n\t * @param fields - Array of dot-notation paths to required fields\n\t * @returns The builder instance for chaining\n\t *\n\t * @example\n\t * ```typescript\n\t * const builder = new MyBuilder({})\n\t * .setRequiredFields(['path.to.field1', 'path.to.field2'])\n\t * .setField1('value1')\n\t * .setField2('value2')\n\t * .buildSafe();\n\t * ```\n\t */\n\tsetRequiredFields(fields: ReadonlyArray<Path<T>>): this {\n\t\tthis._requiredFields = new Set([...fields] as string[]);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the combined required fields from both the static template and instance-level fields.\n\t * @private\n\t */\n\tprivate getRequiredTemplate(): ReadonlyArray<string> {\n\t\tconst ctor = this.constructor as typeof CeriosBuilder;\n\t\tconst staticFields = ctor.requiredTemplate || [];\n\t\tconst instanceFields = Array.from(this._requiredFields);\n\n\t\t// Combine and deduplicate\n\t\treturn [...new Set([...staticFields, ...instanceFields])];\n\t}\n\n\t/**\n\t * Validates that all fields in the required template have been set.\n\t * @private\n\t */\n\tprivate validateRequiredFields(): string[] {\n\t\tconst requiredPaths = this.getRequiredTemplate();\n\t\tconst missing: string[] = [];\n\n\t\tfor (const path of requiredPaths) {\n\t\t\tconst keys = path.split(\".\");\n\t\t\tlet current: any = this._actual;\n\n\t\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\t\tconst key = keys[i];\n\t\t\t\tif (current === null || current === undefined || !(key in current)) {\n\t\t\t\t\tmissing.push(path);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcurrent = current[key];\n\t\t\t}\n\n\t\t\t// Check if the final value is null or undefined\n\t\t\tif (current === null || current === undefined) {\n\t\t\t\tif (!missing.includes(path)) {\n\t\t\t\t\tmissing.push(path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn missing;\n\t}\n\n\t/**\n\t * Creates a new builder instance. Intended to be called by subclasses.\n\t *\n\t * @param _actual - The current partial state of the object being built\n\t * @param _requiredFields - Optional array of required field paths to preserve across instances\n\t * @protected\n\t */\n\tprotected constructor(\n\t\tprotected readonly _actual: Partial<T>,\n\t\t_requiredFields?: RequiredFieldsTemplate<T>\n\t) {\n\t\tif (_requiredFields) {\n\t\t\tthis._requiredFields = new Set([..._requiredFields] as string[]);\n\t\t}\n\t}\n\n\t/**\n\t * Sets a property value and returns a new builder instance with updated type state.\n\t * This method is intended to be wrapped by concrete builder methods in subclasses.\n\t *\n\t * @template K - The property key being set\n\t * @param key - The property key to set\n\t * @param value - The value to assign to the property\n\t * @returns A new builder instance with the property set and type state updated\n\t * @protected\n\t */\n\tprotected setProperty<K extends keyof T>(key: K, value: T[K]): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any, requiredFields?: RequiredFieldsTemplate<T>) => any;\n\t\treturn new BuilderClass(\n\t\t\t{\n\t\t\t\t...this._actual,\n\t\t\t\t[key]: value,\n\t\t\t},\n\t\t\tArray.from(this._requiredFields) as unknown as RequiredFieldsTemplate<T>\n\t\t) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Sets multiple property values at once and returns a new builder instance with updated type state.\n\t * @param props - An object with one or more properties to set.\n\t * @returns A new builder instance with the properties set and type state updated.\n\t * @protected\n\t */\n\tprotected setProperties<K extends keyof T>(props: Pick<T, K>): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any, requiredFields?: RequiredFieldsTemplate<T>) => any;\n\t\treturn new BuilderClass(\n\t\t\t{\n\t\t\t\t...this._actual,\n\t\t\t\t...props,\n\t\t\t},\n\t\t\tArray.from(this._requiredFields) as unknown as RequiredFieldsTemplate<T>\n\t\t) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Sets a deeply nested property value and returns a new builder instance with updated type state.\n\t * This method uses dot notation to set nested properties in a type-safe way.\n\t *\n\t * @template P - The property path (e.g., \"parent.child.grandchild\")\n\t * @param path - The dot-notation path to the property\n\t * @param value - The value to assign to the nested property\n\t * @returns A new builder instance with the nested property set\n\t * @protected\n\t *\n\t * @example\n\t * ```typescript\n\t * builder.setNestedProperty('Order.Details.CustomerId', 'value')\n\t * ```\n\t */\n\tprotected setNestedProperty<P extends Path<T>>(\n\t\tpath: P,\n\t\tvalue: PathValue<T, P>\n\t): this & CeriosBrand<Pick<T, Extract<RootKey<P & string>, keyof T>>> {\n\t\tconst BuilderClass = this.constructor as new (data: any, requiredFields?: RequiredFieldsTemplate<T>) => any;\n\t\tconst keys = (path as string).split(\".\");\n\t\tconst newActual = this.deepClone(this._actual);\n\n\t\tlet current: any = newActual;\n\t\tfor (let i = 0; i < keys.length - 1; i++) {\n\t\t\tconst key = keys[i];\n\t\t\tif (!(key in current) || typeof current[key] !== \"object\" || current[key] === null) {\n\t\t\t\tcurrent[key] = {};\n\t\t\t} else {\n\t\t\t\tcurrent[key] = this.deepClone(current[key]);\n\t\t\t}\n\t\t\tcurrent = current[key];\n\t\t}\n\n\t\tcurrent[keys[keys.length - 1]] = value;\n\n\t\treturn new BuilderClass(\n\t\t\tnewActual,\n\t\t\tArray.from(this._requiredFields) as unknown as RequiredFieldsTemplate<T>\n\t\t) as this & CeriosBrand<Pick<T, Extract<RootKey<P & string>, keyof T>>>;\n\t}\n\n\t/**\n\t * Deep clone helper for nested objects\n\t * @private\n\t */\n\tprivate deepClone<V>(obj: V): V {\n\t\tif (obj === null || typeof obj !== \"object\") {\n\t\t\treturn obj;\n\t\t}\n\t\tif (Array.isArray(obj)) {\n\t\t\treturn obj.map(item => this.deepClone(item)) as any;\n\t\t}\n\t\tconst cloned: any = {};\n\t\tconst hasOwn = Object.prototype.hasOwnProperty;\n\t\tfor (const key in obj) {\n\t\t\tif (hasOwn.call(obj, key)) {\n\t\t\t\tcloned[key] = this.deepClone(obj[key]);\n\t\t\t}\n\t\t}\n\t\treturn cloned;\n\t}\n\n\t/**\n\t * Adds a value to an array property and returns a new builder instance with updated type state.\n\t * This method is intended to be wrapped by concrete builder methods in subclasses for array properties.\n\t *\n\t * @template K - The property key (must be an array property)\n\t * @template V - The type of the array element\n\t * @param key - The array property key to add to\n\t * @param value - The value to add to the array\n\t * @returns A new builder instance with the array property updated and type state updated\n\t * @protected\n\t */\n\tprotected addToArrayProperty<\n\t\tK extends { [P in keyof T]: NonNullable<T[P]> extends Array<any> ? P : never }[keyof T],\n\t\tV extends T[K] extends Array<infer U> ? U : T[K] extends Array<infer U> | undefined ? U : never,\n\t>(key: K, value: V): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any, requiredFields?: RequiredFieldsTemplate<T>) => any;\n\t\tconst currentArray = (this._actual[key] as Array<V> | undefined) ?? [];\n\t\treturn new BuilderClass(\n\t\t\t{\n\t\t\t\t...this._actual,\n\t\t\t\t[key]: [...currentArray, value],\n\t\t\t},\n\t\t\tArray.from(this._requiredFields) as unknown as RequiredFieldsTemplate<T>\n\t\t) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Builds the final object. This method uses TypeScript's contextual typing to ensure\n\t * all required fields are set before allowing build() to be called.\n\t *\n\t * The type constraint checks that all required properties are present.\n\t *\n\t * @returns The fully built object of type T\n\t * @throws {TypeError} If called without all required fields set (compile-time error)\n\t */\n\tbuild(this: this & CeriosBrand<T>): T {\n\t\treturn this._actual as T;\n\t}\n\n\t/**\n\t * Builds the final object with runtime validation using the requiredTemplate.\n\t * This method validates that all fields in the requiredTemplate array are present.\n\t *\n\t * @returns The fully built object of type T\n\t * @throws {Error} If any required field is missing at runtime\n\t *\n\t * @example\n\t * ```typescript\n\t * class MyBuilder extends CeriosBuilder<MyType> {\n\t * static requiredTemplate: RequiredFieldsTemplate<MyType> = [\n\t * 'path.to.field1',\n\t * 'path.to.field2'\n\t * ];\n\t * }\n\t *\n\t * const obj = new MyBuilder({})\n\t * .setRequiredField1(\"value1\")\n\t * .setRequiredField2(\"value2\")\n\t * .buildSafe(); // Validates that both required fields are present\n\t * ```\n\t */\n\tbuildSafe(): T {\n\t\tconst missing = this.validateRequiredFields();\n\n\t\tif (missing.length > 0) {\n\t\t\tthrow new Error(`Missing required fields: ${missing.join(\", \")}. Please set these fields before calling build.`);\n\t\t}\n\n\t\treturn this._actual as T;\n\t}\n\n\t/**\n\t * Builds a partial object, which may not have all required fields set.\n\t * This is useful for scenarios where you want to inspect or validate the current state before finalizing.\n\t *\n\t * @returns The partially built object\n\t */\n\tbuildPartial(): Partial<T> {\n\t\treturn this._actual as Partial<T>;\n\t}\n}\n"],"mappings":"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","names":[]}

@@ -7,8 +7,74 @@ // src/cerios-builder.ts

* @param _actual - The current partial state of the object being built
* @param _requiredFields - Optional array of required field paths to preserve across instances
* @protected
*/
constructor(_actual) {
constructor(_actual, _requiredFields) {
this._actual = _actual;
/**
* Instance-level required fields that can be populated dynamically.
* This allows adding required fields at runtime via the setRequiredFields method.
* @private
*/
this._requiredFields = /* @__PURE__ */ new Set();
if (_requiredFields) {
this._requiredFields = /* @__PURE__ */ new Set([..._requiredFields]);
}
}
/**
* Sets the required fields for this builder instance.
* This allows you to dynamically define which fields are required.
*
* @param fields - Array of dot-notation paths to required fields
* @returns The builder instance for chaining
*
* @example
* ```typescript
* const builder = new MyBuilder({})
* .setRequiredFields(['path.to.field1', 'path.to.field2'])
* .setField1('value1')
* .setField2('value2')
* .buildSafe();
* ```
*/
setRequiredFields(fields) {
this._requiredFields = /* @__PURE__ */ new Set([...fields]);
return this;
}
/**
* Gets the combined required fields from both the static template and instance-level fields.
* @private
*/
getRequiredTemplate() {
const ctor = this.constructor;
const staticFields = ctor.requiredTemplate || [];
const instanceFields = Array.from(this._requiredFields);
return [.../* @__PURE__ */ new Set([...staticFields, ...instanceFields])];
}
/**
* Validates that all fields in the required template have been set.
* @private
*/
validateRequiredFields() {
const requiredPaths = this.getRequiredTemplate();
const missing = [];
for (const path of requiredPaths) {
const keys = path.split(".");
let current = this._actual;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (current === null || current === void 0 || !(key in current)) {
missing.push(path);
break;
}
current = current[key];
}
if (current === null || current === void 0) {
if (!missing.includes(path)) {
missing.push(path);
}
}
}
return missing;
}
/**
* Sets a property value and returns a new builder instance with updated type state.

@@ -25,6 +91,9 @@ * This method is intended to be wrapped by concrete builder methods in subclasses.

const BuilderClass = this.constructor;
return new BuilderClass({
...this._actual,
[key]: value
});
return new BuilderClass(
{
...this._actual,
[key]: value
},
Array.from(this._requiredFields)
);
}

@@ -39,8 +108,66 @@ /**

const BuilderClass = this.constructor;
return new BuilderClass({
...this._actual,
...props
});
return new BuilderClass(
{
...this._actual,
...props
},
Array.from(this._requiredFields)
);
}
/**
* Sets a deeply nested property value and returns a new builder instance with updated type state.
* This method uses dot notation to set nested properties in a type-safe way.
*
* @template P - The property path (e.g., "parent.child.grandchild")
* @param path - The dot-notation path to the property
* @param value - The value to assign to the nested property
* @returns A new builder instance with the nested property set
* @protected
*
* @example
* ```typescript
* builder.setNestedProperty('Order.Details.CustomerId', 'value')
* ```
*/
setNestedProperty(path, value) {
const BuilderClass = this.constructor;
const keys = path.split(".");
const newActual = this.deepClone(this._actual);
let current = newActual;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
current[key] = {};
} else {
current[key] = this.deepClone(current[key]);
}
current = current[key];
}
current[keys[keys.length - 1]] = value;
return new BuilderClass(
newActual,
Array.from(this._requiredFields)
);
}
/**
* Deep clone helper for nested objects
* @private
*/
deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => this.deepClone(item));
}
const cloned = {};
const hasOwn = Object.prototype.hasOwnProperty;
for (const key in obj) {
if (hasOwn.call(obj, key)) {
cloned[key] = this.deepClone(obj[key]);
}
}
return cloned;
}
/**
* Adds a value to an array property and returns a new builder instance with updated type state.

@@ -60,6 +187,9 @@ * This method is intended to be wrapped by concrete builder methods in subclasses for array properties.

const currentArray = (_a = this._actual[key]) != null ? _a : [];
return new BuilderClass({
...this._actual,
[key]: [...currentArray, value]
});
return new BuilderClass(
{
...this._actual,
[key]: [...currentArray, value]
},
Array.from(this._requiredFields)
);
}

@@ -79,2 +209,31 @@ /**

/**
* Builds the final object with runtime validation using the requiredTemplate.
* This method validates that all fields in the requiredTemplate array are present.
*
* @returns The fully built object of type T
* @throws {Error} If any required field is missing at runtime
*
* @example
* ```typescript
* class MyBuilder extends CeriosBuilder<MyType> {
* static requiredTemplate: RequiredFieldsTemplate<MyType> = [
* 'path.to.field1',
* 'path.to.field2'
* ];
* }
*
* const obj = new MyBuilder({})
* .setRequiredField1("value1")
* .setRequiredField2("value2")
* .buildSafe(); // Validates that both required fields are present
* ```
*/
buildSafe() {
const missing = this.validateRequiredFields();
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`);
}
return this._actual;
}
/**
* Builds a partial object, which may not have all required fields set.

@@ -81,0 +240,0 @@ * This is useful for scenarios where you want to inspect or validate the current state before finalizing.

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/cerios-builder.ts"],"sourcesContent":["/**\n * Unique symbol used internally to brand types and track which properties have been set in the builder's type.\n *\n * @internal\n */\ndeclare const __brand: unique symbol;\n\n/**\n * Type utility for branding builder types with information about which properties have been set.\n * This is used to enforce compile-time safety for required fields in the builder pattern.\n *\n * @template T - The type representing the set of properties that have been set\n * @internal\n */\nexport type CeriosBrand<T> = { [__brand]: T };\n\n/**\n * Abstract base class for creating type-safe builders with automatic property setters and compile-time validation of required fields.\n *\n * This class is intended to be extended by concrete builder implementations for your own types.\n * It provides utility methods for setting properties and building the final object, ensuring that all required fields are set at compile time.\n *\n * Example usage:\n * ```typescript\n * interface MyType { foo: string; bar: number[]; }\n * class MyTypeBuilder extends CeriosBuilder<MyType> {\n * setFoo(value: string) { return this.setProperty('foo', value); }\n * addBar(value: number) { return this.addToArrayProperty('bar', value); }\n * }\n * // Usage:\n * const obj = new MyTypeBuilder({})\n * .setFoo('hello')\n * .addBar(42)\n * .build();\n * ```\n *\n * @template T - The complete type being built\n */\nexport abstract class CeriosBuilder<T extends object> {\n\t/**\n\t * Creates a new builder instance. Intended to be called by subclasses.\n\t *\n\t * @param _actual - The current partial state of the object being built\n\t * @protected\n\t */\n\tprotected constructor(protected readonly _actual: Partial<T>) {}\n\n\t/**\n\t * Sets a property value and returns a new builder instance with updated type state.\n\t * This method is intended to be wrapped by concrete builder methods in subclasses.\n\t *\n\t * @template K - The property key being set\n\t * @param key - The property key to set\n\t * @param value - The value to assign to the property\n\t * @returns A new builder instance with the property set and type state updated\n\t * @protected\n\t */\n\tprotected setProperty<K extends keyof T>(key: K, value: T[K]): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any) => any;\n\t\treturn new BuilderClass({\n\t\t\t...this._actual,\n\t\t\t[key]: value,\n\t\t}) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Sets multiple property values at once and returns a new builder instance with updated type state.\n\t * @param props - An object with one or more properties to set.\n\t * @returns A new builder instance with the properties set and type state updated.\n\t * @protected\n\t */\n\tprotected setProperties<K extends keyof T>(props: Pick<T, K>): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any) => any;\n\t\treturn new BuilderClass({\n\t\t\t...this._actual,\n\t\t\t...props,\n\t\t}) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Adds a value to an array property and returns a new builder instance with updated type state.\n\t * This method is intended to be wrapped by concrete builder methods in subclasses for array properties.\n\t *\n\t * @template K - The property key (must be an array property)\n\t * @template V - The type of the array element\n\t * @param key - The array property key to add to\n\t * @param value - The value to add to the array\n\t * @returns A new builder instance with the array property updated and type state updated\n\t * @protected\n\t */\n\tprotected addToArrayProperty<\n\t\tK extends { [P in keyof T]: NonNullable<T[P]> extends Array<any> ? P : never }[keyof T],\n\t\tV extends T[K] extends Array<infer U> ? U : T[K] extends Array<infer U> | undefined ? U : never,\n\t>(key: K, value: V): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any) => any;\n\t\tconst currentArray = (this._actual[key] as Array<V> | undefined) ?? [];\n\t\treturn new BuilderClass({\n\t\t\t...this._actual,\n\t\t\t[key]: [...currentArray, value],\n\t\t}) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Builds the final object. This method uses TypeScript's contextual typing to ensure\n\t * all required fields are set before allowing build() to be called.\n\t *\n\t * The type constraint checks that all required properties are present.\n\t *\n\t * @returns The fully built object of type T\n\t * @throws {TypeError} If called without all required fields set (compile-time error)\n\t */\n\tbuild(this: this & CeriosBrand<T>): T {\n\t\treturn this._actual as T;\n\t}\n\n\t/**\n\t * Builds a partial object, which may not have all required fields set.\n\t * This is useful for scenarios where you want to inspect or validate the current state before finalizing.\n\t *\n\t * @returns The partially built object\n\t */\n\tbuildPartial(): Partial<T> {\n\t\treturn this._actual as Partial<T>;\n\t}\n}\n"],"mappings":";AAsCO,IAAe,gBAAf,MAA+C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO3C,YAA+B,SAAqB;AAArB;AAAA,EAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYrD,YAA+B,KAAQ,OAA6C;AAC7F,UAAM,eAAe,KAAK;AAC1B,WAAO,IAAI,aAAa;AAAA,MACvB,GAAG,KAAK;AAAA,MACR,CAAC,GAAG,GAAG;AAAA,IACR,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,cAAiC,OAAmD;AAC7F,UAAM,eAAe,KAAK;AAC1B,WAAO,IAAI,aAAa;AAAA,MACvB,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACJ,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaU,mBAGR,KAAQ,OAA0C;AA7FrD;AA8FE,UAAM,eAAe,KAAK;AAC1B,UAAM,gBAAgB,UAAK,QAAQ,GAAG,MAAhB,YAA8C,CAAC;AACrE,WAAO,IAAI,aAAa;AAAA,MACvB,GAAG,KAAK;AAAA,MACR,CAAC,GAAG,GAAG,CAAC,GAAG,cAAc,KAAK;AAAA,IAC/B,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,QAAsC;AACrC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAA2B;AAC1B,WAAO,KAAK;AAAA,EACb;AACD;","names":[]}
{"version":3,"sources":["../src/cerios-builder.ts"],"sourcesContent":["/**\n * Unique symbol used internally to brand types and track which properties have been set in the builder's type.\n *\n * @internal\n */\ndeclare const __brand: unique symbol;\n\n/**\n * Type utility for branding builder types with information about which properties have been set.\n * This is used to enforce compile-time safety for required fields in the builder pattern.\n *\n * @template T - The type representing the set of properties that have been set\n * @internal\n */\nexport type CeriosBrand<T> = { [__brand]: T };\n\n/**\n * Helper type to represent a path through an object structure\n */\ntype PathImpl<T, K extends keyof T = keyof T> = K extends string | number\n\t? T[K] extends Record<string, any>\n\t\t? T[K] extends Array<any>\n\t\t\t? K\n\t\t\t: K | `${K}.${PathImpl<T[K]> & string}`\n\t\t: K\n\t: never;\n\nexport type Path<T> = PathImpl<T>;\n\ntype PathValue<T, P> = P extends keyof T\n\t? T[P]\n\t: P extends `${infer K}.${infer Rest}`\n\t\t? K extends keyof T\n\t\t\t? PathValue<T[K], Rest>\n\t\t\t: never\n\t\t: never;\n\n/**\n * Type-safe template for defining required fields using an array of paths.\n * Simply list the paths that are required.\n */\nexport type RequiredFieldsTemplate<T> = ReadonlyArray<Path<T>>;\n\n/**\n * Cache the root key extraction to avoid repeated computation\n * @internal\n */\ntype RootKey<P extends string> = P extends `${infer K}.${string}` ? K : P;\n\n/**\n * Abstract base class for creating type-safe builders with automatic property setters and compile-time validation of required fields.\n *\n * This class is intended to be extended by concrete builder implementations for your own types.\n * It provides utility methods for setting properties and building the final object, ensuring that all required fields are set at compile time.\n *\n * Example usage:\n * ```typescript\n * interface MyType { foo: string; bar: number[]; }\n * class MyTypeBuilder extends CeriosBuilder<MyType> {\n * static requiredTemplate: RequiredFieldsTemplate<MyType> = ['foo'];\n * setFoo(value: string) { return this.setProperty('foo', value); }\n * addBar(value: number) { return this.addToArrayProperty('bar', value); }\n * }\n * // Usage:\n * const obj = new MyTypeBuilder({})\n * .setFoo('hello')\n * .addBar(42)\n * .buildSafe(); // Validates that 'foo' is set\n * ```\n *\n * @template T - The complete type being built\n */\nexport abstract class CeriosBuilder<T extends object> {\n\t/**\n\t * Template defining which fields are required for this builder.\n\t * Subclasses should override this to specify their required fields as an array of paths.\n\t * The template is type-safe - only valid paths from type T can be used.\n\t */\n\tstatic requiredTemplate?: ReadonlyArray<string>;\n\n\t/**\n\t * Instance-level required fields that can be populated dynamically.\n\t * This allows adding required fields at runtime via the setRequiredFields method.\n\t * @private\n\t */\n\tprivate _requiredFields: Set<string> = new Set();\n\n\t/**\n\t * Sets the required fields for this builder instance.\n\t * This allows you to dynamically define which fields are required.\n\t *\n\t * @param fields - Array of dot-notation paths to required fields\n\t * @returns The builder instance for chaining\n\t *\n\t * @example\n\t * ```typescript\n\t * const builder = new MyBuilder({})\n\t * .setRequiredFields(['path.to.field1', 'path.to.field2'])\n\t * .setField1('value1')\n\t * .setField2('value2')\n\t * .buildSafe();\n\t * ```\n\t */\n\tsetRequiredFields(fields: ReadonlyArray<Path<T>>): this {\n\t\tthis._requiredFields = new Set([...fields] as string[]);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the combined required fields from both the static template and instance-level fields.\n\t * @private\n\t */\n\tprivate getRequiredTemplate(): ReadonlyArray<string> {\n\t\tconst ctor = this.constructor as typeof CeriosBuilder;\n\t\tconst staticFields = ctor.requiredTemplate || [];\n\t\tconst instanceFields = Array.from(this._requiredFields);\n\n\t\t// Combine and deduplicate\n\t\treturn [...new Set([...staticFields, ...instanceFields])];\n\t}\n\n\t/**\n\t * Validates that all fields in the required template have been set.\n\t * @private\n\t */\n\tprivate validateRequiredFields(): string[] {\n\t\tconst requiredPaths = this.getRequiredTemplate();\n\t\tconst missing: string[] = [];\n\n\t\tfor (const path of requiredPaths) {\n\t\t\tconst keys = path.split(\".\");\n\t\t\tlet current: any = this._actual;\n\n\t\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\t\tconst key = keys[i];\n\t\t\t\tif (current === null || current === undefined || !(key in current)) {\n\t\t\t\t\tmissing.push(path);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcurrent = current[key];\n\t\t\t}\n\n\t\t\t// Check if the final value is null or undefined\n\t\t\tif (current === null || current === undefined) {\n\t\t\t\tif (!missing.includes(path)) {\n\t\t\t\t\tmissing.push(path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn missing;\n\t}\n\n\t/**\n\t * Creates a new builder instance. Intended to be called by subclasses.\n\t *\n\t * @param _actual - The current partial state of the object being built\n\t * @param _requiredFields - Optional array of required field paths to preserve across instances\n\t * @protected\n\t */\n\tprotected constructor(\n\t\tprotected readonly _actual: Partial<T>,\n\t\t_requiredFields?: RequiredFieldsTemplate<T>\n\t) {\n\t\tif (_requiredFields) {\n\t\t\tthis._requiredFields = new Set([..._requiredFields] as string[]);\n\t\t}\n\t}\n\n\t/**\n\t * Sets a property value and returns a new builder instance with updated type state.\n\t * This method is intended to be wrapped by concrete builder methods in subclasses.\n\t *\n\t * @template K - The property key being set\n\t * @param key - The property key to set\n\t * @param value - The value to assign to the property\n\t * @returns A new builder instance with the property set and type state updated\n\t * @protected\n\t */\n\tprotected setProperty<K extends keyof T>(key: K, value: T[K]): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any, requiredFields?: RequiredFieldsTemplate<T>) => any;\n\t\treturn new BuilderClass(\n\t\t\t{\n\t\t\t\t...this._actual,\n\t\t\t\t[key]: value,\n\t\t\t},\n\t\t\tArray.from(this._requiredFields) as unknown as RequiredFieldsTemplate<T>\n\t\t) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Sets multiple property values at once and returns a new builder instance with updated type state.\n\t * @param props - An object with one or more properties to set.\n\t * @returns A new builder instance with the properties set and type state updated.\n\t * @protected\n\t */\n\tprotected setProperties<K extends keyof T>(props: Pick<T, K>): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any, requiredFields?: RequiredFieldsTemplate<T>) => any;\n\t\treturn new BuilderClass(\n\t\t\t{\n\t\t\t\t...this._actual,\n\t\t\t\t...props,\n\t\t\t},\n\t\t\tArray.from(this._requiredFields) as unknown as RequiredFieldsTemplate<T>\n\t\t) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Sets a deeply nested property value and returns a new builder instance with updated type state.\n\t * This method uses dot notation to set nested properties in a type-safe way.\n\t *\n\t * @template P - The property path (e.g., \"parent.child.grandchild\")\n\t * @param path - The dot-notation path to the property\n\t * @param value - The value to assign to the nested property\n\t * @returns A new builder instance with the nested property set\n\t * @protected\n\t *\n\t * @example\n\t * ```typescript\n\t * builder.setNestedProperty('Order.Details.CustomerId', 'value')\n\t * ```\n\t */\n\tprotected setNestedProperty<P extends Path<T>>(\n\t\tpath: P,\n\t\tvalue: PathValue<T, P>\n\t): this & CeriosBrand<Pick<T, Extract<RootKey<P & string>, keyof T>>> {\n\t\tconst BuilderClass = this.constructor as new (data: any, requiredFields?: RequiredFieldsTemplate<T>) => any;\n\t\tconst keys = (path as string).split(\".\");\n\t\tconst newActual = this.deepClone(this._actual);\n\n\t\tlet current: any = newActual;\n\t\tfor (let i = 0; i < keys.length - 1; i++) {\n\t\t\tconst key = keys[i];\n\t\t\tif (!(key in current) || typeof current[key] !== \"object\" || current[key] === null) {\n\t\t\t\tcurrent[key] = {};\n\t\t\t} else {\n\t\t\t\tcurrent[key] = this.deepClone(current[key]);\n\t\t\t}\n\t\t\tcurrent = current[key];\n\t\t}\n\n\t\tcurrent[keys[keys.length - 1]] = value;\n\n\t\treturn new BuilderClass(\n\t\t\tnewActual,\n\t\t\tArray.from(this._requiredFields) as unknown as RequiredFieldsTemplate<T>\n\t\t) as this & CeriosBrand<Pick<T, Extract<RootKey<P & string>, keyof T>>>;\n\t}\n\n\t/**\n\t * Deep clone helper for nested objects\n\t * @private\n\t */\n\tprivate deepClone<V>(obj: V): V {\n\t\tif (obj === null || typeof obj !== \"object\") {\n\t\t\treturn obj;\n\t\t}\n\t\tif (Array.isArray(obj)) {\n\t\t\treturn obj.map(item => this.deepClone(item)) as any;\n\t\t}\n\t\tconst cloned: any = {};\n\t\tconst hasOwn = Object.prototype.hasOwnProperty;\n\t\tfor (const key in obj) {\n\t\t\tif (hasOwn.call(obj, key)) {\n\t\t\t\tcloned[key] = this.deepClone(obj[key]);\n\t\t\t}\n\t\t}\n\t\treturn cloned;\n\t}\n\n\t/**\n\t * Adds a value to an array property and returns a new builder instance with updated type state.\n\t * This method is intended to be wrapped by concrete builder methods in subclasses for array properties.\n\t *\n\t * @template K - The property key (must be an array property)\n\t * @template V - The type of the array element\n\t * @param key - The array property key to add to\n\t * @param value - The value to add to the array\n\t * @returns A new builder instance with the array property updated and type state updated\n\t * @protected\n\t */\n\tprotected addToArrayProperty<\n\t\tK extends { [P in keyof T]: NonNullable<T[P]> extends Array<any> ? P : never }[keyof T],\n\t\tV extends T[K] extends Array<infer U> ? U : T[K] extends Array<infer U> | undefined ? U : never,\n\t>(key: K, value: V): this & CeriosBrand<Pick<T, K>> {\n\t\tconst BuilderClass = this.constructor as new (data: any, requiredFields?: RequiredFieldsTemplate<T>) => any;\n\t\tconst currentArray = (this._actual[key] as Array<V> | undefined) ?? [];\n\t\treturn new BuilderClass(\n\t\t\t{\n\t\t\t\t...this._actual,\n\t\t\t\t[key]: [...currentArray, value],\n\t\t\t},\n\t\t\tArray.from(this._requiredFields) as unknown as RequiredFieldsTemplate<T>\n\t\t) as this & CeriosBrand<Pick<T, K>>;\n\t}\n\n\t/**\n\t * Builds the final object. This method uses TypeScript's contextual typing to ensure\n\t * all required fields are set before allowing build() to be called.\n\t *\n\t * The type constraint checks that all required properties are present.\n\t *\n\t * @returns The fully built object of type T\n\t * @throws {TypeError} If called without all required fields set (compile-time error)\n\t */\n\tbuild(this: this & CeriosBrand<T>): T {\n\t\treturn this._actual as T;\n\t}\n\n\t/**\n\t * Builds the final object with runtime validation using the requiredTemplate.\n\t * This method validates that all fields in the requiredTemplate array are present.\n\t *\n\t * @returns The fully built object of type T\n\t * @throws {Error} If any required field is missing at runtime\n\t *\n\t * @example\n\t * ```typescript\n\t * class MyBuilder extends CeriosBuilder<MyType> {\n\t * static requiredTemplate: RequiredFieldsTemplate<MyType> = [\n\t * 'path.to.field1',\n\t * 'path.to.field2'\n\t * ];\n\t * }\n\t *\n\t * const obj = new MyBuilder({})\n\t * .setRequiredField1(\"value1\")\n\t * .setRequiredField2(\"value2\")\n\t * .buildSafe(); // Validates that both required fields are present\n\t * ```\n\t */\n\tbuildSafe(): T {\n\t\tconst missing = this.validateRequiredFields();\n\n\t\tif (missing.length > 0) {\n\t\t\tthrow new Error(`Missing required fields: ${missing.join(\", \")}. Please set these fields before calling build.`);\n\t\t}\n\n\t\treturn this._actual as T;\n\t}\n\n\t/**\n\t * Builds a partial object, which may not have all required fields set.\n\t * This is useful for scenarios where you want to inspect or validate the current state before finalizing.\n\t *\n\t * @returns The partially built object\n\t */\n\tbuildPartial(): Partial<T> {\n\t\treturn this._actual as Partial<T>;\n\t}\n}\n"],"mappings":";AAwEO,IAAe,gBAAf,MAA+C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwF3C,YACU,SACnB,iBACC;AAFkB;AA5EpB;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,kBAA+B,oBAAI,IAAI;AA+E9C,QAAI,iBAAiB;AACpB,WAAK,kBAAkB,oBAAI,IAAI,CAAC,GAAG,eAAe,CAAa;AAAA,IAChE;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAhEA,kBAAkB,QAAsC;AACvD,SAAK,kBAAkB,oBAAI,IAAI,CAAC,GAAG,MAAM,CAAa;AACtD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAA6C;AACpD,UAAM,OAAO,KAAK;AAClB,UAAM,eAAe,KAAK,oBAAoB,CAAC;AAC/C,UAAM,iBAAiB,MAAM,KAAK,KAAK,eAAe;AAGtD,WAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,cAAc,GAAG,cAAc,CAAC,CAAC;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,yBAAmC;AAC1C,UAAM,gBAAgB,KAAK,oBAAoB;AAC/C,UAAM,UAAoB,CAAC;AAE3B,eAAW,QAAQ,eAAe;AACjC,YAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,UAAI,UAAe,KAAK;AAExB,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACrC,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,YAAY,QAAQ,YAAY,UAAa,EAAE,OAAO,UAAU;AACnE,kBAAQ,KAAK,IAAI;AACjB;AAAA,QACD;AACA,kBAAU,QAAQ,GAAG;AAAA,MACtB;AAGA,UAAI,YAAY,QAAQ,YAAY,QAAW;AAC9C,YAAI,CAAC,QAAQ,SAAS,IAAI,GAAG;AAC5B,kBAAQ,KAAK,IAAI;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BU,YAA+B,KAAQ,OAA6C;AAC7F,UAAM,eAAe,KAAK;AAC1B,WAAO,IAAI;AAAA,MACV;AAAA,QACC,GAAG,KAAK;AAAA,QACR,CAAC,GAAG,GAAG;AAAA,MACR;AAAA,MACA,MAAM,KAAK,KAAK,eAAe;AAAA,IAChC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,cAAiC,OAAmD;AAC7F,UAAM,eAAe,KAAK;AAC1B,WAAO,IAAI;AAAA,MACV;AAAA,QACC,GAAG,KAAK;AAAA,QACR,GAAG;AAAA,MACJ;AAAA,MACA,MAAM,KAAK,KAAK,eAAe;AAAA,IAChC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBU,kBACT,MACA,OACqE;AACrE,UAAM,eAAe,KAAK;AAC1B,UAAM,OAAQ,KAAgB,MAAM,GAAG;AACvC,UAAM,YAAY,KAAK,UAAU,KAAK,OAAO;AAE7C,QAAI,UAAe;AACnB,aAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AACzC,YAAM,MAAM,KAAK,CAAC;AAClB,UAAI,EAAE,OAAO,YAAY,OAAO,QAAQ,GAAG,MAAM,YAAY,QAAQ,GAAG,MAAM,MAAM;AACnF,gBAAQ,GAAG,IAAI,CAAC;AAAA,MACjB,OAAO;AACN,gBAAQ,GAAG,IAAI,KAAK,UAAU,QAAQ,GAAG,CAAC;AAAA,MAC3C;AACA,gBAAU,QAAQ,GAAG;AAAA,IACtB;AAEA,YAAQ,KAAK,KAAK,SAAS,CAAC,CAAC,IAAI;AAEjC,WAAO,IAAI;AAAA,MACV;AAAA,MACA,MAAM,KAAK,KAAK,eAAe;AAAA,IAChC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAa,KAAW;AAC/B,QAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC5C,aAAO;AAAA,IACR;AACA,QAAI,MAAM,QAAQ,GAAG,GAAG;AACvB,aAAO,IAAI,IAAI,UAAQ,KAAK,UAAU,IAAI,CAAC;AAAA,IAC5C;AACA,UAAM,SAAc,CAAC;AACrB,UAAM,SAAS,OAAO,UAAU;AAChC,eAAW,OAAO,KAAK;AACtB,UAAI,OAAO,KAAK,KAAK,GAAG,GAAG;AAC1B,eAAO,GAAG,IAAI,KAAK,UAAU,IAAI,GAAG,CAAC;AAAA,MACtC;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaU,mBAGR,KAAQ,OAA0C;AA5RrD;AA6RE,UAAM,eAAe,KAAK;AAC1B,UAAM,gBAAgB,UAAK,QAAQ,GAAG,MAAhB,YAA8C,CAAC;AACrE,WAAO,IAAI;AAAA,MACV;AAAA,QACC,GAAG,KAAK;AAAA,QACR,CAAC,GAAG,GAAG,CAAC,GAAG,cAAc,KAAK;AAAA,MAC/B;AAAA,MACA,MAAM,KAAK,KAAK,eAAe;AAAA,IAChC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,QAAsC;AACrC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,YAAe;AACd,UAAM,UAAU,KAAK,uBAAuB;AAE5C,QAAI,QAAQ,SAAS,GAAG;AACvB,YAAM,IAAI,MAAM,4BAA4B,QAAQ,KAAK,IAAI,CAAC,iDAAiD;AAAA,IAChH;AAEA,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAA2B;AAC1B,WAAO,KAAK;AAAA,EACb;AACD;","names":[]}
{
"name": "@cerios/cerios-builder",
"version": "1.1.0",
"version": "1.2.0",
"author": "Ronald Veth - Cerios",

@@ -5,0 +5,0 @@ "description": "A TypeScript builder pattern library providing compile-time type safety for object construction with method chaining and required field validation",

+819
-19

@@ -8,5 +8,8 @@ # @cerios/cerios-builder

- **Type-Safe Building**: Compile-time validation ensures all required properties are set
- **Nested Properties Support**: Build deeply nested objects with dot-notation paths
- **Runtime Validation**: Optional runtime validation with `buildSafe()` and required field templates
- **Dynamic Required Fields**: Add required fields at runtime based on your business logic
- **Method Chaining**: Fluent API for readable object construction
- **Partial Building**: Build incomplete objects when needed
- **Zero Runtime Overhead**: All type checking happens at compile time
- **Zero Runtime Overhead**: All type checking happens at compile time (unless using `buildSafe()`)
- **Extensible**: Easy to create custom builder methods

@@ -27,2 +30,61 @@ - **TypeScript First**: Built with TypeScript, for TypeScript

## 🎯 Quick Start
```typescript
import { CeriosBuilder, RequiredFieldsTemplate } from '@cerios/cerios-builder';
// 1. Define your types
type User = {
id: string;
name: string;
email: string;
age?: number;
};
// 2. Create your builder
class UserBuilder extends CeriosBuilder<User> {
static create() {
return new UserBuilder({});
}
id(value: string) {
return this.setProperty('id', value);
}
name(value: string) {
return this.setProperty('name', value);
}
email(value: string) {
return this.setProperty('email', value);
}
age(value: number) {
return this.setProperty('age', value);
}
}
// 3. Build your object
const user = UserBuilder.create()
.id('123')
.name('John Doe')
.email('john@example.com')
.age(30)
.build();
```
## 📖 Feature Overview
| Feature | Method | Use Case |
|---------|--------|----------|
| **Simple Properties** | `setProperty()` | Set flat object properties |
| **Nested Properties** | `setNestedProperty()` | Set deeply nested properties with dot notation |
| **Array Properties** | `addToArrayProperty()` | Add values to array properties |
| **Multiple Properties** | `setProperties()` | Set multiple properties at once |
| **Required Template** | `static requiredTemplate` | Define required fields for runtime validation |
| **Dynamic Requirements** | `setRequiredFields()` | Add required fields at runtime |
| **Compile-Time Build** | `build()` | Build with TypeScript type checking |
| **Runtime Validation** | `buildSafe()` | Build with runtime validation |
| **Partial Build** | `buildPartial()` | Build incomplete objects |
## 🔧 Basic Usage

@@ -126,2 +188,571 @@

### Deeply Nested Properties
Build complex nested structures with type-safe dot-notation paths:
```typescript
import { CeriosBuilder, RequiredFieldsTemplate } from '@cerios/cerios-builder';
type Address = {
Street: string;
City: string;
PostalCode?: string;
Country: string;
};
type OrderDetails = {
OrderNumber?: string;
CustomerId: string;
TotalAmount: number;
Status: string;
ShippingAddress: Address;
};
type Order = {
Details: OrderDetails;
};
type OrderRequest = {
Order: Order;
};
class OrderRequestBuilder extends CeriosBuilder<OrderRequest> {
static create() {
return new OrderRequestBuilder({}, [
'Order.Details.CustomerId',
'Order.Details.TotalAmount',
'Order.Details.Status',
'Order.Details.ShippingAddress.Street',
'Order.Details.ShippingAddress.City',
'Order.Details.ShippingAddress.Country',
]);
}
static createWithDefaults() {
return this.create().status('pending');
}
orderNumber(value: string) {
return this.setNestedProperty('Order.Details.OrderNumber', value);
}
customerId(value: string) {
return this.setNestedProperty('Order.Details.CustomerId', value);
}
totalAmount(value: number) {
return this.setNestedProperty('Order.Details.TotalAmount', value);
}
status(value: string) {
return this.setNestedProperty('Order.Details.Status', value);
}
shippingStreet(value: string) {
return this.setNestedProperty('Order.Details.ShippingAddress.Street', value);
}
shippingCity(value: string) {
return this.setNestedProperty('Order.Details.ShippingAddress.City', value);
}
shippingPostalCode(value: string) {
return this.setNestedProperty('Order.Details.ShippingAddress.PostalCode', value);
}
shippingCountry(value: string) {
return this.setNestedProperty('Order.Details.ShippingAddress.Country', value);
}
}
// Usage with runtime validation
const order = OrderRequestBuilder.createWithDefaults()
.customerId('CUST-001')
.totalAmount(299.99)
.orderNumber('ORD-12345')
.shippingStreet('123 Main St')
.shippingCity('New York')
.shippingCountry('USA')
.buildSafe(); // ✅ Validates all required fields are set
// ❌ This will throw an error at runtime
try {
const invalidOrder = OrderRequestBuilder.createWithDefaults()
.customerId('CUST-002')
.buildSafe(); // Missing TotalAmount and shipping address fields
} catch (error) {
console.error(error.message);
// "Missing required fields: Order.Details.TotalAmount, Order.Details.ShippingAddress.Street, ..."
}
```
### Runtime Validation with Required Templates
Define which fields are required and validate them at runtime:
```typescript
class ProductBuilder extends CeriosBuilder<Product> {
static create() {
return new ProductBuilder({}, [
'id',
'name',
'price',
]);
}
id(value: string) {
return this.setProperty('id', value);
}
name(value: string) {
return this.setProperty('name', value);
}
price(value: number) {
return this.setProperty('price', value);
}
description(value: string) {
return this.setProperty('description', value);
}
}
// Use buildSafe() for runtime validation
const product = ProductBuilder.create()
.id('PROD-001')
.name('Laptop')
.price(999.99)
.buildSafe(); // ✅ All required fields are set
// This will throw an error
const invalidProduct = ProductBuilder.create()
.id('PROD-002')
.name('Mouse')
.buildSafe(); // ❌ Error: Missing required fields: price
```
### Dynamic Required Fields
Add required fields at runtime based on business logic:
```typescript
class EmployeeBuilder extends CeriosBuilder<Employee> {
static create() {
return new EmployeeBuilder({}, [
'firstName',
'lastName',
'email',
]);
}
firstName(value: string) {
return this.setProperty('firstName', value);
}
lastName(value: string) {
return this.setProperty('lastName', value);
}
email(value: string) {
return this.setProperty('email', value);
}
employeeId(value: string) {
return this.setProperty('employeeId', value);
}
phone(value: string) {
return this.setProperty('phone', value);
}
}
// Scenario 1: New employee (no ID required)
const newEmployee = EmployeeBuilder.create()
.firstName('John')
.lastName('Doe')
.email('john.doe@company.com')
.buildSafe(); // ✅ Only validates firstName, lastName, email
// Scenario 2: Existing employee (ID required)
const existingEmployee = EmployeeBuilder.create()
.setRequiredFields(['employeeId']) // Add employeeId as required dynamically
.firstName('Jane')
.lastName('Smith')
.email('jane.smith@company.com')
.employeeId('EMP-12345') // Must provide employeeId
.buildSafe(); // ✅ Validates firstName, lastName, email, AND employeeId
// Scenario 3: Employee with contact requirement
const contactEmployee = EmployeeBuilder.create()
.setRequiredFields(['phone', 'employeeId']) // Add multiple dynamic fields
.firstName('Bob')
.lastName('Johnson')
.email('bob.johnson@company.com')
.phone('+1-555-0123')
.employeeId('EMP-67890')
.buildSafe(); // ✅ Validates all fields including phone and employeeId
```
### Four Ways to Set Up Required Fields for Deeply Nested Properties
When working with deeply nested structures, you have **four approaches** to configure required field validation:
#### 1. Static Template in Constructor (Recommended for Fixed Requirements)
Define required fields once at the class level and pass them through the constructor. Best for when required fields never change.
```typescript
type Address = {
Street: string;
City: string;
Country: string;
};
type OrderDetails = {
CustomerId: string;
TotalAmount: number;
ShippingAddress: Address;
};
type Order = {
Details: OrderDetails;
};
class OrderBuilder extends CeriosBuilder<Order> {
// Method 1: Static template defined at class level
static requiredTemplate: RequiredFieldsTemplate<Order> = [
'Details.CustomerId',
'Details.TotalAmount',
'Details.ShippingAddress.Street',
'Details.ShippingAddress.City',
'Details.ShippingAddress.Country',
];
static create() {
// Pass template through constructor
return new OrderBuilder({});
}
customerId(value: string) {
return this.setNestedProperty('Details.CustomerId', value);
}
totalAmount(value: number) {
return this.setNestedProperty('Details.TotalAmount', value);
}
shippingStreet(value: string) {
return this.setNestedProperty('Details.ShippingAddress.Street', value);
}
shippingCity(value: string) {
return this.setNestedProperty('Details.ShippingAddress.City', value);
}
shippingCountry(value: string) {
return this.setNestedProperty('Details.ShippingAddress.Country', value);
}
}
// Usage: All required fields validated by template
const order = OrderBuilder.create()
.customerId('CUST-001')
.totalAmount(299.99)
.shippingStreet('123 Main St')
.shippingCity('New York')
.shippingCountry('USA')
.buildSafe(); // ✅ Validates all 5 required nested fields
```
#### 2. Inline Constructor Template (Simple and Direct)
Pass the required template directly in the constructor without defining a static property. Best for simple cases or when you want to keep everything in one place.
```typescript
type Address = {
Street: string;
City: string;
Country: string;
PostalCode?: string;
};
type OrderDetails = {
CustomerId: string;
TotalAmount: number;
ShippingAddress: Address;
};
type Order = {
Details: OrderDetails;
};
class OrderBuilder extends CeriosBuilder<Order> {
// Method 2: Pass template directly in constructor (no static property needed)
static create() {
return new OrderBuilder({}, [
'Details.CustomerId',
'Details.TotalAmount',
'Details.ShippingAddress.Street',
'Details.ShippingAddress.City',
'Details.ShippingAddress.Country',
]);
}
customerId(value: string) {
return this.setNestedProperty('Details.CustomerId', value);
}
totalAmount(value: number) {
return this.setNestedProperty('Details.TotalAmount', value);
}
shippingStreet(value: string) {
return this.setNestedProperty('Details.ShippingAddress.Street', value);
}
shippingCity(value: string) {
return this.setNestedProperty('Details.ShippingAddress.City', value);
}
shippingCountry(value: string) {
return this.setNestedProperty('Details.ShippingAddress.Country', value);
}
shippingPostalCode(value: string) {
return this.setNestedProperty('Details.ShippingAddress.PostalCode', value);
}
}
// Usage: Same validation as Method 1, but simpler setup
const order = OrderBuilder.create()
.customerId('CUST-001')
.totalAmount(299.99)
.shippingStreet('123 Main St')
.shippingCity('New York')
.shippingCountry('USA')
.buildSafe(); // ✅ Validates all required nested fields
// ❌ Still validates properly
try {
const invalidOrder = OrderBuilder.create()
.customerId('CUST-002')
.buildSafe();
} catch (error) {
console.error(error.message);
// "Missing required fields: Details.TotalAmount, Details.ShippingAddress.Street, ..."
}
```
#### 3. Dynamic Required Fields via `setRequiredFields()` (Best for Conditional Logic)
Add required fields at runtime based on business logic. Combines static template with dynamic additions.
```typescript
class OrderBuilder extends CeriosBuilder<Order> {
static requiredTemplate: RequiredFieldsTemplate<Order> = [
'Details.CustomerId',
'Details.TotalAmount',
];
static create() {
return new OrderBuilder({});
}
// Method 3: Add required fields dynamically for specific scenarios
static createInternational() {
return this.create()
.setRequiredFields([
'Details.ShippingAddress.Street',
'Details.ShippingAddress.City',
'Details.ShippingAddress.Country',
'Details.ShippingAddress.PostalCode', // Extra requirement for international
]);
}
static createDomestic() {
return this.create()
.setRequiredFields([
'Details.ShippingAddress.Street',
'Details.ShippingAddress.City',
'Details.ShippingAddress.Country',
]); // No postal code required
}
customerId(value: string) {
return this.setNestedProperty('Details.CustomerId', value);
}
totalAmount(value: number) {
return this.setNestedProperty('Details.TotalAmount', value);
}
shippingStreet(value: string) {
return this.setNestedProperty('Details.ShippingAddress.Street', value);
}
shippingCity(value: string) {
return this.setNestedProperty('Details.ShippingAddress.City', value);
}
shippingCountry(value: string) {
return this.setNestedProperty('Details.ShippingAddress.Country', value);
}
shippingPostalCode(value: string) {
return this.setNestedProperty('Details.ShippingAddress.PostalCode', value);
}
}
// Domestic order: postal code is optional
const domesticOrder = OrderBuilder.createDomestic()
.customerId('CUST-001')
.totalAmount(99.99)
.shippingStreet('123 Main St')
.shippingCity('New York')
.shippingCountry('USA')
.buildSafe(); // ✅ Valid without postal code
// International order: postal code is required
const internationalOrder = OrderBuilder.createInternational()
.customerId('CUST-002')
.totalAmount(299.99)
.shippingStreet('10 Downing Street')
.shippingCity('London')
.shippingCountry('UK')
.shippingPostalCode('SW1A 2AA') // Must provide postal code
.buildSafe(); // ✅ All fields including postal code validated
// ❌ This will fail - missing postal code for international
try {
const invalidOrder = OrderBuilder.createInternational()
.customerId('CUST-003')
.totalAmount(150.00)
.shippingStreet('Rue de Rivoli')
.shippingCity('Paris')
.shippingCountry('France')
.buildSafe(); // Missing PostalCode
} catch (error) {
console.error(error.message);
// "Missing required fields: Details.ShippingAddress.PostalCode"
}
```
#### 4. Inline Dynamic Fields (Best for One-Off Requirements)
Add required fields inline during the building process. Useful for special cases.
```typescript
class OrderBuilder extends CeriosBuilder<Order> {
static create() {
return new OrderBuilder({}, ['Details.CustomerId']);
}
customerId(value: string) {
return this.setNestedProperty('Details.CustomerId', value);
}
totalAmount(value: number) {
return this.setNestedProperty('Details.TotalAmount', value);
}
shippingStreet(value: string) {
return this.setNestedProperty('Details.ShippingAddress.Street', value);
}
shippingCity(value: string) {
return this.setNestedProperty('Details.ShippingAddress.City', value);
}
shippingCountry(value: string) {
return this.setNestedProperty('Details.ShippingAddress.Country', value);
}
}
// Method 4: Inline requirement based on runtime data
function createOrder(isPaidOrder: boolean, requiresShipping: boolean) {
let builder = OrderBuilder.create()
.customerId('CUST-001');
// Add requirements inline based on conditions
if (isPaidOrder) {
builder = builder.setRequiredFields(['Details.TotalAmount']);
}
if (requiresShipping) {
builder = builder.setRequiredFields([
'Details.ShippingAddress.Street',
'Details.ShippingAddress.City',
'Details.ShippingAddress.Country',
]);
}
return builder;
}
// Free order without shipping (digital product)
const digitalOrder = createOrder(false, false)
.buildSafe(); // ✅ Only CustomerId required
// Paid order without shipping (in-store pickup)
const pickupOrder = createOrder(true, false)
.totalAmount(49.99)
.buildSafe(); // ✅ CustomerId + TotalAmount required
// Paid order with shipping
const shippedOrder = createOrder(true, true)
.totalAmount(99.99)
.shippingStreet('456 Oak Ave')
.shippingCity('Boston')
.shippingCountry('USA')
.buildSafe(); // ✅ All fields validated
```
#### Choosing the Right Approach
| Approach | When to Use | Pros | Cons |
|----------|-------------|------|------|
| **Static Template** | Requirements never change, need reusability | Simple, clear, type-safe, reusable | Inflexible, extra line of code |
| **Inline Constructor** | Simple cases, one factory method | Minimal code, clear, type-safe | Template not reusable elsewhere |
| **Dynamic via Factory** | Multiple predefined scenarios | Reusable, organized, type-safe | More boilerplate |
| **Inline Dynamic** | Runtime-dependent logic | Maximum flexibility | Less discoverable |
**Best Practice**: Combine approaches! Use a static template for core required fields, then add dynamic requirements for conditional scenarios:
```typescript
class OrderBuilder extends CeriosBuilder<Order> {
// Core fields always required
static requiredTemplate: RequiredFieldsTemplate<Order> = [
'Details.CustomerId',
];
static create() {
return new OrderBuilder({});
}
// Scenario-specific factory methods
static createPaid() {
return this.create().setRequiredFields(['Details.TotalAmount']);
}
static createShippable() {
return this.createPaid().setRequiredFields([
'Details.ShippingAddress.Street',
'Details.ShippingAddress.City',
'Details.ShippingAddress.Country',
]);
}
// ... builder methods
}
// Usage is clear and type-safe
const order = OrderBuilder.createShippable()
.customerId('CUST-001')
.totalAmount(199.99)
.shippingStreet('789 Maple Dr')
.shippingCity('Seattle')
.shippingCountry('USA')
.buildSafe(); // ✅ All required fields validated
```
### Building Test Data

@@ -361,11 +992,18 @@

// ❌ No compile-time safety
const user = {
id: "123",
name: "John",
// Oops! Forgot required email field
roles: ["admin"]
const order = {
Order: {
Details: {
CustomerId: "CUST-001",
TotalAmount: 299.99,
// Oops! Forgot required Status field
ShippingAddress: {
Street: "123 Main St",
// Oops! Forgot required City and Country
}
}
}
};
// ❌ Runtime error waiting to happen
userService.createUser(user);
orderService.createOrder(order);
```

@@ -375,14 +1013,31 @@

```typescript
// ✅ Compile-time safety
const user = UserBuilder.create()
.id("123")
.name("John")
.email("john@example.com") // Required - won't compile without it
.addRole("admin")
.build();
// ✅ Type-safe nested property building
const order = OrderRequestBuilder.createWithDefaults()
.customerId('CUST-001')
.totalAmount(299.99)
.shippingStreet('123 Main St')
.shippingCity('New York')
.shippingCountry('USA')
.buildSafe(); // ✅ Runtime validation ensures all required fields are set
// ✅ Guaranteed to have all required fields
userService.createUser(user);
// ✅ Clear error messages
try {
const invalid = OrderRequestBuilder.createWithDefaults()
.customerId('CUST-002')
.buildSafe();
} catch (error) {
console.error(error.message);
// "Missing required fields: Order.Details.TotalAmount,
// Order.Details.ShippingAddress.Street, ..."
}
```
### Benefits
- **Type-Safe Paths**: Dot notation with full IntelliSense support
- **Runtime Validation**: Optional runtime checks with clear error messages
- **Flexible Requirements**: Static templates + dynamic required fields
- **Deep Nesting**: Handle complex nested structures easily
- **Developer Experience**: Better autocomplete and error messages
## 📚 API Reference

@@ -394,9 +1049,51 @@

#### Methods
#### Static Properties
- `requiredTemplate?: RequiredFieldsTemplate<T>` - Optional array of required field paths for runtime validation
#### Instance Methods
- `setProperty<K>(key: K, value: T[K])` - Sets a property and returns a new builder instance
- `setProperties<K>(props: Pick<T, K>)` - Sets multiple properties at once and returns a new builder instance
- `setNestedProperty<P>(path: P, value: PathValue<T, P>)` - Sets a deeply nested property using dot notation
- `addToArrayProperty<K, V>(key: K, value: V)` - Adds a value to an array property and returns a new builder instance
- `build()` - Builds the final object (only available when all required properties are set)
- `setRequiredFields(fields: ReadonlyArray<Path<T>>)` - Sets required fields dynamically for this instance
- `build()` - Builds the final object (only available when all required properties are set via type system)
- `buildSafe()` - Builds the final object with runtime validation of required fields from template
- `buildPartial()` - Builds a partial object with currently set properties
### RequiredFieldsTemplate<T>
Type-safe array of paths for defining required fields.
```typescript
type RequiredFieldsTemplate<T> = ReadonlyArray<Path<T>>;
```
Example:
```typescript
static requiredTemplate: RequiredFieldsTemplate<OrderRequest> = [
'Order.Details.CustomerId',
'Order.Details.TotalAmount',
'Order.Details.ShippingAddress.Street',
];
```
### Path<T>
Type utility that generates all valid dot-notation paths for a type.
```typescript
// For a type like:
type User = {
profile: {
name: string;
email: string;
};
};
// Path<User> includes:
// 'profile' | 'profile.name' | 'profile.email'
```
### CeriosBrand<T>

@@ -406,4 +1103,107 @@

## 📄 License
## � Build Methods Explained
### `build()` - Compile-Time Safety
Use `build()` when you want **compile-time type safety**. TypeScript will enforce that all required properties are set before allowing the build.
```typescript
type User = {
id: string;
name: string;
email: string;
};
const user = UserBuilder.create()
.id('123')
.name('John')
.email('john@example.com')
.build(); // ✅ Compiles - all required fields set
const invalid = UserBuilder.create()
.id('123')
.name('John')
.build(); // ❌ TypeScript error - missing email
```
### `buildSafe()` - Runtime Validation
Use `buildSafe()` when you want **runtime validation** using the `requiredTemplate`. This is useful when:
- Building from user input or external data
- You want to validate deeply nested required fields
- You need dynamic required fields based on business logic
```typescript
class OrderBuilder extends CeriosBuilder<Order> {
static requiredTemplate: RequiredFieldsTemplate<Order> = [
'Details.CustomerId',
'Details.TotalAmount',
];
static create() {
return new OrderBuilder({});
}
// ... methods
}
const order = OrderBuilder.create()
.customerId('CUST-001')
.totalAmount(100)
.buildSafe(); // ✅ Runtime validation passes
const invalid = OrderBuilder.create()
.customerId('CUST-002')
.buildSafe(); // ❌ Throws: "Missing required fields: Details.TotalAmount"
```
### `buildPartial()` - No Validation
Use `buildPartial()` when you need to build an incomplete object:
```typescript
const partial = UserBuilder.create()
.name('Incomplete User')
.buildPartial(); // Returns Partial<User>
// Useful for:
// - Progressive form filling
// - Template objects
// - Partial updates
```
## 💡 Best Practices
1. **Use `setNestedProperty()` for deep structures**: Instead of building nested objects separately, use dot notation for better type safety and cleaner code.
2. **Define `requiredTemplate` for runtime validation**: When working with external data or complex validation rules, define a required template and use `buildSafe()`.
3. **Use `setRequiredFields()` for conditional requirements**: When requirements change based on context (e.g., new vs existing records), use dynamic required fields.
4. **Create factory methods**: Use static `create()` and `createWithDefaults()` methods for better ergonomics:
```typescript
static createWithDefaults() {
return this.create()
.status('pending')
.createdAt(new Date());
}
```
5. **Combine with validation libraries**: Use `buildSafe()` with libraries like Zod for comprehensive validation:
```typescript
const data = builder.buildSafe();
const validated = orderSchema.parse(data); // Zod validation
```
6. **Keep builders focused**: One builder per entity type - don't try to handle multiple unrelated types in one builder.
7. **Use type-safe paths**: The `RequiredFieldsTemplate` type ensures you can only specify valid paths:
```typescript
static requiredTemplate: RequiredFieldsTemplate<Order> = [
'Details.CustomerId', // ✅ Valid path
'Details.InvalidField', // ❌ TypeScript error
];
```
## �📄 License
MIT © [Cerios](LICENSE)

@@ -410,0 +1210,0 @@