@cerios/cerios-builder
Advanced tools
+492
-60
@@ -6,2 +6,5 @@ /** | ||
| * | ||
| * @deprecated Prefer `BuilderComposerFromFactory` (or `ClassBuilderComposerFromFactory`) | ||
| * for callback-based APIs. This alias remains for backward compatibility. | ||
| * | ||
| * @template B - A builder instance type | ||
@@ -11,9 +14,8 @@ * | ||
| * ```typescript | ||
| * // Instead of: | ||
| * function withAddress( | ||
| * builder: AddressBuilder & CeriosBrand<Pick<Address, "city" | "country">> | ||
| * import { BuilderComposerFromFactory } from "@cerios/cerios-builder"; | ||
| * | ||
| * // Preferred modern pattern: | ||
| * function withAddressDefaults( | ||
| * builderFn: BuilderComposerFromFactory<typeof AddressBuilder.createWithDefaults> | ||
| * ) { ... } | ||
| * | ||
| * // You can write: | ||
| * function withAddress(builder: BuilderType<ReturnType<typeof AddressBuilder.createWithDefaults>>) { ... } | ||
| * ``` | ||
@@ -23,2 +25,11 @@ */ | ||
| /** | ||
| * Helper type to extract optional keys from a type. | ||
| * Returns keys where the property can be undefined. | ||
| * | ||
| * @template T - The type to extract optional keys from | ||
| */ | ||
| type OptionalKeys<T> = { | ||
| [K in keyof T]-?: undefined extends T[K] ? K : never; | ||
| }[keyof T]; | ||
| /** | ||
| * Recursively makes all properties readonly for deep immutability. | ||
@@ -29,3 +40,3 @@ * Handles arrays, objects, and primitive types. | ||
| */ | ||
| type DeepReadonly<T> = T extends (infer R)[] ? DeepReadonlyArray<R> : T extends (...args: any[]) => any ? T : T extends object ? DeepReadonlyObject<T> : T; | ||
| type DeepReadonly<T> = T extends (infer R)[] ? DeepReadonlyArray<R> : T extends (...args: unknown[]) => unknown ? T : T extends object ? DeepReadonlyObject<T> : T; | ||
| /** | ||
@@ -52,17 +63,73 @@ * Helper type for deep readonly arrays | ||
| /** | ||
| * Internal brand for builder type-state tracking. | ||
| * Prefer helper aliases like `BuilderStep` in public APIs. | ||
| */ | ||
| type InternalBuilderBrand<T> = { | ||
| [__brand]: T; | ||
| }; | ||
| /** | ||
| * Type utility for branding builder types with information about which properties have been set. | ||
| * This is used to enforce compile-time safety for required fields in the builder pattern. | ||
| * | ||
| * @deprecated Prefer `BuilderStep`, `BuilderPreset`, `BuilderComposer`, or `BuilderComposerFromFactory` in user-facing APIs. | ||
| * This type remains exported for backward compatibility. | ||
| * | ||
| * @template T - The type representing the set of properties that have been set | ||
| * @internal | ||
| */ | ||
| type CeriosBrand<T> = { | ||
| [__brand]: T; | ||
| }; | ||
| type CeriosBrand<T> = InternalBuilderBrand<T>; | ||
| type RootFromPath$1<P extends string> = P extends `${infer K}.${string}` ? K : P; | ||
| type StepKey<T extends object, S extends keyof T | Path<T>> = S extends keyof T ? S : Extract<RootFromPath$1<S & string>, keyof T>; | ||
| /** | ||
| * Helper type for fluent builder methods that set one root property. | ||
| * This keeps method signatures short while preserving compile-time field tracking. | ||
| * Supports both root keys ("name") and dot-notation paths ("address.street"). | ||
| * | ||
| * @template B - The current builder instance type (usually `this`) | ||
| * @template T - The target object type being built | ||
| * @template S - A root key or path in T | ||
| */ | ||
| type BuilderStep<B, T extends object, S extends keyof T | Path<T>> = B & InternalBuilderBrand<Pick<T, StepKey<T, S>>>; | ||
| /** | ||
| * Helper type for factory methods that return a preconfigured builder state. | ||
| * Useful for methods like `createWithDefaults()` where you want an explicit return type | ||
| * without losing compile-time tracking of which fields are already set. | ||
| * | ||
| * @template B - The builder instance type | ||
| * @template T - The target object type being built | ||
| * @template S - A root key or path (or union) already configured by the factory | ||
| */ | ||
| type BuilderPreset<B, T extends object, S extends keyof T | Path<T>> = BuilderStep<B, T, S>; | ||
| /** | ||
| * Helper type for callback-based builder composition APIs. | ||
| * | ||
| * Input builder: | ||
| * - If `Preset` is omitted, callback receives the base builder `B`. | ||
| * - If `Preset` is provided, callback receives a preconfigured builder state. | ||
| * | ||
| * Output builder: | ||
| * - Callback must return a fully buildable state for `T`. | ||
| * | ||
| * @template B - The builder instance type | ||
| * @template T - The target object type being built | ||
| * @template Preset - Optional preset key/path union already configured before callback execution | ||
| */ | ||
| type BuilderComposer<B, T extends object, Preset extends keyof T | Path<T> = never> = (builder: [Preset] extends [never] ? B : BuilderPreset<B, T, Preset>) => BuilderPreset<B, T, keyof T>; | ||
| type BuilderBaseFromFactoryReturn<R> = R extends (infer B) & InternalBuilderBrand<unknown> ? B : R; | ||
| type BuilderTargetFromFactoryReturn<R> = BuilderBaseFromFactoryReturn<R> extends CeriosBuilder<infer T> ? T : never; | ||
| /** | ||
| * Helper type for composition callbacks based on a builder factory method. | ||
| * | ||
| * This infers both the callback input type (including presets/defaults) and the | ||
| * fully-buildable output type directly from the factory return type. | ||
| * | ||
| * @template F - A builder factory function type (for example: `typeof MyBuilder.createWithDefaults`) | ||
| */ | ||
| type BuilderComposerFromFactory<F extends (...args: never[]) => unknown> = (builder: ReturnType<F>) => BuilderPreset<BuilderBaseFromFactoryReturn<ReturnType<F>>, BuilderTargetFromFactoryReturn<ReturnType<F>>, keyof BuilderTargetFromFactoryReturn<ReturnType<F>>>; | ||
| /** | ||
| * Helper type to represent a path through an object structure | ||
| * Handles optional properties by unwrapping them with NonNullable | ||
| */ | ||
| type PathImpl<T, K extends keyof T = keyof T> = K extends string | number ? NonNullable<T[K]> extends Record<string, any> ? NonNullable<T[K]> extends Array<any> ? K : K | `${K}.${PathImpl<NonNullable<T[K]>> & string}` : K : never; | ||
| type Path<T> = PathImpl<T>; | ||
| type PathImpl$1<T, K extends keyof T = keyof T> = K extends string | number ? NonNullable<T[K]> extends object ? NonNullable<T[K]> extends Array<unknown> ? K : K | `${K}.${PathImpl$1<NonNullable<T[K]>> & string}` : K : never; | ||
| type Path<T> = PathImpl$1<T>; | ||
| /** | ||
@@ -78,8 +145,2 @@ * Helper type to get the value at a specific path, handling optional properties | ||
| /** | ||
| * Cache the root key extraction to avoid repeated computation | ||
| * Handles optional properties by ensuring the key is valid for T | ||
| * @internal | ||
| */ | ||
| type RootKey<P extends string, T = any> = P extends `${infer K}.${string}` ? K extends keyof T ? K : never : P extends keyof T ? P : never; | ||
| /** | ||
| * Abstract base class for creating type-safe builders with automatic property setters and compile-time validation of required fields. | ||
@@ -113,2 +174,5 @@ * | ||
| * The template is type-safe - only valid paths from type T can be used. | ||
| * | ||
| * @deprecated Prefer passing required fields via subclass constructor through `super(data, requiredFields)` | ||
| * or setting them at runtime with `setRequiredFields()`. | ||
| */ | ||
@@ -123,2 +187,7 @@ static requiredTemplate?: ReadonlyArray<string>; | ||
| /** | ||
| * Custom validators that run during build. | ||
| * @private | ||
| */ | ||
| private _validators; | ||
| /** | ||
| * Sets the required fields for this builder instance. | ||
@@ -141,2 +210,21 @@ * This allows you to dynamically define which fields are required. | ||
| /** | ||
| * Adds a custom validator function that will be executed during build. | ||
| * Validators can return true for valid, false for invalid, or a string error message. | ||
| * Multiple validators can be added and all will be checked. | ||
| * | ||
| * @param validator - Function that validates the partial object | ||
| * @returns The builder instance for chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder({}) | ||
| * .addValidator(obj => obj.age ? obj.age >= 18 : 'Age must be 18 or older') | ||
| * .addValidator(obj => obj.email?.includes('@') || 'Invalid email format') | ||
| * .setAge(20) | ||
| * .setEmail('user@example.com') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| addValidator(validator: (obj: Partial<T>) => boolean | string): this; | ||
| /** | ||
| * Gets the combined required fields from both the static template and instance-level fields. | ||
@@ -152,2 +240,43 @@ * @private | ||
| /** | ||
| * Runs all custom validators and returns any error messages. | ||
| * @private | ||
| */ | ||
| private runValidators; | ||
| /** | ||
| * Removes an optional property from the builder. | ||
| * Only works with optional properties (those that can be undefined). | ||
| * | ||
| * @template K - The optional property key to remove | ||
| * @param key - The property key to remove | ||
| * @returns A new builder instance without the specified property | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder() | ||
| * .setName('John') | ||
| * .setEmail('john@example.com') | ||
| * .removeOptionalProperty('email'); | ||
| * // Email is now removed from the builder | ||
| * ``` | ||
| */ | ||
| removeOptionalProperty<K extends OptionalKeys<T>>(key: K): this; | ||
| /** | ||
| * Clears all optional properties from the builder, keeping only required ones. | ||
| * Properties in the required template and those marked as required are preserved. | ||
| * | ||
| * @returns A new builder instance with only required properties | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder() | ||
| * .setName('John') // required | ||
| * .setAge(30) // required | ||
| * .setEmail('john@example.com') // optional | ||
| * .setPhone('555-1234') // optional | ||
| * .clearOptionalProperties(); | ||
| * // Only name and age remain | ||
| * ``` | ||
| */ | ||
| clearOptionalProperties(): this; | ||
| /** | ||
| * Creates a new builder instance. Intended to be called by subclasses. | ||
@@ -157,5 +286,6 @@ * | ||
| * @param _requiredFields - Optional array of required field paths to preserve across instances | ||
| * @param _validators - Optional array of validators to preserve across instances | ||
| * @protected | ||
| */ | ||
| protected constructor(_actual: Partial<T>, _requiredFields?: RequiredFieldsTemplate<T>); | ||
| protected constructor(_actual: Partial<T>, _requiredFields?: RequiredFieldsTemplate<T>, _validators?: Array<(obj: Partial<T>) => boolean | string>); | ||
| /** | ||
@@ -171,3 +301,3 @@ * Sets a property value and returns a new builder instance with updated type state. | ||
| */ | ||
| protected setProperty<K extends keyof T>(key: K, value: T[K]): this & CeriosBrand<Pick<T, K>>; | ||
| protected setProperty<K extends keyof T>(key: K, value: T[K]): BuilderStep<this, T, K>; | ||
| /** | ||
@@ -179,3 +309,3 @@ * Sets multiple property values at once and returns a new builder instance with updated type state. | ||
| */ | ||
| protected setProperties<K extends keyof T>(props: Pick<T, K>): this & CeriosBrand<Pick<T, K>>; | ||
| protected setProperties<K extends keyof T>(props: Pick<T, K>): BuilderStep<this, T, K>; | ||
| /** | ||
@@ -196,3 +326,3 @@ * Sets a deeply nested property value and returns a new builder instance with updated type state. | ||
| */ | ||
| protected setNestedProperty<P extends Path<T>>(path: P, value: PathValue<T, P>): this & CeriosBrand<Pick<T, RootKey<P & string, T> extends never ? keyof T : RootKey<P & string, T>>>; | ||
| protected setNestedProperty<P extends Path<T>>(path: P, value: PathValue<T, P>): BuilderStep<this, T, P>; | ||
| /** | ||
@@ -215,4 +345,4 @@ * Deep clone helper for nested objects | ||
| protected addToArrayProperty<K extends { | ||
| [P in keyof T]: NonNullable<T[P]> extends Array<any> ? P : never; | ||
| }[keyof T], V extends T[K] extends Array<infer U> ? U : T[K] extends Array<infer U> | undefined ? U : never>(key: K, value: V): this & CeriosBrand<Pick<T, K>>; | ||
| [P in keyof T]: NonNullable<T[P]> extends Array<unknown> ? P : never; | ||
| }[keyof T], V extends T[K] extends Array<infer U> ? U : T[K] extends Array<infer U> | undefined ? U : never>(key: K, value: V): BuilderStep<this, T, K>; | ||
| /** | ||
@@ -228,3 +358,3 @@ * Builds the final object with both compile-time and runtime validation. | ||
| */ | ||
| build(this: this & CeriosBrand<T>): T; | ||
| build(this: this & InternalBuilderBrand<T>): T; | ||
| /** | ||
@@ -239,3 +369,3 @@ * Builds the final object with only compile-time validation, skipping runtime checks. | ||
| */ | ||
| buildWithoutRuntimeValidation(this: this & CeriosBrand<T>): T; | ||
| buildWithoutRuntimeValidation(this: this & InternalBuilderBrand<T>): T; | ||
| /** | ||
@@ -270,11 +400,36 @@ * Builds the final object with only runtime validation, skipping compile-time checks. | ||
| /** | ||
| * @deprecated Use build() instead. buildSafe() is now an alias for build(). | ||
| * Builds the final object with runtime validation using the requiredTemplate. | ||
| * This method validates that all fields in the requiredTemplate array are present. | ||
| * Creates a new builder instance from an existing object. | ||
| * This is useful for creating builders from existing instances to modify them. | ||
| * | ||
| * @returns The fully built object of type T | ||
| * @throws {Error} If any required field is missing at runtime | ||
| * @param instance - The existing object to create a builder from | ||
| * @returns A new builder instance initialized with the object's data | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const existingPerson = { name: 'John', age: 30 }; | ||
| * const builder = MyBuilder.from(existingPerson); | ||
| * const updated = builder.setAge(31).build(); | ||
| * ``` | ||
| */ | ||
| buildSafe(): T; | ||
| static from<T extends object, B extends new (data: Partial<T>) => unknown>(this: B, instance: T): InstanceType<B>; | ||
| /** | ||
| * Creates a clone of the current builder instance. | ||
| * The clone has the same state but is independent - changes to one won't affect the other. | ||
| * | ||
| * @returns A new builder instance with the same state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder1 = new MyBuilder({}).setName('John'); | ||
| * const builder2 = builder1.clone(); | ||
| * // builder2 is independent of builder1 | ||
| * ``` | ||
| */ | ||
| clone(): this; | ||
| /** | ||
| * Static deep clone helper for the from() method. | ||
| * @private | ||
| */ | ||
| private static deepCloneStatic; | ||
| /** | ||
| * Builds and freezes the final object with both compile-time and runtime validation. | ||
@@ -290,3 +445,3 @@ * The returned object is shallowly frozen - top-level properties cannot be modified, | ||
| */ | ||
| buildFrozen(this: this & CeriosBrand<T>): Readonly<T>; | ||
| buildFrozen(this: this & InternalBuilderBrand<T>): Readonly<T>; | ||
| /** | ||
@@ -302,3 +457,3 @@ * Builds and deeply freezes the final object with both compile-time and runtime validation. | ||
| */ | ||
| buildDeepFrozen(this: this & CeriosBrand<T>): DeepReadonly<T>; | ||
| buildDeepFrozen(this: this & InternalBuilderBrand<T>): DeepReadonly<T>; | ||
| /** | ||
@@ -315,3 +470,3 @@ * Builds and seals the final object with both compile-time and runtime validation. | ||
| */ | ||
| buildSealed(this: this & CeriosBrand<T>): T; | ||
| buildSealed(this: this & InternalBuilderBrand<T>): T; | ||
| /** | ||
@@ -328,3 +483,3 @@ * Builds and deeply seals the final object with both compile-time and runtime validation. | ||
| */ | ||
| buildDeepSealed(this: this & CeriosBrand<T>): T; | ||
| buildDeepSealed(this: this & InternalBuilderBrand<T>): T; | ||
| } | ||
@@ -342,11 +497,70 @@ | ||
| type DataPropertiesOnly<T> = { | ||
| [K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: T[K]; | ||
| [K in keyof T as T[K] extends (...args: unknown[]) => unknown ? never : K]: T[K]; | ||
| }; | ||
| /** | ||
| * Brand type specifically for class builders that only tracks data properties. | ||
| * Internal brand for class-builder type-state tracking. | ||
| * Prefer helper aliases like `ClassBuilderStep` in public APIs. | ||
| */ | ||
| type CeriosClassBrand<T> = { | ||
| type InternalClassBrand<T> = { | ||
| readonly __classBuilderBrand: T; | ||
| }; | ||
| type RootFromPath<P extends string> = P extends `${infer K}.${string}` ? K : P; | ||
| type ClassStepKey<T extends object, S extends keyof T | ClassPath<T>> = S extends keyof T ? Extract<S, keyof DataPropertiesOnly<T>> : Extract<RootFromPath<S & string>, keyof DataPropertiesOnly<T>>; | ||
| /** | ||
| * Helper type for fluent class-builder methods. | ||
| * Supports both direct data-property keys ("name") and nested paths ("address.city"). | ||
| * | ||
| * @template B - The current builder instance type (usually `this`) | ||
| * @template T - The class type being built | ||
| * @template S - A data-property key or class path | ||
| */ | ||
| type ClassBuilderStep<B, T extends object, S extends keyof T | ClassPath<T>> = B & InternalClassBrand<Pick<DataPropertiesOnly<T>, ClassStepKey<T, S>>>; | ||
| /** | ||
| * Helper type for factory methods that return a preconfigured class-builder state. | ||
| * | ||
| * @template B - The class-builder instance type | ||
| * @template T - The class type being built | ||
| * @template S - A data-property key or class path (or union) configured by the factory | ||
| */ | ||
| type ClassBuilderPreset<B, T extends object, S extends keyof DataPropertiesOnly<T> | ClassPath<T>> = ClassBuilderStep<B, T, S>; | ||
| /** | ||
| * Helper type for callback-based class-builder composition APIs. | ||
| * | ||
| * @template B - The class-builder instance type | ||
| * @template T - The class type being built | ||
| * @template Preset - Optional preset key/path union already configured before callback execution | ||
| */ | ||
| type ClassBuilderComposer<B, T extends object, Preset extends keyof DataPropertiesOnly<T> | ClassPath<T> = never> = (builder: [Preset] extends [never] ? B : ClassBuilderPreset<B, T, Preset>) => ClassBuilderPreset<B, T, keyof DataPropertiesOnly<T>>; | ||
| type ClassBuilderBaseFromFactoryReturn<R> = R extends (infer B) & InternalClassBrand<unknown> ? B : R; | ||
| type ClassBuilderTargetFromFactoryReturn<R> = ClassBuilderBaseFromFactoryReturn<R> extends CeriosClassBuilder<infer T> ? T : never; | ||
| /** | ||
| * Helper type for composition callbacks based on a class-builder factory method. | ||
| * | ||
| * This infers both the callback input type (including presets/defaults) and the | ||
| * fully-buildable output type directly from the factory return type. | ||
| * | ||
| * @template F - A class-builder factory function type (for example: `typeof MyBuilder.createWithDefaults`) | ||
| */ | ||
| type ClassBuilderComposerFromFactory<F extends (...args: never[]) => unknown> = (builder: ReturnType<F>) => ClassBuilderPreset<ClassBuilderBaseFromFactoryReturn<ReturnType<F>>, ClassBuilderTargetFromFactoryReturn<ReturnType<F>>, keyof DataPropertiesOnly<ClassBuilderTargetFromFactoryReturn<ReturnType<F>>>>; | ||
| /** | ||
| * Helper type to represent a path through an object structure for class properties. | ||
| * Only considers data properties (excludes methods). | ||
| * Handles optional properties by unwrapping them with NonNullable. | ||
| * @internal | ||
| */ | ||
| type PathImpl<T, K extends keyof DataPropertiesOnly<T> = keyof DataPropertiesOnly<T>> = K extends string | number ? NonNullable<DataPropertiesOnly<T>[K]> extends object ? NonNullable<DataPropertiesOnly<T>[K]> extends Array<unknown> ? K : K | `${K}.${PathImpl<NonNullable<DataPropertiesOnly<T>[K]>> & string}` : K : never; | ||
| /** | ||
| * Type representing valid dot-notation paths through class data properties. | ||
| * @template T - The class type | ||
| */ | ||
| type ClassPath<T> = PathImpl<T>; | ||
| /** | ||
| * Helper type to get the value at a specific path in a class, handling optional properties. | ||
| * Only considers data properties (excludes methods). | ||
| * @template T - The class type | ||
| * @template P - The path string | ||
| * @internal | ||
| */ | ||
| type ClassPathValue<T, P> = P extends keyof DataPropertiesOnly<T> ? DataPropertiesOnly<T>[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof DataPropertiesOnly<T> ? ClassPathValue<NonNullable<DataPropertiesOnly<T>[K]>, Rest> : never : never; | ||
| /** | ||
| * Type-safe builder specifically designed for classes. | ||
@@ -383,2 +597,18 @@ * This builder automatically instantiates the target class and provides: | ||
| /** | ||
| * Optional static template defining which data properties are required. | ||
| * Subclasses can set this to specify required fields, which will be preserved | ||
| * when calling clearOptionalProperties(). | ||
| * | ||
| * @deprecated Prefer passing required fields via subclass constructor and `super(...)` | ||
| * or setting them at runtime with `setRequiredFields()`. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class PersonBuilder extends CeriosClassBuilder<Person> { | ||
| * static requiredDataProperties = ['name', 'age'] as const; | ||
| * } | ||
| * ``` | ||
| */ | ||
| static requiredDataProperties?: ReadonlyArray<string>; | ||
| /** | ||
| * The class constructor to instantiate when building. | ||
@@ -394,7 +624,20 @@ * @private | ||
| /** | ||
| * Custom validators that run during build. | ||
| * @private | ||
| */ | ||
| private _validators; | ||
| /** | ||
| * Instance-level required fields that can be populated dynamically. | ||
| * This allows adding required fields at runtime via the setRequiredFields method. | ||
| * @private | ||
| */ | ||
| private _requiredFields; | ||
| /** | ||
| * Creates a new class builder instance. | ||
| * @param classConstructor - The class constructor to use for building | ||
| * @param data - Optional initial data | ||
| * @param _validators - Optional array of validators to preserve across instances | ||
| * @param _requiredFields - Optional required fields to preserve across instances | ||
| */ | ||
| protected constructor(classConstructor: ClassConstructor<T>, data?: Partial<T>); | ||
| protected constructor(classConstructor: ClassConstructor<T>, data?: Partial<T>, _validators?: Array<(obj: Partial<T>) => boolean | string>, _requiredFields?: ReadonlyArray<ClassPath<T>> | Set<string>); | ||
| /** | ||
@@ -417,5 +660,14 @@ * Gets the class constructor for this builder. | ||
| * @returns A new builder instance with the property set | ||
| * @protected | ||
| */ | ||
| setProperty<K extends keyof DataPropertiesOnly<T>>(key: K, value: DataPropertiesOnly<T>[K]): this & CeriosClassBrand<Pick<DataPropertiesOnly<T>, K>>; | ||
| protected setProperty<K extends keyof DataPropertiesOnly<T>>(key: K, value: DataPropertiesOnly<T>[K]): ClassBuilderStep<this, T, K>; | ||
| /** | ||
| * Fallback overload for generic subclass scenarios where TypeScript cannot | ||
| * resolve `keyof DataPropertiesOnly<T>` from a literal key. | ||
| * This keeps fluent APIs ergonomic in shared generic base builders. | ||
| * @protected | ||
| */ | ||
| protected setProperty<K extends keyof T & string>(key: K, value: T[K]): ClassBuilderStep<this, T, K>; | ||
| protected setProperty<K extends keyof DataPropertiesOnly<T>>(key: K, value: DataPropertiesOnly<T>[K]): ClassBuilderStep<this, T, K>; | ||
| /** | ||
| * Sets multiple properties at once. | ||
@@ -425,5 +677,137 @@ * @template K - The property keys being set | ||
| * @returns A new builder instance with the properties set | ||
| * @protected | ||
| */ | ||
| setProperties<K extends keyof DataPropertiesOnly<T>>(props: Pick<DataPropertiesOnly<T>, K>): this & CeriosClassBrand<Pick<DataPropertiesOnly<T>, K>>; | ||
| protected setProperties<K extends keyof DataPropertiesOnly<T>>(props: Pick<DataPropertiesOnly<T>, K>): ClassBuilderStep<this, T, K>; | ||
| /** | ||
| * 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. | ||
| * Only supports data properties (excludes methods). | ||
| * | ||
| * @template P - The property path (e.g., "address.city") | ||
| * @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 | ||
| * class Address { | ||
| * street!: string; | ||
| * city!: string; | ||
| * } | ||
| * class Person { | ||
| * name!: string; | ||
| * address!: Address; | ||
| * } | ||
| * const builder = new CeriosClassBuilder(Person); | ||
| * const person = builder | ||
| * .setNestedProperty('address.city', 'New York') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| protected setNestedProperty<P extends ClassPath<T>>(path: P, value: ClassPathValue<T, P>): ClassBuilderStep<this, T, P>; | ||
| /** | ||
| * 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 = PersonBuilder.create() | ||
| * .setRequiredFields(['name', 'age']) | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('age', 30) | ||
| * .buildWithoutCompileTimeValidation(); | ||
| * ``` | ||
| */ | ||
| setRequiredFields(fields: ReadonlyArray<ClassPath<T>>): this; | ||
| /** | ||
| * Gets the combined required fields from both the static template and instance-level fields. | ||
| * If instance-level fields are set via setRequiredFields(), they are combined with static fields. | ||
| * @private | ||
| */ | ||
| private getRequiredTemplate; | ||
| /** | ||
| * Validates that all fields in the required template have been set. | ||
| * @private | ||
| */ | ||
| private validateRequiredFields; | ||
| /** | ||
| * Adds a custom validator function that will be executed during build. | ||
| * Validators can return true for valid, false for invalid, or a string error message. | ||
| * Multiple validators can be added and all will be checked. | ||
| * | ||
| * @param validator - Function that validates the partial object | ||
| * @returns The builder instance for chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = PersonBuilder.create() | ||
| * .addValidator(obj => obj.age ? obj.age >= 18 : 'Age must be 18 or older') | ||
| * .addValidator(obj => obj.email?.includes('@') || 'Invalid email format') | ||
| * .setProperty('age', 20) | ||
| * .setProperty('email', 'user@example.com') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| addValidator(validator: (obj: Partial<T>) => boolean | string): this; | ||
| /** | ||
| * Runs all custom validators and returns any error messages. | ||
| * @private | ||
| */ | ||
| private runValidators; | ||
| /** | ||
| * Removes an optional property from the builder. | ||
| * Only works with optional data properties (those that can be undefined). | ||
| * Methods are automatically excluded. | ||
| * | ||
| * @template K - The optional property key to remove | ||
| * @param key - The property key to remove | ||
| * @returns A new builder instance without the specified property | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Person { | ||
| * name!: string; | ||
| * email?: string; | ||
| * } | ||
| * const builder = PersonBuilder.create() | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('email', 'john@example.com') | ||
| * .removeOptionalProperty('email'); | ||
| * // Email is now removed from the builder | ||
| * ``` | ||
| */ | ||
| removeOptionalProperty<K extends OptionalKeys<DataPropertiesOnly<T>>>(key: K): this; | ||
| /** | ||
| * Clears all optional properties from the builder, keeping only required data properties. | ||
| * Uses the combined required-field template from static defaults and instance-level fields. | ||
| * If no required fields are configured, all properties are cleared. | ||
| * | ||
| * @returns A new builder instance with only required properties | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Person { | ||
| * name!: string; // required | ||
| * age!: number; // required | ||
| * email?: string; // optional | ||
| * phone?: string; // optional | ||
| * } | ||
| * class PersonBuilder extends CeriosClassBuilder<Person> { | ||
| * static requiredDataProperties = ['name', 'age'] as const; | ||
| * } | ||
| * const builder = PersonBuilder.create() | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('age', 30) | ||
| * .setProperty('email', 'john@example.com') | ||
| * .setProperty('phone', '555-1234') | ||
| * .clearOptionalProperties(); | ||
| * // Only name and age are preserved, email and phone are cleared | ||
| * ``` | ||
| */ | ||
| clearOptionalProperties(): this; | ||
| /** | ||
| * Adds a value to an array property. | ||
@@ -437,20 +821,33 @@ * @template K - The array property key | ||
| addToArrayProperty<K extends { | ||
| [P in keyof DataPropertiesOnly<T>]: NonNullable<DataPropertiesOnly<T>[P]> extends Array<any> ? P : never; | ||
| }[keyof DataPropertiesOnly<T>], V extends DataPropertiesOnly<T>[K] extends Array<infer U> ? U : DataPropertiesOnly<T>[K] extends Array<infer U> | undefined ? U : never>(key: K, value: V): this & CeriosClassBrand<Pick<DataPropertiesOnly<T>, K>>; | ||
| [P in keyof DataPropertiesOnly<T>]: NonNullable<DataPropertiesOnly<T>[P]> extends Array<unknown> ? P : never; | ||
| }[keyof DataPropertiesOnly<T>], V extends DataPropertiesOnly<T>[K] extends Array<infer U> ? U : DataPropertiesOnly<T>[K] extends Array<infer U> | undefined ? U : never>(key: K, value: V): ClassBuilderStep<this, T, K>; | ||
| /** | ||
| * Builds the final class instance with compile-time and runtime validation. | ||
| * - Compile-time: TypeScript enforces all data properties are set | ||
| * - Runtime: Validates nested class instances are properly instantiated | ||
| * - Runtime: Validates required fields and custom validators | ||
| * | ||
| * @returns The fully built and validated class instance | ||
| * @throws {Error} If nested validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| build(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): T; | ||
| build(this: this & InternalClassBrand<DataPropertiesOnly<T>>): T; | ||
| /** | ||
| * Builds the class instance with runtime validation only (no compile-time). | ||
| * Use this when building from external/dynamic data. | ||
| * Builds the final class instance with only compile-time validation, skipping runtime checks. | ||
| * Use this when you want TypeScript safety but need to skip runtime validation for performance. | ||
| * | ||
| * @returns The built class instance | ||
| * @throws {Error} If validation fails | ||
| * - Compile-time: TypeScript enforces all data properties are set | ||
| * - Runtime: No validation | ||
| * | ||
| * @returns The fully built class instance | ||
| */ | ||
| buildWithoutRuntimeValidation(this: this & InternalClassBrand<DataPropertiesOnly<T>>): T; | ||
| /** | ||
| * Builds the final class instance with only runtime validation, skipping compile-time checks. | ||
| * Use this when building from external data where compile-time checks aren't possible. | ||
| * | ||
| * - Compile-time: No TypeScript enforcement | ||
| * - Runtime: Validates required fields and custom validators | ||
| * | ||
| * @returns The fully built class instance | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildWithoutCompileTimeValidation(): T; | ||
@@ -474,5 +871,5 @@ /** | ||
| * @returns The frozen class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildFrozen(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): Readonly<T>; | ||
| buildFrozen(this: this & InternalClassBrand<DataPropertiesOnly<T>>): Readonly<T>; | ||
| /** | ||
@@ -482,5 +879,5 @@ * Builds and deeply freezes the class instance. | ||
| * @returns The deeply frozen class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildDeepFrozen(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): DeepReadonly<T>; | ||
| buildDeepFrozen(this: this & InternalClassBrand<DataPropertiesOnly<T>>): DeepReadonly<T>; | ||
| /** | ||
@@ -490,5 +887,5 @@ * Builds and seals the class instance (shallow freeze). | ||
| * @returns The sealed class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildSealed(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): T; | ||
| buildSealed(this: this & InternalClassBrand<DataPropertiesOnly<T>>): T; | ||
| /** | ||
@@ -498,5 +895,5 @@ * Builds and deeply seals the class instance. | ||
| * @returns The deeply sealed class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildDeepSealed(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): T; | ||
| buildDeepSealed(this: this & InternalClassBrand<DataPropertiesOnly<T>>): T; | ||
| /** | ||
@@ -517,4 +914,39 @@ * Deep freeze helper. | ||
| private deepClone; | ||
| /** | ||
| * Creates a new builder instance from an existing class instance. | ||
| * This is useful for creating builders from existing instances to modify them. | ||
| * | ||
| * @param classConstructor - The class constructor to use | ||
| * @param instance - The existing class instance to create a builder from | ||
| * @returns A new builder instance initialized with the instance's data | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const existingPerson = new Person({ name: 'John', age: 30 }); | ||
| * const builder = PersonBuilder.from(Person, existingPerson); | ||
| * const updated = builder.setProperty('age', 31).build(); | ||
| * ``` | ||
| */ | ||
| static from<T extends object, B extends new (classConstructor: ClassConstructor<T>, data: Partial<T>) => unknown>(this: B, classConstructor: ClassConstructor<T>, instance: T): InstanceType<B>; | ||
| /** | ||
| * Creates a clone of the current builder instance. | ||
| * The clone has the same state but is independent - changes to one won't affect the other. | ||
| * | ||
| * @returns A new builder instance with the same state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder1 = PersonBuilder.create().setProperty('name', 'John'); | ||
| * const builder2 = builder1.clone(); | ||
| * // builder2 is independent of builder1 | ||
| * ``` | ||
| */ | ||
| clone(): this; | ||
| /** | ||
| * Static deep clone helper for the from() method. | ||
| * @private | ||
| */ | ||
| private static deepCloneStatic; | ||
| } | ||
| export { type BuilderType, type CeriosBrand, CeriosBuilder, CeriosClassBuilder, type ClassConstructor, type DeepReadonly, type RequiredFieldsTemplate }; | ||
| export { type BuilderComposer, type BuilderComposerFromFactory, type BuilderPreset, type BuilderStep, type BuilderType, type CeriosBrand, CeriosBuilder, CeriosClassBuilder, type ClassBuilderComposer, type ClassBuilderComposerFromFactory, type ClassBuilderPreset, type ClassBuilderStep, type ClassConstructor, type ClassPath, type DeepReadonly, type OptionalKeys, type RequiredFieldsTemplate }; |
+492
-60
@@ -6,2 +6,5 @@ /** | ||
| * | ||
| * @deprecated Prefer `BuilderComposerFromFactory` (or `ClassBuilderComposerFromFactory`) | ||
| * for callback-based APIs. This alias remains for backward compatibility. | ||
| * | ||
| * @template B - A builder instance type | ||
@@ -11,9 +14,8 @@ * | ||
| * ```typescript | ||
| * // Instead of: | ||
| * function withAddress( | ||
| * builder: AddressBuilder & CeriosBrand<Pick<Address, "city" | "country">> | ||
| * import { BuilderComposerFromFactory } from "@cerios/cerios-builder"; | ||
| * | ||
| * // Preferred modern pattern: | ||
| * function withAddressDefaults( | ||
| * builderFn: BuilderComposerFromFactory<typeof AddressBuilder.createWithDefaults> | ||
| * ) { ... } | ||
| * | ||
| * // You can write: | ||
| * function withAddress(builder: BuilderType<ReturnType<typeof AddressBuilder.createWithDefaults>>) { ... } | ||
| * ``` | ||
@@ -23,2 +25,11 @@ */ | ||
| /** | ||
| * Helper type to extract optional keys from a type. | ||
| * Returns keys where the property can be undefined. | ||
| * | ||
| * @template T - The type to extract optional keys from | ||
| */ | ||
| type OptionalKeys<T> = { | ||
| [K in keyof T]-?: undefined extends T[K] ? K : never; | ||
| }[keyof T]; | ||
| /** | ||
| * Recursively makes all properties readonly for deep immutability. | ||
@@ -29,3 +40,3 @@ * Handles arrays, objects, and primitive types. | ||
| */ | ||
| type DeepReadonly<T> = T extends (infer R)[] ? DeepReadonlyArray<R> : T extends (...args: any[]) => any ? T : T extends object ? DeepReadonlyObject<T> : T; | ||
| type DeepReadonly<T> = T extends (infer R)[] ? DeepReadonlyArray<R> : T extends (...args: unknown[]) => unknown ? T : T extends object ? DeepReadonlyObject<T> : T; | ||
| /** | ||
@@ -52,17 +63,73 @@ * Helper type for deep readonly arrays | ||
| /** | ||
| * Internal brand for builder type-state tracking. | ||
| * Prefer helper aliases like `BuilderStep` in public APIs. | ||
| */ | ||
| type InternalBuilderBrand<T> = { | ||
| [__brand]: T; | ||
| }; | ||
| /** | ||
| * Type utility for branding builder types with information about which properties have been set. | ||
| * This is used to enforce compile-time safety for required fields in the builder pattern. | ||
| * | ||
| * @deprecated Prefer `BuilderStep`, `BuilderPreset`, `BuilderComposer`, or `BuilderComposerFromFactory` in user-facing APIs. | ||
| * This type remains exported for backward compatibility. | ||
| * | ||
| * @template T - The type representing the set of properties that have been set | ||
| * @internal | ||
| */ | ||
| type CeriosBrand<T> = { | ||
| [__brand]: T; | ||
| }; | ||
| type CeriosBrand<T> = InternalBuilderBrand<T>; | ||
| type RootFromPath$1<P extends string> = P extends `${infer K}.${string}` ? K : P; | ||
| type StepKey<T extends object, S extends keyof T | Path<T>> = S extends keyof T ? S : Extract<RootFromPath$1<S & string>, keyof T>; | ||
| /** | ||
| * Helper type for fluent builder methods that set one root property. | ||
| * This keeps method signatures short while preserving compile-time field tracking. | ||
| * Supports both root keys ("name") and dot-notation paths ("address.street"). | ||
| * | ||
| * @template B - The current builder instance type (usually `this`) | ||
| * @template T - The target object type being built | ||
| * @template S - A root key or path in T | ||
| */ | ||
| type BuilderStep<B, T extends object, S extends keyof T | Path<T>> = B & InternalBuilderBrand<Pick<T, StepKey<T, S>>>; | ||
| /** | ||
| * Helper type for factory methods that return a preconfigured builder state. | ||
| * Useful for methods like `createWithDefaults()` where you want an explicit return type | ||
| * without losing compile-time tracking of which fields are already set. | ||
| * | ||
| * @template B - The builder instance type | ||
| * @template T - The target object type being built | ||
| * @template S - A root key or path (or union) already configured by the factory | ||
| */ | ||
| type BuilderPreset<B, T extends object, S extends keyof T | Path<T>> = BuilderStep<B, T, S>; | ||
| /** | ||
| * Helper type for callback-based builder composition APIs. | ||
| * | ||
| * Input builder: | ||
| * - If `Preset` is omitted, callback receives the base builder `B`. | ||
| * - If `Preset` is provided, callback receives a preconfigured builder state. | ||
| * | ||
| * Output builder: | ||
| * - Callback must return a fully buildable state for `T`. | ||
| * | ||
| * @template B - The builder instance type | ||
| * @template T - The target object type being built | ||
| * @template Preset - Optional preset key/path union already configured before callback execution | ||
| */ | ||
| type BuilderComposer<B, T extends object, Preset extends keyof T | Path<T> = never> = (builder: [Preset] extends [never] ? B : BuilderPreset<B, T, Preset>) => BuilderPreset<B, T, keyof T>; | ||
| type BuilderBaseFromFactoryReturn<R> = R extends (infer B) & InternalBuilderBrand<unknown> ? B : R; | ||
| type BuilderTargetFromFactoryReturn<R> = BuilderBaseFromFactoryReturn<R> extends CeriosBuilder<infer T> ? T : never; | ||
| /** | ||
| * Helper type for composition callbacks based on a builder factory method. | ||
| * | ||
| * This infers both the callback input type (including presets/defaults) and the | ||
| * fully-buildable output type directly from the factory return type. | ||
| * | ||
| * @template F - A builder factory function type (for example: `typeof MyBuilder.createWithDefaults`) | ||
| */ | ||
| type BuilderComposerFromFactory<F extends (...args: never[]) => unknown> = (builder: ReturnType<F>) => BuilderPreset<BuilderBaseFromFactoryReturn<ReturnType<F>>, BuilderTargetFromFactoryReturn<ReturnType<F>>, keyof BuilderTargetFromFactoryReturn<ReturnType<F>>>; | ||
| /** | ||
| * Helper type to represent a path through an object structure | ||
| * Handles optional properties by unwrapping them with NonNullable | ||
| */ | ||
| type PathImpl<T, K extends keyof T = keyof T> = K extends string | number ? NonNullable<T[K]> extends Record<string, any> ? NonNullable<T[K]> extends Array<any> ? K : K | `${K}.${PathImpl<NonNullable<T[K]>> & string}` : K : never; | ||
| type Path<T> = PathImpl<T>; | ||
| type PathImpl$1<T, K extends keyof T = keyof T> = K extends string | number ? NonNullable<T[K]> extends object ? NonNullable<T[K]> extends Array<unknown> ? K : K | `${K}.${PathImpl$1<NonNullable<T[K]>> & string}` : K : never; | ||
| type Path<T> = PathImpl$1<T>; | ||
| /** | ||
@@ -78,8 +145,2 @@ * Helper type to get the value at a specific path, handling optional properties | ||
| /** | ||
| * Cache the root key extraction to avoid repeated computation | ||
| * Handles optional properties by ensuring the key is valid for T | ||
| * @internal | ||
| */ | ||
| type RootKey<P extends string, T = any> = P extends `${infer K}.${string}` ? K extends keyof T ? K : never : P extends keyof T ? P : never; | ||
| /** | ||
| * Abstract base class for creating type-safe builders with automatic property setters and compile-time validation of required fields. | ||
@@ -113,2 +174,5 @@ * | ||
| * The template is type-safe - only valid paths from type T can be used. | ||
| * | ||
| * @deprecated Prefer passing required fields via subclass constructor through `super(data, requiredFields)` | ||
| * or setting them at runtime with `setRequiredFields()`. | ||
| */ | ||
@@ -123,2 +187,7 @@ static requiredTemplate?: ReadonlyArray<string>; | ||
| /** | ||
| * Custom validators that run during build. | ||
| * @private | ||
| */ | ||
| private _validators; | ||
| /** | ||
| * Sets the required fields for this builder instance. | ||
@@ -141,2 +210,21 @@ * This allows you to dynamically define which fields are required. | ||
| /** | ||
| * Adds a custom validator function that will be executed during build. | ||
| * Validators can return true for valid, false for invalid, or a string error message. | ||
| * Multiple validators can be added and all will be checked. | ||
| * | ||
| * @param validator - Function that validates the partial object | ||
| * @returns The builder instance for chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder({}) | ||
| * .addValidator(obj => obj.age ? obj.age >= 18 : 'Age must be 18 or older') | ||
| * .addValidator(obj => obj.email?.includes('@') || 'Invalid email format') | ||
| * .setAge(20) | ||
| * .setEmail('user@example.com') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| addValidator(validator: (obj: Partial<T>) => boolean | string): this; | ||
| /** | ||
| * Gets the combined required fields from both the static template and instance-level fields. | ||
@@ -152,2 +240,43 @@ * @private | ||
| /** | ||
| * Runs all custom validators and returns any error messages. | ||
| * @private | ||
| */ | ||
| private runValidators; | ||
| /** | ||
| * Removes an optional property from the builder. | ||
| * Only works with optional properties (those that can be undefined). | ||
| * | ||
| * @template K - The optional property key to remove | ||
| * @param key - The property key to remove | ||
| * @returns A new builder instance without the specified property | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder() | ||
| * .setName('John') | ||
| * .setEmail('john@example.com') | ||
| * .removeOptionalProperty('email'); | ||
| * // Email is now removed from the builder | ||
| * ``` | ||
| */ | ||
| removeOptionalProperty<K extends OptionalKeys<T>>(key: K): this; | ||
| /** | ||
| * Clears all optional properties from the builder, keeping only required ones. | ||
| * Properties in the required template and those marked as required are preserved. | ||
| * | ||
| * @returns A new builder instance with only required properties | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder() | ||
| * .setName('John') // required | ||
| * .setAge(30) // required | ||
| * .setEmail('john@example.com') // optional | ||
| * .setPhone('555-1234') // optional | ||
| * .clearOptionalProperties(); | ||
| * // Only name and age remain | ||
| * ``` | ||
| */ | ||
| clearOptionalProperties(): this; | ||
| /** | ||
| * Creates a new builder instance. Intended to be called by subclasses. | ||
@@ -157,5 +286,6 @@ * | ||
| * @param _requiredFields - Optional array of required field paths to preserve across instances | ||
| * @param _validators - Optional array of validators to preserve across instances | ||
| * @protected | ||
| */ | ||
| protected constructor(_actual: Partial<T>, _requiredFields?: RequiredFieldsTemplate<T>); | ||
| protected constructor(_actual: Partial<T>, _requiredFields?: RequiredFieldsTemplate<T>, _validators?: Array<(obj: Partial<T>) => boolean | string>); | ||
| /** | ||
@@ -171,3 +301,3 @@ * Sets a property value and returns a new builder instance with updated type state. | ||
| */ | ||
| protected setProperty<K extends keyof T>(key: K, value: T[K]): this & CeriosBrand<Pick<T, K>>; | ||
| protected setProperty<K extends keyof T>(key: K, value: T[K]): BuilderStep<this, T, K>; | ||
| /** | ||
@@ -179,3 +309,3 @@ * Sets multiple property values at once and returns a new builder instance with updated type state. | ||
| */ | ||
| protected setProperties<K extends keyof T>(props: Pick<T, K>): this & CeriosBrand<Pick<T, K>>; | ||
| protected setProperties<K extends keyof T>(props: Pick<T, K>): BuilderStep<this, T, K>; | ||
| /** | ||
@@ -196,3 +326,3 @@ * Sets a deeply nested property value and returns a new builder instance with updated type state. | ||
| */ | ||
| protected setNestedProperty<P extends Path<T>>(path: P, value: PathValue<T, P>): this & CeriosBrand<Pick<T, RootKey<P & string, T> extends never ? keyof T : RootKey<P & string, T>>>; | ||
| protected setNestedProperty<P extends Path<T>>(path: P, value: PathValue<T, P>): BuilderStep<this, T, P>; | ||
| /** | ||
@@ -215,4 +345,4 @@ * Deep clone helper for nested objects | ||
| protected addToArrayProperty<K extends { | ||
| [P in keyof T]: NonNullable<T[P]> extends Array<any> ? P : never; | ||
| }[keyof T], V extends T[K] extends Array<infer U> ? U : T[K] extends Array<infer U> | undefined ? U : never>(key: K, value: V): this & CeriosBrand<Pick<T, K>>; | ||
| [P in keyof T]: NonNullable<T[P]> extends Array<unknown> ? P : never; | ||
| }[keyof T], V extends T[K] extends Array<infer U> ? U : T[K] extends Array<infer U> | undefined ? U : never>(key: K, value: V): BuilderStep<this, T, K>; | ||
| /** | ||
@@ -228,3 +358,3 @@ * Builds the final object with both compile-time and runtime validation. | ||
| */ | ||
| build(this: this & CeriosBrand<T>): T; | ||
| build(this: this & InternalBuilderBrand<T>): T; | ||
| /** | ||
@@ -239,3 +369,3 @@ * Builds the final object with only compile-time validation, skipping runtime checks. | ||
| */ | ||
| buildWithoutRuntimeValidation(this: this & CeriosBrand<T>): T; | ||
| buildWithoutRuntimeValidation(this: this & InternalBuilderBrand<T>): T; | ||
| /** | ||
@@ -270,11 +400,36 @@ * Builds the final object with only runtime validation, skipping compile-time checks. | ||
| /** | ||
| * @deprecated Use build() instead. buildSafe() is now an alias for build(). | ||
| * Builds the final object with runtime validation using the requiredTemplate. | ||
| * This method validates that all fields in the requiredTemplate array are present. | ||
| * Creates a new builder instance from an existing object. | ||
| * This is useful for creating builders from existing instances to modify them. | ||
| * | ||
| * @returns The fully built object of type T | ||
| * @throws {Error} If any required field is missing at runtime | ||
| * @param instance - The existing object to create a builder from | ||
| * @returns A new builder instance initialized with the object's data | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const existingPerson = { name: 'John', age: 30 }; | ||
| * const builder = MyBuilder.from(existingPerson); | ||
| * const updated = builder.setAge(31).build(); | ||
| * ``` | ||
| */ | ||
| buildSafe(): T; | ||
| static from<T extends object, B extends new (data: Partial<T>) => unknown>(this: B, instance: T): InstanceType<B>; | ||
| /** | ||
| * Creates a clone of the current builder instance. | ||
| * The clone has the same state but is independent - changes to one won't affect the other. | ||
| * | ||
| * @returns A new builder instance with the same state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder1 = new MyBuilder({}).setName('John'); | ||
| * const builder2 = builder1.clone(); | ||
| * // builder2 is independent of builder1 | ||
| * ``` | ||
| */ | ||
| clone(): this; | ||
| /** | ||
| * Static deep clone helper for the from() method. | ||
| * @private | ||
| */ | ||
| private static deepCloneStatic; | ||
| /** | ||
| * Builds and freezes the final object with both compile-time and runtime validation. | ||
@@ -290,3 +445,3 @@ * The returned object is shallowly frozen - top-level properties cannot be modified, | ||
| */ | ||
| buildFrozen(this: this & CeriosBrand<T>): Readonly<T>; | ||
| buildFrozen(this: this & InternalBuilderBrand<T>): Readonly<T>; | ||
| /** | ||
@@ -302,3 +457,3 @@ * Builds and deeply freezes the final object with both compile-time and runtime validation. | ||
| */ | ||
| buildDeepFrozen(this: this & CeriosBrand<T>): DeepReadonly<T>; | ||
| buildDeepFrozen(this: this & InternalBuilderBrand<T>): DeepReadonly<T>; | ||
| /** | ||
@@ -315,3 +470,3 @@ * Builds and seals the final object with both compile-time and runtime validation. | ||
| */ | ||
| buildSealed(this: this & CeriosBrand<T>): T; | ||
| buildSealed(this: this & InternalBuilderBrand<T>): T; | ||
| /** | ||
@@ -328,3 +483,3 @@ * Builds and deeply seals the final object with both compile-time and runtime validation. | ||
| */ | ||
| buildDeepSealed(this: this & CeriosBrand<T>): T; | ||
| buildDeepSealed(this: this & InternalBuilderBrand<T>): T; | ||
| } | ||
@@ -342,11 +497,70 @@ | ||
| type DataPropertiesOnly<T> = { | ||
| [K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: T[K]; | ||
| [K in keyof T as T[K] extends (...args: unknown[]) => unknown ? never : K]: T[K]; | ||
| }; | ||
| /** | ||
| * Brand type specifically for class builders that only tracks data properties. | ||
| * Internal brand for class-builder type-state tracking. | ||
| * Prefer helper aliases like `ClassBuilderStep` in public APIs. | ||
| */ | ||
| type CeriosClassBrand<T> = { | ||
| type InternalClassBrand<T> = { | ||
| readonly __classBuilderBrand: T; | ||
| }; | ||
| type RootFromPath<P extends string> = P extends `${infer K}.${string}` ? K : P; | ||
| type ClassStepKey<T extends object, S extends keyof T | ClassPath<T>> = S extends keyof T ? Extract<S, keyof DataPropertiesOnly<T>> : Extract<RootFromPath<S & string>, keyof DataPropertiesOnly<T>>; | ||
| /** | ||
| * Helper type for fluent class-builder methods. | ||
| * Supports both direct data-property keys ("name") and nested paths ("address.city"). | ||
| * | ||
| * @template B - The current builder instance type (usually `this`) | ||
| * @template T - The class type being built | ||
| * @template S - A data-property key or class path | ||
| */ | ||
| type ClassBuilderStep<B, T extends object, S extends keyof T | ClassPath<T>> = B & InternalClassBrand<Pick<DataPropertiesOnly<T>, ClassStepKey<T, S>>>; | ||
| /** | ||
| * Helper type for factory methods that return a preconfigured class-builder state. | ||
| * | ||
| * @template B - The class-builder instance type | ||
| * @template T - The class type being built | ||
| * @template S - A data-property key or class path (or union) configured by the factory | ||
| */ | ||
| type ClassBuilderPreset<B, T extends object, S extends keyof DataPropertiesOnly<T> | ClassPath<T>> = ClassBuilderStep<B, T, S>; | ||
| /** | ||
| * Helper type for callback-based class-builder composition APIs. | ||
| * | ||
| * @template B - The class-builder instance type | ||
| * @template T - The class type being built | ||
| * @template Preset - Optional preset key/path union already configured before callback execution | ||
| */ | ||
| type ClassBuilderComposer<B, T extends object, Preset extends keyof DataPropertiesOnly<T> | ClassPath<T> = never> = (builder: [Preset] extends [never] ? B : ClassBuilderPreset<B, T, Preset>) => ClassBuilderPreset<B, T, keyof DataPropertiesOnly<T>>; | ||
| type ClassBuilderBaseFromFactoryReturn<R> = R extends (infer B) & InternalClassBrand<unknown> ? B : R; | ||
| type ClassBuilderTargetFromFactoryReturn<R> = ClassBuilderBaseFromFactoryReturn<R> extends CeriosClassBuilder<infer T> ? T : never; | ||
| /** | ||
| * Helper type for composition callbacks based on a class-builder factory method. | ||
| * | ||
| * This infers both the callback input type (including presets/defaults) and the | ||
| * fully-buildable output type directly from the factory return type. | ||
| * | ||
| * @template F - A class-builder factory function type (for example: `typeof MyBuilder.createWithDefaults`) | ||
| */ | ||
| type ClassBuilderComposerFromFactory<F extends (...args: never[]) => unknown> = (builder: ReturnType<F>) => ClassBuilderPreset<ClassBuilderBaseFromFactoryReturn<ReturnType<F>>, ClassBuilderTargetFromFactoryReturn<ReturnType<F>>, keyof DataPropertiesOnly<ClassBuilderTargetFromFactoryReturn<ReturnType<F>>>>; | ||
| /** | ||
| * Helper type to represent a path through an object structure for class properties. | ||
| * Only considers data properties (excludes methods). | ||
| * Handles optional properties by unwrapping them with NonNullable. | ||
| * @internal | ||
| */ | ||
| type PathImpl<T, K extends keyof DataPropertiesOnly<T> = keyof DataPropertiesOnly<T>> = K extends string | number ? NonNullable<DataPropertiesOnly<T>[K]> extends object ? NonNullable<DataPropertiesOnly<T>[K]> extends Array<unknown> ? K : K | `${K}.${PathImpl<NonNullable<DataPropertiesOnly<T>[K]>> & string}` : K : never; | ||
| /** | ||
| * Type representing valid dot-notation paths through class data properties. | ||
| * @template T - The class type | ||
| */ | ||
| type ClassPath<T> = PathImpl<T>; | ||
| /** | ||
| * Helper type to get the value at a specific path in a class, handling optional properties. | ||
| * Only considers data properties (excludes methods). | ||
| * @template T - The class type | ||
| * @template P - The path string | ||
| * @internal | ||
| */ | ||
| type ClassPathValue<T, P> = P extends keyof DataPropertiesOnly<T> ? DataPropertiesOnly<T>[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof DataPropertiesOnly<T> ? ClassPathValue<NonNullable<DataPropertiesOnly<T>[K]>, Rest> : never : never; | ||
| /** | ||
| * Type-safe builder specifically designed for classes. | ||
@@ -383,2 +597,18 @@ * This builder automatically instantiates the target class and provides: | ||
| /** | ||
| * Optional static template defining which data properties are required. | ||
| * Subclasses can set this to specify required fields, which will be preserved | ||
| * when calling clearOptionalProperties(). | ||
| * | ||
| * @deprecated Prefer passing required fields via subclass constructor and `super(...)` | ||
| * or setting them at runtime with `setRequiredFields()`. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class PersonBuilder extends CeriosClassBuilder<Person> { | ||
| * static requiredDataProperties = ['name', 'age'] as const; | ||
| * } | ||
| * ``` | ||
| */ | ||
| static requiredDataProperties?: ReadonlyArray<string>; | ||
| /** | ||
| * The class constructor to instantiate when building. | ||
@@ -394,7 +624,20 @@ * @private | ||
| /** | ||
| * Custom validators that run during build. | ||
| * @private | ||
| */ | ||
| private _validators; | ||
| /** | ||
| * Instance-level required fields that can be populated dynamically. | ||
| * This allows adding required fields at runtime via the setRequiredFields method. | ||
| * @private | ||
| */ | ||
| private _requiredFields; | ||
| /** | ||
| * Creates a new class builder instance. | ||
| * @param classConstructor - The class constructor to use for building | ||
| * @param data - Optional initial data | ||
| * @param _validators - Optional array of validators to preserve across instances | ||
| * @param _requiredFields - Optional required fields to preserve across instances | ||
| */ | ||
| protected constructor(classConstructor: ClassConstructor<T>, data?: Partial<T>); | ||
| protected constructor(classConstructor: ClassConstructor<T>, data?: Partial<T>, _validators?: Array<(obj: Partial<T>) => boolean | string>, _requiredFields?: ReadonlyArray<ClassPath<T>> | Set<string>); | ||
| /** | ||
@@ -417,5 +660,14 @@ * Gets the class constructor for this builder. | ||
| * @returns A new builder instance with the property set | ||
| * @protected | ||
| */ | ||
| setProperty<K extends keyof DataPropertiesOnly<T>>(key: K, value: DataPropertiesOnly<T>[K]): this & CeriosClassBrand<Pick<DataPropertiesOnly<T>, K>>; | ||
| protected setProperty<K extends keyof DataPropertiesOnly<T>>(key: K, value: DataPropertiesOnly<T>[K]): ClassBuilderStep<this, T, K>; | ||
| /** | ||
| * Fallback overload for generic subclass scenarios where TypeScript cannot | ||
| * resolve `keyof DataPropertiesOnly<T>` from a literal key. | ||
| * This keeps fluent APIs ergonomic in shared generic base builders. | ||
| * @protected | ||
| */ | ||
| protected setProperty<K extends keyof T & string>(key: K, value: T[K]): ClassBuilderStep<this, T, K>; | ||
| protected setProperty<K extends keyof DataPropertiesOnly<T>>(key: K, value: DataPropertiesOnly<T>[K]): ClassBuilderStep<this, T, K>; | ||
| /** | ||
| * Sets multiple properties at once. | ||
@@ -425,5 +677,137 @@ * @template K - The property keys being set | ||
| * @returns A new builder instance with the properties set | ||
| * @protected | ||
| */ | ||
| setProperties<K extends keyof DataPropertiesOnly<T>>(props: Pick<DataPropertiesOnly<T>, K>): this & CeriosClassBrand<Pick<DataPropertiesOnly<T>, K>>; | ||
| protected setProperties<K extends keyof DataPropertiesOnly<T>>(props: Pick<DataPropertiesOnly<T>, K>): ClassBuilderStep<this, T, K>; | ||
| /** | ||
| * 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. | ||
| * Only supports data properties (excludes methods). | ||
| * | ||
| * @template P - The property path (e.g., "address.city") | ||
| * @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 | ||
| * class Address { | ||
| * street!: string; | ||
| * city!: string; | ||
| * } | ||
| * class Person { | ||
| * name!: string; | ||
| * address!: Address; | ||
| * } | ||
| * const builder = new CeriosClassBuilder(Person); | ||
| * const person = builder | ||
| * .setNestedProperty('address.city', 'New York') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| protected setNestedProperty<P extends ClassPath<T>>(path: P, value: ClassPathValue<T, P>): ClassBuilderStep<this, T, P>; | ||
| /** | ||
| * 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 = PersonBuilder.create() | ||
| * .setRequiredFields(['name', 'age']) | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('age', 30) | ||
| * .buildWithoutCompileTimeValidation(); | ||
| * ``` | ||
| */ | ||
| setRequiredFields(fields: ReadonlyArray<ClassPath<T>>): this; | ||
| /** | ||
| * Gets the combined required fields from both the static template and instance-level fields. | ||
| * If instance-level fields are set via setRequiredFields(), they are combined with static fields. | ||
| * @private | ||
| */ | ||
| private getRequiredTemplate; | ||
| /** | ||
| * Validates that all fields in the required template have been set. | ||
| * @private | ||
| */ | ||
| private validateRequiredFields; | ||
| /** | ||
| * Adds a custom validator function that will be executed during build. | ||
| * Validators can return true for valid, false for invalid, or a string error message. | ||
| * Multiple validators can be added and all will be checked. | ||
| * | ||
| * @param validator - Function that validates the partial object | ||
| * @returns The builder instance for chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = PersonBuilder.create() | ||
| * .addValidator(obj => obj.age ? obj.age >= 18 : 'Age must be 18 or older') | ||
| * .addValidator(obj => obj.email?.includes('@') || 'Invalid email format') | ||
| * .setProperty('age', 20) | ||
| * .setProperty('email', 'user@example.com') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| addValidator(validator: (obj: Partial<T>) => boolean | string): this; | ||
| /** | ||
| * Runs all custom validators and returns any error messages. | ||
| * @private | ||
| */ | ||
| private runValidators; | ||
| /** | ||
| * Removes an optional property from the builder. | ||
| * Only works with optional data properties (those that can be undefined). | ||
| * Methods are automatically excluded. | ||
| * | ||
| * @template K - The optional property key to remove | ||
| * @param key - The property key to remove | ||
| * @returns A new builder instance without the specified property | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Person { | ||
| * name!: string; | ||
| * email?: string; | ||
| * } | ||
| * const builder = PersonBuilder.create() | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('email', 'john@example.com') | ||
| * .removeOptionalProperty('email'); | ||
| * // Email is now removed from the builder | ||
| * ``` | ||
| */ | ||
| removeOptionalProperty<K extends OptionalKeys<DataPropertiesOnly<T>>>(key: K): this; | ||
| /** | ||
| * Clears all optional properties from the builder, keeping only required data properties. | ||
| * Uses the combined required-field template from static defaults and instance-level fields. | ||
| * If no required fields are configured, all properties are cleared. | ||
| * | ||
| * @returns A new builder instance with only required properties | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Person { | ||
| * name!: string; // required | ||
| * age!: number; // required | ||
| * email?: string; // optional | ||
| * phone?: string; // optional | ||
| * } | ||
| * class PersonBuilder extends CeriosClassBuilder<Person> { | ||
| * static requiredDataProperties = ['name', 'age'] as const; | ||
| * } | ||
| * const builder = PersonBuilder.create() | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('age', 30) | ||
| * .setProperty('email', 'john@example.com') | ||
| * .setProperty('phone', '555-1234') | ||
| * .clearOptionalProperties(); | ||
| * // Only name and age are preserved, email and phone are cleared | ||
| * ``` | ||
| */ | ||
| clearOptionalProperties(): this; | ||
| /** | ||
| * Adds a value to an array property. | ||
@@ -437,20 +821,33 @@ * @template K - The array property key | ||
| addToArrayProperty<K extends { | ||
| [P in keyof DataPropertiesOnly<T>]: NonNullable<DataPropertiesOnly<T>[P]> extends Array<any> ? P : never; | ||
| }[keyof DataPropertiesOnly<T>], V extends DataPropertiesOnly<T>[K] extends Array<infer U> ? U : DataPropertiesOnly<T>[K] extends Array<infer U> | undefined ? U : never>(key: K, value: V): this & CeriosClassBrand<Pick<DataPropertiesOnly<T>, K>>; | ||
| [P in keyof DataPropertiesOnly<T>]: NonNullable<DataPropertiesOnly<T>[P]> extends Array<unknown> ? P : never; | ||
| }[keyof DataPropertiesOnly<T>], V extends DataPropertiesOnly<T>[K] extends Array<infer U> ? U : DataPropertiesOnly<T>[K] extends Array<infer U> | undefined ? U : never>(key: K, value: V): ClassBuilderStep<this, T, K>; | ||
| /** | ||
| * Builds the final class instance with compile-time and runtime validation. | ||
| * - Compile-time: TypeScript enforces all data properties are set | ||
| * - Runtime: Validates nested class instances are properly instantiated | ||
| * - Runtime: Validates required fields and custom validators | ||
| * | ||
| * @returns The fully built and validated class instance | ||
| * @throws {Error} If nested validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| build(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): T; | ||
| build(this: this & InternalClassBrand<DataPropertiesOnly<T>>): T; | ||
| /** | ||
| * Builds the class instance with runtime validation only (no compile-time). | ||
| * Use this when building from external/dynamic data. | ||
| * Builds the final class instance with only compile-time validation, skipping runtime checks. | ||
| * Use this when you want TypeScript safety but need to skip runtime validation for performance. | ||
| * | ||
| * @returns The built class instance | ||
| * @throws {Error} If validation fails | ||
| * - Compile-time: TypeScript enforces all data properties are set | ||
| * - Runtime: No validation | ||
| * | ||
| * @returns The fully built class instance | ||
| */ | ||
| buildWithoutRuntimeValidation(this: this & InternalClassBrand<DataPropertiesOnly<T>>): T; | ||
| /** | ||
| * Builds the final class instance with only runtime validation, skipping compile-time checks. | ||
| * Use this when building from external data where compile-time checks aren't possible. | ||
| * | ||
| * - Compile-time: No TypeScript enforcement | ||
| * - Runtime: Validates required fields and custom validators | ||
| * | ||
| * @returns The fully built class instance | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildWithoutCompileTimeValidation(): T; | ||
@@ -474,5 +871,5 @@ /** | ||
| * @returns The frozen class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildFrozen(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): Readonly<T>; | ||
| buildFrozen(this: this & InternalClassBrand<DataPropertiesOnly<T>>): Readonly<T>; | ||
| /** | ||
@@ -482,5 +879,5 @@ * Builds and deeply freezes the class instance. | ||
| * @returns The deeply frozen class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildDeepFrozen(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): DeepReadonly<T>; | ||
| buildDeepFrozen(this: this & InternalClassBrand<DataPropertiesOnly<T>>): DeepReadonly<T>; | ||
| /** | ||
@@ -490,5 +887,5 @@ * Builds and seals the class instance (shallow freeze). | ||
| * @returns The sealed class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildSealed(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): T; | ||
| buildSealed(this: this & InternalClassBrand<DataPropertiesOnly<T>>): T; | ||
| /** | ||
@@ -498,5 +895,5 @@ * Builds and deeply seals the class instance. | ||
| * @returns The deeply sealed class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildDeepSealed(this: this & CeriosClassBrand<DataPropertiesOnly<T>>): T; | ||
| buildDeepSealed(this: this & InternalClassBrand<DataPropertiesOnly<T>>): T; | ||
| /** | ||
@@ -517,4 +914,39 @@ * Deep freeze helper. | ||
| private deepClone; | ||
| /** | ||
| * Creates a new builder instance from an existing class instance. | ||
| * This is useful for creating builders from existing instances to modify them. | ||
| * | ||
| * @param classConstructor - The class constructor to use | ||
| * @param instance - The existing class instance to create a builder from | ||
| * @returns A new builder instance initialized with the instance's data | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const existingPerson = new Person({ name: 'John', age: 30 }); | ||
| * const builder = PersonBuilder.from(Person, existingPerson); | ||
| * const updated = builder.setProperty('age', 31).build(); | ||
| * ``` | ||
| */ | ||
| static from<T extends object, B extends new (classConstructor: ClassConstructor<T>, data: Partial<T>) => unknown>(this: B, classConstructor: ClassConstructor<T>, instance: T): InstanceType<B>; | ||
| /** | ||
| * Creates a clone of the current builder instance. | ||
| * The clone has the same state but is independent - changes to one won't affect the other. | ||
| * | ||
| * @returns A new builder instance with the same state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder1 = PersonBuilder.create().setProperty('name', 'John'); | ||
| * const builder2 = builder1.clone(); | ||
| * // builder2 is independent of builder1 | ||
| * ``` | ||
| */ | ||
| clone(): this; | ||
| /** | ||
| * Static deep clone helper for the from() method. | ||
| * @private | ||
| */ | ||
| private static deepCloneStatic; | ||
| } | ||
| export { type BuilderType, type CeriosBrand, CeriosBuilder, CeriosClassBuilder, type ClassConstructor, type DeepReadonly, type RequiredFieldsTemplate }; | ||
| export { type BuilderComposer, type BuilderComposerFromFactory, type BuilderPreset, type BuilderStep, type BuilderType, type CeriosBrand, CeriosBuilder, CeriosClassBuilder, type ClassBuilderComposer, type ClassBuilderComposerFromFactory, type ClassBuilderPreset, type ClassBuilderStep, type ClassConstructor, type ClassPath, type DeepReadonly, type OptionalKeys, type RequiredFieldsTemplate }; |
+584
-39
@@ -47,3 +47,3 @@ "use strict"; | ||
| } | ||
| var CeriosBuilder = class { | ||
| var CeriosBuilder = class _CeriosBuilder { | ||
| /** | ||
@@ -54,5 +54,6 @@ * Creates a new builder instance. Intended to be called by subclasses. | ||
| * @param _requiredFields - Optional array of required field paths to preserve across instances | ||
| * @param _validators - Optional array of validators to preserve across instances | ||
| * @protected | ||
| */ | ||
| constructor(_actual, _requiredFields) { | ||
| constructor(_actual, _requiredFields, _validators) { | ||
| this._actual = _actual; | ||
@@ -65,5 +66,13 @@ /** | ||
| this._requiredFields = /* @__PURE__ */ new Set(); | ||
| /** | ||
| * Custom validators that run during build. | ||
| * @private | ||
| */ | ||
| this._validators = []; | ||
| if (_requiredFields) { | ||
| this._requiredFields = /* @__PURE__ */ new Set([..._requiredFields]); | ||
| } | ||
| if (_validators) { | ||
| this._validators = [..._validators]; | ||
| } | ||
| } | ||
@@ -91,2 +100,27 @@ /** | ||
| /** | ||
| * Adds a custom validator function that will be executed during build. | ||
| * Validators can return true for valid, false for invalid, or a string error message. | ||
| * Multiple validators can be added and all will be checked. | ||
| * | ||
| * @param validator - Function that validates the partial object | ||
| * @returns The builder instance for chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder({}) | ||
| * .addValidator(obj => obj.age ? obj.age >= 18 : 'Age must be 18 or older') | ||
| * .addValidator(obj => obj.email?.includes('@') || 'Invalid email format') | ||
| * .setAge(20) | ||
| * .setEmail('user@example.com') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| addValidator(validator) { | ||
| const BuilderClass = this.constructor; | ||
| return new BuilderClass(this._actual, Array.from(this._requiredFields), [ | ||
| ...this._validators, | ||
| validator | ||
| ]); | ||
| } | ||
| /** | ||
| * Gets the combined required fields from both the static template and instance-level fields. | ||
@@ -96,4 +130,5 @@ * @private | ||
| getRequiredTemplate() { | ||
| var _a; | ||
| const ctor = this.constructor; | ||
| const staticFields = ctor.requiredTemplate || []; | ||
| const staticFields = (_a = ctor.requiredTemplate) != null ? _a : []; | ||
| const instanceFields = Array.from(this._requiredFields); | ||
@@ -114,3 +149,3 @@ return [.../* @__PURE__ */ new Set([...staticFields, ...instanceFields])]; | ||
| const key = keys[i]; | ||
| if (current === null || current === void 0 || !(key in current)) { | ||
| if (current === null || current === void 0 || typeof current !== "object" || !(key in current)) { | ||
| missing.push(path); | ||
@@ -130,2 +165,86 @@ break; | ||
| /** | ||
| * Runs all custom validators and returns any error messages. | ||
| * @private | ||
| */ | ||
| runValidators() { | ||
| const errors = []; | ||
| for (const validator of this._validators) { | ||
| const result = validator(this._actual); | ||
| if (result === false) { | ||
| errors.push("Validation failed"); | ||
| } else if (typeof result === "string") { | ||
| errors.push(result); | ||
| } | ||
| } | ||
| return errors; | ||
| } | ||
| /** | ||
| * Removes an optional property from the builder. | ||
| * Only works with optional properties (those that can be undefined). | ||
| * | ||
| * @template K - The optional property key to remove | ||
| * @param key - The property key to remove | ||
| * @returns A new builder instance without the specified property | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder() | ||
| * .setName('John') | ||
| * .setEmail('john@example.com') | ||
| * .removeOptionalProperty('email'); | ||
| * // Email is now removed from the builder | ||
| * ``` | ||
| */ | ||
| removeOptionalProperty(key) { | ||
| const BuilderClass = this.constructor; | ||
| const newData = { ...this._actual }; | ||
| delete newData[key]; | ||
| return new BuilderClass( | ||
| newData, | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
| } | ||
| /** | ||
| * Clears all optional properties from the builder, keeping only required ones. | ||
| * Properties in the required template and those marked as required are preserved. | ||
| * | ||
| * @returns A new builder instance with only required properties | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder() | ||
| * .setName('John') // required | ||
| * .setAge(30) // required | ||
| * .setEmail('john@example.com') // optional | ||
| * .setPhone('555-1234') // optional | ||
| * .clearOptionalProperties(); | ||
| * // Only name and age remain | ||
| * ``` | ||
| */ | ||
| clearOptionalProperties() { | ||
| const BuilderClass = this.constructor; | ||
| const requiredPaths = this.getRequiredTemplate(); | ||
| const newData = {}; | ||
| for (const path of requiredPaths) { | ||
| const keys = path.split("."); | ||
| if (keys.length === 1) { | ||
| const key = keys[0]; | ||
| if (key in this._actual) { | ||
| newData[key] = this._actual[key]; | ||
| } | ||
| } else { | ||
| const rootKey = keys[0]; | ||
| if (rootKey in this._actual && !(rootKey in newData)) { | ||
| newData[rootKey] = this._actual[rootKey]; | ||
| } | ||
| } | ||
| } | ||
| return new BuilderClass( | ||
| newData, | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
| } | ||
| /** | ||
| * Sets a property value and returns a new builder instance with updated type state. | ||
@@ -147,3 +266,4 @@ * This method is intended to be wrapped by concrete builder methods in subclasses. | ||
| }, | ||
| Array.from(this._requiredFields) | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
@@ -164,3 +284,4 @@ } | ||
| }, | ||
| Array.from(this._requiredFields) | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
@@ -190,6 +311,7 @@ } | ||
| const key = keys[i]; | ||
| if (!(key in current) || typeof current[key] !== "object" || current[key] === null) { | ||
| const existing = current[key]; | ||
| if (!(key in current) || typeof existing !== "object" || existing === null) { | ||
| current[key] = {}; | ||
| } else { | ||
| current[key] = this.deepClone(current[key]); | ||
| current[key] = this.deepClone(existing); | ||
| } | ||
@@ -201,3 +323,4 @@ current = current[key]; | ||
| newActual, | ||
| Array.from(this._requiredFields) | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
@@ -217,5 +340,4 @@ } | ||
| const cloned = {}; | ||
| const hasOwn = Object.prototype.hasOwnProperty; | ||
| for (const key in obj) { | ||
| if (hasOwn.call(obj, key)) { | ||
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
| cloned[key] = this.deepClone(obj[key]); | ||
@@ -246,3 +368,4 @@ } | ||
| }, | ||
| Array.from(this._requiredFields) | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
@@ -265,2 +388,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return this._actual; | ||
@@ -295,2 +422,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return this._actual; | ||
@@ -320,13 +451,61 @@ } | ||
| /** | ||
| * @deprecated Use build() instead. buildSafe() is now an alias for build(). | ||
| * Builds the final object with runtime validation using the requiredTemplate. | ||
| * This method validates that all fields in the requiredTemplate array are present. | ||
| * Creates a new builder instance from an existing object. | ||
| * This is useful for creating builders from existing instances to modify them. | ||
| * | ||
| * @returns The fully built object of type T | ||
| * @throws {Error} If any required field is missing at runtime | ||
| * @param instance - The existing object to create a builder from | ||
| * @returns A new builder instance initialized with the object's data | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const existingPerson = { name: 'John', age: 30 }; | ||
| * const builder = MyBuilder.from(existingPerson); | ||
| * const updated = builder.setAge(31).build(); | ||
| * ``` | ||
| */ | ||
| buildSafe() { | ||
| return this.buildWithoutCompileTimeValidation(); | ||
| static from(instance) { | ||
| const clonedData = _CeriosBuilder.deepCloneStatic(instance); | ||
| return new this(clonedData); | ||
| } | ||
| /** | ||
| * Creates a clone of the current builder instance. | ||
| * The clone has the same state but is independent - changes to one won't affect the other. | ||
| * | ||
| * @returns A new builder instance with the same state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder1 = new MyBuilder({}).setName('John'); | ||
| * const builder2 = builder1.clone(); | ||
| * // builder2 is independent of builder1 | ||
| * ``` | ||
| */ | ||
| clone() { | ||
| const BuilderClass = this.constructor; | ||
| const clonedData = this.deepClone(this._actual); | ||
| return new BuilderClass( | ||
| clonedData, | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
| } | ||
| /** | ||
| * Static deep clone helper for the from() method. | ||
| * @private | ||
| */ | ||
| static deepCloneStatic(obj) { | ||
| if (obj === null || typeof obj !== "object") { | ||
| return obj; | ||
| } | ||
| if (Array.isArray(obj)) { | ||
| return obj.map((item) => this.deepCloneStatic(item)); | ||
| } | ||
| const cloned = {}; | ||
| for (const key in obj) { | ||
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
| cloned[key] = this.deepCloneStatic(obj[key]); | ||
| } | ||
| } | ||
| return cloned; | ||
| } | ||
| /** | ||
| * Builds and freezes the final object with both compile-time and runtime validation. | ||
@@ -347,2 +526,6 @@ * The returned object is shallowly frozen - top-level properties cannot be modified, | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return Object.freeze(this._actual); | ||
@@ -365,2 +548,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return deepFreeze(this._actual); | ||
@@ -384,2 +571,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return Object.seal(this._actual); | ||
@@ -403,2 +594,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return deepSeal(this._actual); | ||
@@ -409,3 +604,3 @@ } | ||
| // src/cerios-class-builder.ts | ||
| var CeriosClassBuilder = class { | ||
| var CeriosClassBuilder = class _CeriosClassBuilder { | ||
| /** | ||
@@ -415,6 +610,25 @@ * Creates a new class builder instance. | ||
| * @param data - Optional initial data | ||
| * @param _validators - Optional array of validators to preserve across instances | ||
| * @param _requiredFields - Optional required fields to preserve across instances | ||
| */ | ||
| constructor(classConstructor, data = {}) { | ||
| constructor(classConstructor, data = {}, _validators, _requiredFields) { | ||
| /** | ||
| * Custom validators that run during build. | ||
| * @private | ||
| */ | ||
| this._validators = []; | ||
| /** | ||
| * 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(); | ||
| this._classConstructor = classConstructor; | ||
| this._actual = data; | ||
| if (_validators) { | ||
| this._validators = [..._validators]; | ||
| } | ||
| if (_requiredFields) { | ||
| this._requiredFields = _requiredFields instanceof Set ? new Set(_requiredFields) : /* @__PURE__ */ new Set([..._requiredFields]); | ||
| } | ||
| } | ||
@@ -435,11 +649,4 @@ /** | ||
| const BuilderClass = this.constructor; | ||
| return new BuilderClass(this._classConstructor, data); | ||
| return new BuilderClass(this._classConstructor, data, this._validators, this._requiredFields); | ||
| } | ||
| /** | ||
| * Sets a property value and returns a new builder instance with updated type state. | ||
| * @template K - The property key being set | ||
| * @param key - The property key to set | ||
| * @param value - The value to assign to the property | ||
| * @returns A new builder instance with the property set | ||
| */ | ||
| setProperty(key, value) { | ||
@@ -457,2 +664,3 @@ const newBuilder = this.createBuilder({ | ||
| * @returns A new builder instance with the properties set | ||
| * @protected | ||
| */ | ||
@@ -467,2 +675,216 @@ setProperties(props) { | ||
| /** | ||
| * 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. | ||
| * Only supports data properties (excludes methods). | ||
| * | ||
| * @template P - The property path (e.g., "address.city") | ||
| * @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 | ||
| * class Address { | ||
| * street!: string; | ||
| * city!: string; | ||
| * } | ||
| * class Person { | ||
| * name!: string; | ||
| * address!: Address; | ||
| * } | ||
| * const builder = new CeriosClassBuilder(Person); | ||
| * const person = builder | ||
| * .setNestedProperty('address.city', 'New York') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| setNestedProperty(path, value) { | ||
| 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]; | ||
| const existing = current[key]; | ||
| if (!(key in current) || typeof existing !== "object" || existing === null) { | ||
| current[key] = {}; | ||
| } else { | ||
| current[key] = this.deepClone(existing); | ||
| } | ||
| current = current[key]; | ||
| } | ||
| current[keys[keys.length - 1]] = value; | ||
| const newBuilder = this.createBuilder(newActual); | ||
| return newBuilder; | ||
| } | ||
| /** | ||
| * 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 = PersonBuilder.create() | ||
| * .setRequiredFields(['name', 'age']) | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('age', 30) | ||
| * .buildWithoutCompileTimeValidation(); | ||
| * ``` | ||
| */ | ||
| setRequiredFields(fields) { | ||
| const BuilderClass = this.constructor; | ||
| return new BuilderClass(this._classConstructor, this._actual, this._validators, /* @__PURE__ */ new Set([...fields])); | ||
| } | ||
| /** | ||
| * Gets the combined required fields from both the static template and instance-level fields. | ||
| * If instance-level fields are set via setRequiredFields(), they are combined with static fields. | ||
| * @private | ||
| */ | ||
| getRequiredTemplate() { | ||
| var _a; | ||
| const ctor = this.constructor; | ||
| const staticFields = (_a = ctor.requiredDataProperties) != null ? _a : []; | ||
| const instanceFields = Array.from(this._requiredFields); | ||
| if (instanceFields.length > 0) { | ||
| return [.../* @__PURE__ */ new Set([...staticFields, ...instanceFields])]; | ||
| } | ||
| return staticFields; | ||
| } | ||
| /** | ||
| * 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 || typeof current !== "object" || !(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; | ||
| } | ||
| /** | ||
| * Adds a custom validator function that will be executed during build. | ||
| * Validators can return true for valid, false for invalid, or a string error message. | ||
| * Multiple validators can be added and all will be checked. | ||
| * | ||
| * @param validator - Function that validates the partial object | ||
| * @returns The builder instance for chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = PersonBuilder.create() | ||
| * .addValidator(obj => obj.age ? obj.age >= 18 : 'Age must be 18 or older') | ||
| * .addValidator(obj => obj.email?.includes('@') || 'Invalid email format') | ||
| * .setProperty('age', 20) | ||
| * .setProperty('email', 'user@example.com') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| addValidator(validator) { | ||
| const BuilderClass = this.constructor; | ||
| return new BuilderClass( | ||
| this._classConstructor, | ||
| this._actual, | ||
| [...this._validators, validator], | ||
| this._requiredFields | ||
| ); | ||
| } | ||
| /** | ||
| * Runs all custom validators and returns any error messages. | ||
| * @private | ||
| */ | ||
| runValidators() { | ||
| const errors = []; | ||
| for (const validator of this._validators) { | ||
| const result = validator(this._actual); | ||
| if (result === false) { | ||
| errors.push("Validation failed"); | ||
| } else if (typeof result === "string") { | ||
| errors.push(result); | ||
| } | ||
| } | ||
| return errors; | ||
| } | ||
| /** | ||
| * Removes an optional property from the builder. | ||
| * Only works with optional data properties (those that can be undefined). | ||
| * Methods are automatically excluded. | ||
| * | ||
| * @template K - The optional property key to remove | ||
| * @param key - The property key to remove | ||
| * @returns A new builder instance without the specified property | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Person { | ||
| * name!: string; | ||
| * email?: string; | ||
| * } | ||
| * const builder = PersonBuilder.create() | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('email', 'john@example.com') | ||
| * .removeOptionalProperty('email'); | ||
| * // Email is now removed from the builder | ||
| * ``` | ||
| */ | ||
| removeOptionalProperty(key) { | ||
| const newData = { ...this._actual }; | ||
| delete newData[key]; | ||
| return this.createBuilder(newData); | ||
| } | ||
| /** | ||
| * Clears all optional properties from the builder, keeping only required data properties. | ||
| * Uses the combined required-field template from static defaults and instance-level fields. | ||
| * If no required fields are configured, all properties are cleared. | ||
| * | ||
| * @returns A new builder instance with only required properties | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Person { | ||
| * name!: string; // required | ||
| * age!: number; // required | ||
| * email?: string; // optional | ||
| * phone?: string; // optional | ||
| * } | ||
| * class PersonBuilder extends CeriosClassBuilder<Person> { | ||
| * static requiredDataProperties = ['name', 'age'] as const; | ||
| * } | ||
| * const builder = PersonBuilder.create() | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('age', 30) | ||
| * .setProperty('email', 'john@example.com') | ||
| * .setProperty('phone', '555-1234') | ||
| * .clearOptionalProperties(); | ||
| * // Only name and age are preserved, email and phone are cleared | ||
| * ``` | ||
| */ | ||
| clearOptionalProperties() { | ||
| const requiredProps = this.getRequiredTemplate(); | ||
| const newData = {}; | ||
| for (const prop of requiredProps) { | ||
| const key = prop; | ||
| if (key in this._actual) { | ||
| newData[key] = this._actual[key]; | ||
| } | ||
| } | ||
| return this.createBuilder(newData); | ||
| } | ||
| /** | ||
| * Adds a value to an array property. | ||
@@ -487,8 +909,16 @@ * @template K - The array property key | ||
| * - Compile-time: TypeScript enforces all data properties are set | ||
| * - Runtime: Validates nested class instances are properly instantiated | ||
| * - Runtime: Validates required fields and custom validators | ||
| * | ||
| * @returns The fully built and validated class instance | ||
| * @throws {Error} If nested validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| build() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -504,9 +934,39 @@ const instance = new ctor(this._actual); | ||
| /** | ||
| * Builds the class instance with runtime validation only (no compile-time). | ||
| * Use this when building from external/dynamic data. | ||
| * Builds the final class instance with only compile-time validation, skipping runtime checks. | ||
| * Use this when you want TypeScript safety but need to skip runtime validation for performance. | ||
| * | ||
| * @returns The built class instance | ||
| * @throws {Error} If validation fails | ||
| * - Compile-time: TypeScript enforces all data properties are set | ||
| * - Runtime: No validation | ||
| * | ||
| * @returns The fully built class instance | ||
| */ | ||
| buildWithoutRuntimeValidation() { | ||
| const ctor = this.getClassConstructor(); | ||
| const instance = new ctor(this._actual); | ||
| const dataKeys = Object.keys(this._actual); | ||
| const needsAssign = dataKeys.some((key) => instance[key] === void 0 && this._actual[key] !== void 0); | ||
| if (needsAssign) { | ||
| Object.assign(instance, this._actual); | ||
| } | ||
| return instance; | ||
| } | ||
| /** | ||
| * Builds the final class instance with only runtime validation, skipping compile-time checks. | ||
| * Use this when building from external data where compile-time checks aren't possible. | ||
| * | ||
| * - Compile-time: No TypeScript enforcement | ||
| * - Runtime: Validates required fields and custom validators | ||
| * | ||
| * @returns The fully built class instance | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildWithoutCompileTimeValidation() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -549,5 +1009,13 @@ const instance = new ctor(this._actual); | ||
| * @returns The frozen class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildFrozen() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -566,5 +1034,13 @@ const instance = new ctor(this._actual); | ||
| * @returns The deeply frozen class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildDeepFrozen() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -583,5 +1059,13 @@ const instance = new ctor(this._actual); | ||
| * @returns The sealed class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildSealed() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -600,5 +1084,13 @@ const instance = new ctor(this._actual); | ||
| * @returns The deeply sealed class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildDeepSealed() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -662,2 +1154,55 @@ const instance = new ctor(this._actual); | ||
| } | ||
| /** | ||
| * Creates a new builder instance from an existing class instance. | ||
| * This is useful for creating builders from existing instances to modify them. | ||
| * | ||
| * @param classConstructor - The class constructor to use | ||
| * @param instance - The existing class instance to create a builder from | ||
| * @returns A new builder instance initialized with the instance's data | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const existingPerson = new Person({ name: 'John', age: 30 }); | ||
| * const builder = PersonBuilder.from(Person, existingPerson); | ||
| * const updated = builder.setProperty('age', 31).build(); | ||
| * ``` | ||
| */ | ||
| static from(classConstructor, instance) { | ||
| const clonedData = _CeriosClassBuilder.deepCloneStatic(instance); | ||
| return new this(classConstructor, clonedData); | ||
| } | ||
| /** | ||
| * Creates a clone of the current builder instance. | ||
| * The clone has the same state but is independent - changes to one won't affect the other. | ||
| * | ||
| * @returns A new builder instance with the same state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder1 = PersonBuilder.create().setProperty('name', 'John'); | ||
| * const builder2 = builder1.clone(); | ||
| * // builder2 is independent of builder1 | ||
| * ``` | ||
| */ | ||
| clone() { | ||
| const clonedData = this.deepClone(this._actual); | ||
| return this.createBuilder(clonedData); | ||
| } | ||
| /** | ||
| * Static deep clone helper for the from() method. | ||
| * @private | ||
| */ | ||
| static deepCloneStatic(obj) { | ||
| if (obj === null || typeof obj !== "object") { | ||
| return obj; | ||
| } | ||
| if (Array.isArray(obj)) { | ||
| return obj.map((item) => this.deepCloneStatic(item)); | ||
| } | ||
| const cloned = {}; | ||
| for (const key of Object.keys(obj)) { | ||
| cloned[key] = this.deepCloneStatic(obj[key]); | ||
| } | ||
| return cloned; | ||
| } | ||
| }; | ||
@@ -664,0 +1209,0 @@ // Annotate the CommonJS export names for ESM import in node: |
+584
-39
@@ -20,3 +20,3 @@ // src/cerios-builder.ts | ||
| } | ||
| var CeriosBuilder = class { | ||
| var CeriosBuilder = class _CeriosBuilder { | ||
| /** | ||
@@ -27,5 +27,6 @@ * Creates a new builder instance. Intended to be called by subclasses. | ||
| * @param _requiredFields - Optional array of required field paths to preserve across instances | ||
| * @param _validators - Optional array of validators to preserve across instances | ||
| * @protected | ||
| */ | ||
| constructor(_actual, _requiredFields) { | ||
| constructor(_actual, _requiredFields, _validators) { | ||
| this._actual = _actual; | ||
@@ -38,5 +39,13 @@ /** | ||
| this._requiredFields = /* @__PURE__ */ new Set(); | ||
| /** | ||
| * Custom validators that run during build. | ||
| * @private | ||
| */ | ||
| this._validators = []; | ||
| if (_requiredFields) { | ||
| this._requiredFields = /* @__PURE__ */ new Set([..._requiredFields]); | ||
| } | ||
| if (_validators) { | ||
| this._validators = [..._validators]; | ||
| } | ||
| } | ||
@@ -64,2 +73,27 @@ /** | ||
| /** | ||
| * Adds a custom validator function that will be executed during build. | ||
| * Validators can return true for valid, false for invalid, or a string error message. | ||
| * Multiple validators can be added and all will be checked. | ||
| * | ||
| * @param validator - Function that validates the partial object | ||
| * @returns The builder instance for chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder({}) | ||
| * .addValidator(obj => obj.age ? obj.age >= 18 : 'Age must be 18 or older') | ||
| * .addValidator(obj => obj.email?.includes('@') || 'Invalid email format') | ||
| * .setAge(20) | ||
| * .setEmail('user@example.com') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| addValidator(validator) { | ||
| const BuilderClass = this.constructor; | ||
| return new BuilderClass(this._actual, Array.from(this._requiredFields), [ | ||
| ...this._validators, | ||
| validator | ||
| ]); | ||
| } | ||
| /** | ||
| * Gets the combined required fields from both the static template and instance-level fields. | ||
@@ -69,4 +103,5 @@ * @private | ||
| getRequiredTemplate() { | ||
| var _a; | ||
| const ctor = this.constructor; | ||
| const staticFields = ctor.requiredTemplate || []; | ||
| const staticFields = (_a = ctor.requiredTemplate) != null ? _a : []; | ||
| const instanceFields = Array.from(this._requiredFields); | ||
@@ -87,3 +122,3 @@ return [.../* @__PURE__ */ new Set([...staticFields, ...instanceFields])]; | ||
| const key = keys[i]; | ||
| if (current === null || current === void 0 || !(key in current)) { | ||
| if (current === null || current === void 0 || typeof current !== "object" || !(key in current)) { | ||
| missing.push(path); | ||
@@ -103,2 +138,86 @@ break; | ||
| /** | ||
| * Runs all custom validators and returns any error messages. | ||
| * @private | ||
| */ | ||
| runValidators() { | ||
| const errors = []; | ||
| for (const validator of this._validators) { | ||
| const result = validator(this._actual); | ||
| if (result === false) { | ||
| errors.push("Validation failed"); | ||
| } else if (typeof result === "string") { | ||
| errors.push(result); | ||
| } | ||
| } | ||
| return errors; | ||
| } | ||
| /** | ||
| * Removes an optional property from the builder. | ||
| * Only works with optional properties (those that can be undefined). | ||
| * | ||
| * @template K - The optional property key to remove | ||
| * @param key - The property key to remove | ||
| * @returns A new builder instance without the specified property | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder() | ||
| * .setName('John') | ||
| * .setEmail('john@example.com') | ||
| * .removeOptionalProperty('email'); | ||
| * // Email is now removed from the builder | ||
| * ``` | ||
| */ | ||
| removeOptionalProperty(key) { | ||
| const BuilderClass = this.constructor; | ||
| const newData = { ...this._actual }; | ||
| delete newData[key]; | ||
| return new BuilderClass( | ||
| newData, | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
| } | ||
| /** | ||
| * Clears all optional properties from the builder, keeping only required ones. | ||
| * Properties in the required template and those marked as required are preserved. | ||
| * | ||
| * @returns A new builder instance with only required properties | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = new MyBuilder() | ||
| * .setName('John') // required | ||
| * .setAge(30) // required | ||
| * .setEmail('john@example.com') // optional | ||
| * .setPhone('555-1234') // optional | ||
| * .clearOptionalProperties(); | ||
| * // Only name and age remain | ||
| * ``` | ||
| */ | ||
| clearOptionalProperties() { | ||
| const BuilderClass = this.constructor; | ||
| const requiredPaths = this.getRequiredTemplate(); | ||
| const newData = {}; | ||
| for (const path of requiredPaths) { | ||
| const keys = path.split("."); | ||
| if (keys.length === 1) { | ||
| const key = keys[0]; | ||
| if (key in this._actual) { | ||
| newData[key] = this._actual[key]; | ||
| } | ||
| } else { | ||
| const rootKey = keys[0]; | ||
| if (rootKey in this._actual && !(rootKey in newData)) { | ||
| newData[rootKey] = this._actual[rootKey]; | ||
| } | ||
| } | ||
| } | ||
| return new BuilderClass( | ||
| newData, | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
| } | ||
| /** | ||
| * Sets a property value and returns a new builder instance with updated type state. | ||
@@ -120,3 +239,4 @@ * This method is intended to be wrapped by concrete builder methods in subclasses. | ||
| }, | ||
| Array.from(this._requiredFields) | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
@@ -137,3 +257,4 @@ } | ||
| }, | ||
| Array.from(this._requiredFields) | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
@@ -163,6 +284,7 @@ } | ||
| const key = keys[i]; | ||
| if (!(key in current) || typeof current[key] !== "object" || current[key] === null) { | ||
| const existing = current[key]; | ||
| if (!(key in current) || typeof existing !== "object" || existing === null) { | ||
| current[key] = {}; | ||
| } else { | ||
| current[key] = this.deepClone(current[key]); | ||
| current[key] = this.deepClone(existing); | ||
| } | ||
@@ -174,3 +296,4 @@ current = current[key]; | ||
| newActual, | ||
| Array.from(this._requiredFields) | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
@@ -190,5 +313,4 @@ } | ||
| const cloned = {}; | ||
| const hasOwn = Object.prototype.hasOwnProperty; | ||
| for (const key in obj) { | ||
| if (hasOwn.call(obj, key)) { | ||
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
| cloned[key] = this.deepClone(obj[key]); | ||
@@ -219,3 +341,4 @@ } | ||
| }, | ||
| Array.from(this._requiredFields) | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
@@ -238,2 +361,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return this._actual; | ||
@@ -268,2 +395,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return this._actual; | ||
@@ -293,13 +424,61 @@ } | ||
| /** | ||
| * @deprecated Use build() instead. buildSafe() is now an alias for build(). | ||
| * Builds the final object with runtime validation using the requiredTemplate. | ||
| * This method validates that all fields in the requiredTemplate array are present. | ||
| * Creates a new builder instance from an existing object. | ||
| * This is useful for creating builders from existing instances to modify them. | ||
| * | ||
| * @returns The fully built object of type T | ||
| * @throws {Error} If any required field is missing at runtime | ||
| * @param instance - The existing object to create a builder from | ||
| * @returns A new builder instance initialized with the object's data | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const existingPerson = { name: 'John', age: 30 }; | ||
| * const builder = MyBuilder.from(existingPerson); | ||
| * const updated = builder.setAge(31).build(); | ||
| * ``` | ||
| */ | ||
| buildSafe() { | ||
| return this.buildWithoutCompileTimeValidation(); | ||
| static from(instance) { | ||
| const clonedData = _CeriosBuilder.deepCloneStatic(instance); | ||
| return new this(clonedData); | ||
| } | ||
| /** | ||
| * Creates a clone of the current builder instance. | ||
| * The clone has the same state but is independent - changes to one won't affect the other. | ||
| * | ||
| * @returns A new builder instance with the same state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder1 = new MyBuilder({}).setName('John'); | ||
| * const builder2 = builder1.clone(); | ||
| * // builder2 is independent of builder1 | ||
| * ``` | ||
| */ | ||
| clone() { | ||
| const BuilderClass = this.constructor; | ||
| const clonedData = this.deepClone(this._actual); | ||
| return new BuilderClass( | ||
| clonedData, | ||
| Array.from(this._requiredFields), | ||
| this._validators | ||
| ); | ||
| } | ||
| /** | ||
| * Static deep clone helper for the from() method. | ||
| * @private | ||
| */ | ||
| static deepCloneStatic(obj) { | ||
| if (obj === null || typeof obj !== "object") { | ||
| return obj; | ||
| } | ||
| if (Array.isArray(obj)) { | ||
| return obj.map((item) => this.deepCloneStatic(item)); | ||
| } | ||
| const cloned = {}; | ||
| for (const key in obj) { | ||
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
| cloned[key] = this.deepCloneStatic(obj[key]); | ||
| } | ||
| } | ||
| return cloned; | ||
| } | ||
| /** | ||
| * Builds and freezes the final object with both compile-time and runtime validation. | ||
@@ -320,2 +499,6 @@ * The returned object is shallowly frozen - top-level properties cannot be modified, | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return Object.freeze(this._actual); | ||
@@ -338,2 +521,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return deepFreeze(this._actual); | ||
@@ -357,2 +544,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return Object.seal(this._actual); | ||
@@ -376,2 +567,6 @@ } | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| return deepSeal(this._actual); | ||
@@ -382,3 +577,3 @@ } | ||
| // src/cerios-class-builder.ts | ||
| var CeriosClassBuilder = class { | ||
| var CeriosClassBuilder = class _CeriosClassBuilder { | ||
| /** | ||
@@ -388,6 +583,25 @@ * Creates a new class builder instance. | ||
| * @param data - Optional initial data | ||
| * @param _validators - Optional array of validators to preserve across instances | ||
| * @param _requiredFields - Optional required fields to preserve across instances | ||
| */ | ||
| constructor(classConstructor, data = {}) { | ||
| constructor(classConstructor, data = {}, _validators, _requiredFields) { | ||
| /** | ||
| * Custom validators that run during build. | ||
| * @private | ||
| */ | ||
| this._validators = []; | ||
| /** | ||
| * 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(); | ||
| this._classConstructor = classConstructor; | ||
| this._actual = data; | ||
| if (_validators) { | ||
| this._validators = [..._validators]; | ||
| } | ||
| if (_requiredFields) { | ||
| this._requiredFields = _requiredFields instanceof Set ? new Set(_requiredFields) : /* @__PURE__ */ new Set([..._requiredFields]); | ||
| } | ||
| } | ||
@@ -408,11 +622,4 @@ /** | ||
| const BuilderClass = this.constructor; | ||
| return new BuilderClass(this._classConstructor, data); | ||
| return new BuilderClass(this._classConstructor, data, this._validators, this._requiredFields); | ||
| } | ||
| /** | ||
| * Sets a property value and returns a new builder instance with updated type state. | ||
| * @template K - The property key being set | ||
| * @param key - The property key to set | ||
| * @param value - The value to assign to the property | ||
| * @returns A new builder instance with the property set | ||
| */ | ||
| setProperty(key, value) { | ||
@@ -430,2 +637,3 @@ const newBuilder = this.createBuilder({ | ||
| * @returns A new builder instance with the properties set | ||
| * @protected | ||
| */ | ||
@@ -440,2 +648,216 @@ setProperties(props) { | ||
| /** | ||
| * 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. | ||
| * Only supports data properties (excludes methods). | ||
| * | ||
| * @template P - The property path (e.g., "address.city") | ||
| * @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 | ||
| * class Address { | ||
| * street!: string; | ||
| * city!: string; | ||
| * } | ||
| * class Person { | ||
| * name!: string; | ||
| * address!: Address; | ||
| * } | ||
| * const builder = new CeriosClassBuilder(Person); | ||
| * const person = builder | ||
| * .setNestedProperty('address.city', 'New York') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| setNestedProperty(path, value) { | ||
| 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]; | ||
| const existing = current[key]; | ||
| if (!(key in current) || typeof existing !== "object" || existing === null) { | ||
| current[key] = {}; | ||
| } else { | ||
| current[key] = this.deepClone(existing); | ||
| } | ||
| current = current[key]; | ||
| } | ||
| current[keys[keys.length - 1]] = value; | ||
| const newBuilder = this.createBuilder(newActual); | ||
| return newBuilder; | ||
| } | ||
| /** | ||
| * 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 = PersonBuilder.create() | ||
| * .setRequiredFields(['name', 'age']) | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('age', 30) | ||
| * .buildWithoutCompileTimeValidation(); | ||
| * ``` | ||
| */ | ||
| setRequiredFields(fields) { | ||
| const BuilderClass = this.constructor; | ||
| return new BuilderClass(this._classConstructor, this._actual, this._validators, /* @__PURE__ */ new Set([...fields])); | ||
| } | ||
| /** | ||
| * Gets the combined required fields from both the static template and instance-level fields. | ||
| * If instance-level fields are set via setRequiredFields(), they are combined with static fields. | ||
| * @private | ||
| */ | ||
| getRequiredTemplate() { | ||
| var _a; | ||
| const ctor = this.constructor; | ||
| const staticFields = (_a = ctor.requiredDataProperties) != null ? _a : []; | ||
| const instanceFields = Array.from(this._requiredFields); | ||
| if (instanceFields.length > 0) { | ||
| return [.../* @__PURE__ */ new Set([...staticFields, ...instanceFields])]; | ||
| } | ||
| return staticFields; | ||
| } | ||
| /** | ||
| * 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 || typeof current !== "object" || !(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; | ||
| } | ||
| /** | ||
| * Adds a custom validator function that will be executed during build. | ||
| * Validators can return true for valid, false for invalid, or a string error message. | ||
| * Multiple validators can be added and all will be checked. | ||
| * | ||
| * @param validator - Function that validates the partial object | ||
| * @returns The builder instance for chaining | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder = PersonBuilder.create() | ||
| * .addValidator(obj => obj.age ? obj.age >= 18 : 'Age must be 18 or older') | ||
| * .addValidator(obj => obj.email?.includes('@') || 'Invalid email format') | ||
| * .setProperty('age', 20) | ||
| * .setProperty('email', 'user@example.com') | ||
| * .build(); | ||
| * ``` | ||
| */ | ||
| addValidator(validator) { | ||
| const BuilderClass = this.constructor; | ||
| return new BuilderClass( | ||
| this._classConstructor, | ||
| this._actual, | ||
| [...this._validators, validator], | ||
| this._requiredFields | ||
| ); | ||
| } | ||
| /** | ||
| * Runs all custom validators and returns any error messages. | ||
| * @private | ||
| */ | ||
| runValidators() { | ||
| const errors = []; | ||
| for (const validator of this._validators) { | ||
| const result = validator(this._actual); | ||
| if (result === false) { | ||
| errors.push("Validation failed"); | ||
| } else if (typeof result === "string") { | ||
| errors.push(result); | ||
| } | ||
| } | ||
| return errors; | ||
| } | ||
| /** | ||
| * Removes an optional property from the builder. | ||
| * Only works with optional data properties (those that can be undefined). | ||
| * Methods are automatically excluded. | ||
| * | ||
| * @template K - The optional property key to remove | ||
| * @param key - The property key to remove | ||
| * @returns A new builder instance without the specified property | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Person { | ||
| * name!: string; | ||
| * email?: string; | ||
| * } | ||
| * const builder = PersonBuilder.create() | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('email', 'john@example.com') | ||
| * .removeOptionalProperty('email'); | ||
| * // Email is now removed from the builder | ||
| * ``` | ||
| */ | ||
| removeOptionalProperty(key) { | ||
| const newData = { ...this._actual }; | ||
| delete newData[key]; | ||
| return this.createBuilder(newData); | ||
| } | ||
| /** | ||
| * Clears all optional properties from the builder, keeping only required data properties. | ||
| * Uses the combined required-field template from static defaults and instance-level fields. | ||
| * If no required fields are configured, all properties are cleared. | ||
| * | ||
| * @returns A new builder instance with only required properties | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * class Person { | ||
| * name!: string; // required | ||
| * age!: number; // required | ||
| * email?: string; // optional | ||
| * phone?: string; // optional | ||
| * } | ||
| * class PersonBuilder extends CeriosClassBuilder<Person> { | ||
| * static requiredDataProperties = ['name', 'age'] as const; | ||
| * } | ||
| * const builder = PersonBuilder.create() | ||
| * .setProperty('name', 'John') | ||
| * .setProperty('age', 30) | ||
| * .setProperty('email', 'john@example.com') | ||
| * .setProperty('phone', '555-1234') | ||
| * .clearOptionalProperties(); | ||
| * // Only name and age are preserved, email and phone are cleared | ||
| * ``` | ||
| */ | ||
| clearOptionalProperties() { | ||
| const requiredProps = this.getRequiredTemplate(); | ||
| const newData = {}; | ||
| for (const prop of requiredProps) { | ||
| const key = prop; | ||
| if (key in this._actual) { | ||
| newData[key] = this._actual[key]; | ||
| } | ||
| } | ||
| return this.createBuilder(newData); | ||
| } | ||
| /** | ||
| * Adds a value to an array property. | ||
@@ -460,8 +882,16 @@ * @template K - The array property key | ||
| * - Compile-time: TypeScript enforces all data properties are set | ||
| * - Runtime: Validates nested class instances are properly instantiated | ||
| * - Runtime: Validates required fields and custom validators | ||
| * | ||
| * @returns The fully built and validated class instance | ||
| * @throws {Error} If nested validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| build() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -477,9 +907,39 @@ const instance = new ctor(this._actual); | ||
| /** | ||
| * Builds the class instance with runtime validation only (no compile-time). | ||
| * Use this when building from external/dynamic data. | ||
| * Builds the final class instance with only compile-time validation, skipping runtime checks. | ||
| * Use this when you want TypeScript safety but need to skip runtime validation for performance. | ||
| * | ||
| * @returns The built class instance | ||
| * @throws {Error} If validation fails | ||
| * - Compile-time: TypeScript enforces all data properties are set | ||
| * - Runtime: No validation | ||
| * | ||
| * @returns The fully built class instance | ||
| */ | ||
| buildWithoutRuntimeValidation() { | ||
| const ctor = this.getClassConstructor(); | ||
| const instance = new ctor(this._actual); | ||
| const dataKeys = Object.keys(this._actual); | ||
| const needsAssign = dataKeys.some((key) => instance[key] === void 0 && this._actual[key] !== void 0); | ||
| if (needsAssign) { | ||
| Object.assign(instance, this._actual); | ||
| } | ||
| return instance; | ||
| } | ||
| /** | ||
| * Builds the final class instance with only runtime validation, skipping compile-time checks. | ||
| * Use this when building from external data where compile-time checks aren't possible. | ||
| * | ||
| * - Compile-time: No TypeScript enforcement | ||
| * - Runtime: Validates required fields and custom validators | ||
| * | ||
| * @returns The fully built class instance | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildWithoutCompileTimeValidation() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -522,5 +982,13 @@ const instance = new ctor(this._actual); | ||
| * @returns The frozen class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildFrozen() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -539,5 +1007,13 @@ const instance = new ctor(this._actual); | ||
| * @returns The deeply frozen class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildDeepFrozen() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -556,5 +1032,13 @@ const instance = new ctor(this._actual); | ||
| * @returns The sealed class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildSealed() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -573,5 +1057,13 @@ const instance = new ctor(this._actual); | ||
| * @returns The deeply sealed class instance | ||
| * @throws {Error} If validation fails | ||
| * @throws {Error} If required fields are missing or validation fails | ||
| */ | ||
| buildDeepSealed() { | ||
| const missing = this.validateRequiredFields(); | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing required fields: ${missing.join(", ")}. Please set these fields before calling build.`); | ||
| } | ||
| const validationErrors = this.runValidators(); | ||
| if (validationErrors.length > 0) { | ||
| throw new Error(`Validation failed: ${validationErrors.join("; ")}`); | ||
| } | ||
| const ctor = this.getClassConstructor(); | ||
@@ -635,2 +1127,55 @@ const instance = new ctor(this._actual); | ||
| } | ||
| /** | ||
| * Creates a new builder instance from an existing class instance. | ||
| * This is useful for creating builders from existing instances to modify them. | ||
| * | ||
| * @param classConstructor - The class constructor to use | ||
| * @param instance - The existing class instance to create a builder from | ||
| * @returns A new builder instance initialized with the instance's data | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const existingPerson = new Person({ name: 'John', age: 30 }); | ||
| * const builder = PersonBuilder.from(Person, existingPerson); | ||
| * const updated = builder.setProperty('age', 31).build(); | ||
| * ``` | ||
| */ | ||
| static from(classConstructor, instance) { | ||
| const clonedData = _CeriosClassBuilder.deepCloneStatic(instance); | ||
| return new this(classConstructor, clonedData); | ||
| } | ||
| /** | ||
| * Creates a clone of the current builder instance. | ||
| * The clone has the same state but is independent - changes to one won't affect the other. | ||
| * | ||
| * @returns A new builder instance with the same state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const builder1 = PersonBuilder.create().setProperty('name', 'John'); | ||
| * const builder2 = builder1.clone(); | ||
| * // builder2 is independent of builder1 | ||
| * ``` | ||
| */ | ||
| clone() { | ||
| const clonedData = this.deepClone(this._actual); | ||
| return this.createBuilder(clonedData); | ||
| } | ||
| /** | ||
| * Static deep clone helper for the from() method. | ||
| * @private | ||
| */ | ||
| static deepCloneStatic(obj) { | ||
| if (obj === null || typeof obj !== "object") { | ||
| return obj; | ||
| } | ||
| if (Array.isArray(obj)) { | ||
| return obj.map((item) => this.deepCloneStatic(item)); | ||
| } | ||
| const cloned = {}; | ||
| for (const key of Object.keys(obj)) { | ||
| cloned[key] = this.deepCloneStatic(obj[key]); | ||
| } | ||
| return cloned; | ||
| } | ||
| }; | ||
@@ -637,0 +1182,0 @@ export { |
+2
-2
| The MIT License (MIT) | ||
| Copyright © 2025 Cerios | ||
| Copyright © 2026 Cerios | ||
@@ -8,2 +8,2 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
+42
-34
| { | ||
| "name": "@cerios/cerios-builder", | ||
| "version": "1.5.0", | ||
| "author": "Ronald Veth - Cerios", | ||
| "version": "1.6.0", | ||
| "description": "A TypeScript builder pattern library providing compile-time type safety for object construction with method chaining and required field validation", | ||
| "keywords": [ | ||
| "builder-pattern", | ||
| "cerios", | ||
| "object-construction", | ||
| "typescript" | ||
| ], | ||
| "homepage": "https://github.com/CeriosTesting/cerios-builder#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/CeriosTesting/cerios-builder/issues" | ||
| }, | ||
| "license": "MIT", | ||
| "keywords": ["typescript", "builder-pattern", "object-construction", "cerios"], | ||
| "author": "Ronald Veth - Cerios", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/CeriosTesting/cerios-builder.git" | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "type": "commonjs", | ||
| "main": "./dist/index.js", | ||
| "module": "./dist/index.mjs", | ||
| "types": "./dist/index.d.ts", | ||
| "files": ["dist"], | ||
| "type": "commonjs", | ||
| "exports": { | ||
@@ -20,41 +35,34 @@ "./package.json": "./package.json", | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/CeriosTesting/cerios-builder/issues" | ||
| }, | ||
| "homepage": "https://github.com/CeriosTesting/cerios-builder#readme", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/CeriosTesting/cerios-builder.git" | ||
| }, | ||
| "scripts": { | ||
| "biome:migrate": "biome migrate --write", | ||
| "build": "tsup", | ||
| "changeset": "npx changeset", | ||
| "changeset:publish": "tsup && changeset publish", | ||
| "changeset": "changeset", | ||
| "changeset:publish": "changeset publish --provenance", | ||
| "changeset:version": "changeset version && npm i", | ||
| "check": "biome check --write", | ||
| "changeset:prepare-release": "npm run changeset:version && npm run format", | ||
| "format": "oxfmt", | ||
| "format:check": "oxfmt --check", | ||
| "lint": "oxlint --type-aware", | ||
| "lint:fix": "oxlint --type-aware --fix", | ||
| "check-exports": "attw --pack .", | ||
| "compile": "tsc --noEmit", | ||
| "dev": "tsc --watch", | ||
| "lint": "biome lint", | ||
| "prepare": "husky install", | ||
| "test": "jest", | ||
| "test:coverage": "jest --coverage", | ||
| "test:watch": "jest --watch", | ||
| "update-all-packages": "npx npm-check-updates -u && npm i" | ||
| "test": "vitest --run", | ||
| "test:coverage": "vitest --coverage", | ||
| "test:watch": "vitest --watch", | ||
| "update-all-packages": "npm-check-updates -u && npm i" | ||
| }, | ||
| "devDependencies": { | ||
| "@arethetypeswrong/cli": "^0.18.2", | ||
| "@biomejs/biome": "2.3.2", | ||
| "@changesets/cli": "^2.29.7", | ||
| "@types/jest": "^29.5.5", | ||
| "@types/node": "^20.8.0", | ||
| "husky": "^8.0.0", | ||
| "jest": "^29.7.0", | ||
| "lint-staged": "^16.2.6", | ||
| "npm-check-updates": "^19.1.2", | ||
| "ts-jest": "^29.1.1", | ||
| "tsup": "^8.5.0", | ||
| "typescript": "^5.2.2" | ||
| "@changesets/cli": "^2.30.0", | ||
| "@types/node": "^25.4.0", | ||
| "husky": "^9.1.7", | ||
| "lint-staged": "^16.3.3", | ||
| "npm-check-updates": "^19.6.3", | ||
| "oxfmt": "^0.38.0", | ||
| "oxlint": "^1.53.0", | ||
| "oxlint-tsgolint": "^0.16.0", | ||
| "tsup": "^8.5.1", | ||
| "typescript": "^5.9.3", | ||
| "vitest": "^4.0.18" | ||
| } | ||
| } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
389176
78.3%3249
88.46%2190
24.57%