UNPKG

@resk/core

Version:

An innovative TypeScript framework that empowers developers to build applications with a fully decorator-based architecture for efficient resource management. By combining the power of decorators with a resource-oriented design, DecorRes enhances code cla

412 lines (409 loc) 20.9 kB
import "reflect-metadata"; import { IClassConstructor, ITypeRegistryRenderer } from '../types'; /** * Creates a property decorator that stores metadata without making the property readonly @template MetadataType - The type of the metadata @template PropertyKeyType - The type of the property key * @param metadataKey - The key to store the metadata under @param metadata - The metadata to store, or a function that returns the metadata * @returns PropertyDecorator */ export declare function createPropertyDecorator<MetadataType = any, PropertyKeyType extends string | symbol | number = any>(metadataKey: any, /** Retrieves the metadata for a property @param {Object} target - The class constructor @param {PropertyKeyType} propertyKey - The property key @param {MetadataType} existingMetaData - The existing metadata for the property @param {Record<PropertyKeyType, MetadataType>} allExistingMetadata - All existing metadata for the class @returns {MetadataType} The metadata for the property */ metadata: ((existingMetaData: MetadataType, allExistingMetadata: Record<PropertyKeyType, MetadataType>, target: Object, propertyKey: any) => MetadataType) | MetadataType): PropertyDecorator; /** * Retrieves all properties of a class that have been decorated with a specific metadata key. @template MetaDataType - The type of the metadata @template PropertyKeyType - The type of the property key * @param target The class to retrieve decorated properties from. * @param metadataKey The metadata key. * @returns Record of property names and their metadata */ export declare function getDecoratedProperties<MetaDataType = any, PropertyKeyType extends string | symbol | number = any>(target: IClassConstructor, metadataKey: any): Record<PropertyKeyType, MetaDataType>; /** * Retrieves metadata for a specific property. * @template MetaDataType - The type of the metadata * @param target - target The class or object containing the property. * @param metadataKey - The key to retrieve metadata for *@param propertyKey - The name of the propertyfor which metadata is being retrieved. * @returns {MetaDataType} The metadata value for the property. */ export declare function getDecoratedProperty<MetaDataType = any>(target: IClassConstructor, metadataKey: any, propertyKey: any): MetaDataType; /** * @class TypeRegistry * * A utility class responsible for registering and managing custom renderers for various types of components. * * This class supports extensibility by allowing external users to register their own renderers * for different components, such as `datagridCell` or `formField`. It uses a centralized registry * to map types (e.g., 'number', 'string', 'date') to rendering functions. * * The `TypeRegistry` is primarily designed to help render components dynamically based on the value's type * and the component type. It makes use of internal registries to store and retrieve these renderers * based on the context. * It's allows external users to register custom renderers for specific value types * (e.g., 'number', 'string') and component types (e.g., 'datagridCell', 'formField'). These renderers * can then be retrieved and used to render dynamic content in components. * * The class makes use of a centralized registry to store and retrieve renderers for various contexts, * ensuring that each component type has its own set of renderers. This implementation uses Reflect Metadata to store and retrieve renderers efficiently. * * ## Example Usage: * * ```typescript * // Register a number renderer for datagrid cells * TypeRegistry.register('number', 'datagridCell', (value: number) => { * return `<div class="cell number">${value}</div>`; * }); * * // Render a number cell * const numberCell = TypeRegistry.getRenderer('number', 'datagridCell'); * console.log(numberCell?.(123)); // Output: <div class="cell number">123</div> * * // Register a text renderer for form fields * TypeRegistry.register('text', 'formField', (value: string) => { * return `<input type="text" value="${value}" />`; * }); * * // Render a form text field * const textField = TypeRegistry.getRenderer('text', 'formField'); * console.log(textField?.('Hello World')); // Output: <input type="text" value="Hello World" /> * ``` */ export declare class TypeRegistry { private static getMetadataKey; /** * Registers a new renderer for a specific value type and component type. * * This method allows users to extend the system by adding their own custom renderers for various types. * For example, you can register a renderer for numbers in a datagrid or a custom input field for forms. * @template InputType - The type of the value to be rendered. @template OutputType - The type of output that the rendering function will produce. * @param {string} type - The value type (e.g., 'number', 'string', 'date'). * @param {string} componentType - The component type (e.g., 'datagridCell', 'formField'). * @param {ITypeRegistryRenderer<InputType,OutputType>} renderer - The function that renders the value as an HTML string. * * ## Example: * * ```typescript * TypeRegistry.register('number', 'datagridCell', (value: number) => { * return `<div class="cell number">${value}</div>`; * }); * ``` */ static register<InputType = any, OutputType = any>(type: string, componentType: string, renderer: ITypeRegistryRenderer<InputType, OutputType>): void; /** * Retrieves a registered renderer for a specific value type and component type. * * This method allows users to fetch a renderer for a particular type (e.g., 'number', 'string') * and component type (e.g., `datagridCell`, `formField`), which can then be used to render values dynamically. * * @template InputType - The type of the value that the renderer handles. @template OutputType - The type of output that the rendering function will produce. * @param {string} type - The value type (e.g., 'number', 'string', 'date'). * @param {string} componentType - The component type (e.g., 'datagridCell', 'formField'). * @returns {ITypeRegistryRenderer<InputType,OuputType> | undefined} - The renderer function if found, or `undefined` if no renderer is registered. * * ## Example: * * ```typescript * const renderer = TypeRegistry.getRenderer('number', 'datagridCell'); * if (renderer) { * const html = renderer(123); * console.log(html); // Output: <div class="cell number">123</div> * } * ``` */ static getRenderer<InputType = any, OutputType = any>(type: string, componentType: string): ITypeRegistryRenderer<InputType, OutputType> | undefined; /** * Checks if a renderer exists for a specific value type and component type. * * This method can be used to verify whether a renderer is registered for a given type * and component before attempting to render content dynamically. * * @param {string} type - The value type (e.g., 'number', 'string'). * @param {string} componentType - The component type (e.g., 'datagridCell', 'formField'). * @returns {boolean} - Returns `true` if a renderer is found, otherwise `false`. * * ## Example: * * ```typescript * const hasRenderer = TypeRegistry.hasRenderer('number', 'datagridCell'); * console.log(hasRenderer); // Output: true or false depending on if a renderer exists * ``` */ static hasRenderer(type: string, componentType: string): boolean; /** * Renders a component based on its registered type and value. * @template InputType - The type of the value that the renderer handles. @template OutputType - The type of output that the rendering function will produce. * @param type - The type of the value (e.g., 'number', 'string'). * @param value - The value to be rendered. * @param componentType - The type of component being rendered (e.g., 'datagridCell', 'formField'). @param fallbackValue - The value to be rendered if no renderer is found. * @returns {OutputType}.The rendered content */ static render<InputType = any, OutputType = any>(type: string, value: InputType, componentType: string, fallbackValue?: OutputType): OutputType; } /** * A decorator factory that registers a rendering function for a specific type and component. * * This function creates a decorator that can be applied to a rendering function, * allowing the user to register that function with the `TypeRegistry` under a specific * type and component type. This makes it easy to extend rendering capabilities for * different components dynamically. * * @template InputType - The type of input that the rendering function will accept. * @template OutputType - The type of output that the rendering function will produce. * * @param {string} componentType - The type of component being registered (e.g., 'datagridCell', 'formField'). * @returns {function(type: string): function(target: ITypeRegistryRenderer<InputType, OutputType>): void} - * A decorator function that accepts the type and registers the rendering function. * * ## Example: * * ```typescript * // Create a number renderer for datagrid cells * const numberRenderer: ITypeRegistryRenderer<number, string> = (value) => { * return `<div class="cell number">${value}</div>`; * }; * * // Use the decorator to register the renderer * @createTypeRegistryDecorator<number, string>('datagridCell')('number') * class NumberCellRenderer { * render(value: number): string { * return numberRenderer(value); * } * } * * // The number renderer can now be used in a datagrid * const renderer = TypeRegistry.getRenderer('number', 'datagridCell'); * console.log(renderer(123)); // Output: <div class="cell number">123</div> * ``` */ export declare function createTypeRegistryDecorator<InputType = any, OutputType = any>(componentType: string): (type: string) => (target: ITypeRegistryRenderer<InputType, OutputType>) => void; /** * The `ComponentRegistry` class is a dynamic registry system that associates renderers (functions) * with specific component names. It allows you to register and retrieve renderers for various components * based on their names, making it easier to decouple the rendering logic from the components themselves. * This is especially useful in cases where different components may require distinct rendering behavior * for different types of data. * * The renderers are stored using Reflect metadata, enabling the system to dynamically resolve and invoke * the appropriate renderer without hard-coding logic for each component. * * ### Key Features: * - **Dynamic Registration**: Register renderers for specific components at runtime. * - **Flexible Rendering**: Retrieve and apply the correct renderer based on component name. * - **Type-Safe**: Generic types `InputType` and `OutputType` allow for strong typing of the renderer functions. * - **Fallback Mechanism**: Provide a fallback value when no renderer is found for a given component name. * * ### Use Cases: * * #### 1. Custom UI Rendering Based on Component Types * In UI applications, components can have different rendering logic for different data types. * For example, rendering numbers differently from dates. The `ComponentRegistry` allows you to register * specific renderers for these different components and render them based on their types. * * ```ts * // Register a renderer for handling numbers * ComponentRegistry.register<number, string>("numberRenderer", (input: number) => { * return `Formatted Number: ${input.toFixed(2)}`; * }); * * // Register a renderer for handling dates * ComponentRegistry.register<Date, string>("dateRenderer", (input: Date) => { * return `Formatted Date: ${input.toDateString()}`; * }); * * // Render a number using the registered number renderer * const renderedNumber = ComponentRegistry.render(123.456, "numberRenderer"); * console.log(renderedNumber); // Outputs: "Formatted Number: 123.46" * * // Render a date using the registered date renderer * const renderedDate = ComponentRegistry.render(new Date(2024, 9, 15), "dateRenderer"); * console.log(renderedDate); // Outputs: "Formatted Date: Mon Oct 15 2024" * ``` * * #### 2. Fallback Rendering for Unregistered Components * In cases where a renderer is not registered for a given component, you can provide a fallback value. * This ensures that your application can handle missing renderers gracefully. * * ```ts * // Render with a fallback when no renderer is registered for the component * const renderedValue = ComponentRegistry.render(42, "unknownRenderer", "Fallback: No renderer found"); * console.log(renderedValue); // Outputs: "Fallback: No renderer found" * * // Register a renderer for text components later on * ComponentRegistry.register<string, string>("textRenderer", (input: string) => { * return `Text: ${input.toUpperCase()}`; * }); * * // Render the text component after registration * const renderedText = ComponentRegistry.render("hello", "textRenderer", "Default text"); * console.log(renderedText); // Outputs: "Text: HELLO" * ``` * * #### 3. Conditional Rendering for Complex Components * For applications with multiple components that require different renderers depending on conditions * (e.g., dashboard widgets, form elements), the `ComponentRegistry` can dynamically choose which renderer * to use at runtime. * * ```ts * // Register a renderer for number widgets * ComponentRegistry.register<number, string>("widgetNumberRenderer", (input: number) => { * return `Widget Number: ${input}`; * }); * * // Register a renderer for string widgets * ComponentRegistry.register<string, string>("widgetStringRenderer", (input: string) => { * return `Widget String: ${input}`; * }); * * // Use conditional rendering based on component names * const widgetData = { type: "widgetNumberRenderer", value: 25 }; * const renderedWidget = ComponentRegistry.render(widgetData.value, widgetData.type, "Default Widget"); * console.log(renderedWidget); // Outputs: "Widget Number: 25" * ``` * * The `ComponentRegistry` provides an elegant and scalable solution for managing rendering logic across * various types of components, enabling flexible and maintainable code. */ export declare class ComponentRegistry { /** * Constructs the metadata key used for storing component renderers. * * @param componentName - The name of the component for which the metadata key is generated. * If not provided, defaults to `"undefined-component-name"`. * @returns The generated metadata key string, in the format `COMPONENT_REGISTRY_KEY:componentName`. * * @example * ```ts * const key = ComponentRegistry.getMetadataKey("myComponent"); * // key would be something like "COMPONENT_REGISTRY_KEY:myComponent" * ``` */ private static getMetadataKey; /** * Registers a renderer for a specific component by its name. * The renderer is stored using Reflect metadata and can be retrieved later for rendering specific values. * * @typeParam InputType - The type of the input value that the renderer will handle. * @typeParam OutputType - The type of the output value produced by the renderer. * @param componentName - The name of the component to register the renderer for. Must be a non-empty string. * @param renderer - A function that takes an input of `InputType` and returns an output of `OutputType`. * * @example * ```ts * ComponentRegistry.register<number, string>("numberRenderer", (input: number) => `The number is ${input}`); * ``` */ static register<InputType = any, OutputType = any>(componentName: string, renderer: ITypeRegistryRenderer<InputType, OutputType>): void; /** * Retrieves the renderer function for a specific component by its name. * * @typeParam InputType - The type of the input value that the renderer handles. * @typeParam OutputType - The type of the output value produced by the renderer. * @param componentName - The name of the component whose renderer is being retrieved. * @returns The renderer function, if one is registered for the component, otherwise `undefined`. * * @example * ```ts * const renderer = ComponentRegistry.getRenderer<number, string>("numberRenderer"); * if (renderer) { * console.log(renderer(42)); // Outputs: "The number is 42" * } * ``` */ static getRenderer<InputType = any, OutputType = any>(componentName: string): ITypeRegistryRenderer<InputType, OutputType> | undefined; /** * Checks if a renderer is registered for a given component name. * * @param componentName - The name of the component to check. * @returns `true` if a renderer is registered for the component, otherwise `false`. * * @example * ```ts * const hasRenderer = ComponentRegistry.hasRenderer("numberRenderer"); * console.log(hasRenderer); // Outputs: true or false * ``` */ static hasRenderer(componentName: string): boolean; /** * Renders a value using the registered renderer for the specified component. * If no renderer is found, it returns the `fallbackValue` (if provided). * * @typeParam InputType - The type of the input value to be rendered. * @typeParam OutputType - The type of the output value produced by the renderer. * @param value - The input value to be rendered by the component's renderer. * @param componentName - The name of the component whose renderer will process the input value. * @param fallbackValue - (Optional) A value to return if no renderer is registered for the component. * @returns The output value produced by the renderer, or the `fallbackValue` if no renderer is registered. * * @example * ```ts * const output = ComponentRegistry.render(42, "numberRenderer", "No renderer available"); * console.log(output); // Outputs: "The number is 42" or "No renderer available" if the renderer isn't registered * ``` */ static render<InputType = any, OutputType = any>(value: InputType, componentName: string, fallbackValue?: OutputType): OutputType; } /** * Creates a decorator that registers a rendering function for a specified component in the `ComponentRegistry`. * This allows the decorated function to be automatically registered as a renderer for the given component name. * * The decorator can be applied to any function or class that matches the signature of the `ITypeRegistryRenderer<InputType, OutputType>`. * This is useful for associating a specific component renderer with a component name without manually calling `ComponentRegistry.register()`. * * ### Example Usage: * * #### 1. Registering a String Renderer * * ```ts * // Create a string renderer decorator for the component "stringRenderer" * const StringRenderer = createComponentRegistryDecorator<string, string>("stringRenderer"); * * // Apply the decorator to a string rendering function * @StringRenderer * function renderString(input: string): string { * return `Formatted String: ${input.toUpperCase()}`; * } * * // Use the ComponentRegistry to render a string using the registered function * const renderedString = ComponentRegistry.render("hello world", "stringRenderer"); * console.log(renderedString); // Outputs: "Formatted String: HELLO WORLD" * ``` * * #### 2. Registering a Date Renderer * * ```ts * // Create a date renderer decorator for the component "dateRenderer" * const DateRenderer = createComponentRegistryDecorator<Date, string>("dateRenderer"); * * // Apply the decorator to a date rendering function * @DateRenderer * function renderDate(input: Date): string { * return `Formatted Date: ${input.toISOString()}`; * } * * // Use the registered renderer for dates * const renderedDate = ComponentRegistry.render(new Date(), "dateRenderer"); * console.log(renderedDate); // Outputs: "Formatted Date: 2024-10-15T12:00:00.000Z" (example output) * ``` * * @typeParam InputType - The type of the input value that the renderer handles. * @typeParam OutputType - The type of the output value produced by the renderer. * @param componentName - The name of the component to register the renderer for. * @returns A decorator function that registers the target function as a renderer in the `ComponentRegistry`. */ export declare function createComponentRegistryDecorator<InputType = any, OutputType = any>(componentName: string): (target: ITypeRegistryRenderer<InputType, OutputType>) => void;