@decaf-ts/decorator-validation
Version:
simple decorator based validation engine
231 lines • 9.44 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Decoration = void 0;
const constants_1 = require("./constants.cjs");
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function defaultFlavourResolver(target) {
return constants_1.DefaultFlavour;
}
/**
* @description A decorator management class that handles flavoured decorators
* @summary The Decoration class provides a builder pattern for creating and managing decorators with different flavours.
* It supports registering, extending, and applying decorators with context-aware flavour resolution.
* The class implements a fluent interface for defining, extending, and applying decorators with different flavours,
* allowing for framework-specific decorator implementations while maintaining a consistent API.
* @template T Type of the decorator (ClassDecorator | PropertyDecorator | MethodDecorator)
* @param {string} [flavour] Optional flavour parameter for the decorator context
* @class
* @category Model
* @example
* ```typescript
* // Create a new decoration for 'component' with default flavour
* const componentDecorator = new Decoration()
* .for('component')
* .define(customComponentDecorator);
*
* // Create a flavoured decoration
* const vueComponent = new Decoration('vue')
* .for('component')
* .define(vueComponentDecorator);
*
* // Apply the decoration
* @componentDecorator
* class MyComponent {}
* ```
* @mermaid
* sequenceDiagram
* participant C as Client
* participant D as Decoration
* participant R as FlavourResolver
* participant F as DecoratorFactory
*
* C->>D: new Decoration(flavour)
* C->>D: for(key)
* C->>D: define(decorators)
* D->>D: register(key, flavour, decorators)
* D->>F: decoratorFactory(key, flavour)
* F->>R: resolve(target)
* R-->>F: resolved flavour
* F->>F: apply decorators
* F-->>C: decorated target
*/
class Decoration {
/**
* @description Static map of registered decorators
* @summary Stores all registered decorators organized by key and flavour
*/
static { this.decorators = {}; }
/**
* @description Function to resolve flavour from a target
* @summary Resolver function that determines the appropriate flavour for a given target
*/
static { this.flavourResolver = defaultFlavourResolver; }
constructor(flavour = constants_1.DefaultFlavour) {
this.flavour = flavour;
}
/**
* @description Sets the key for the decoration builder
* @summary Initializes a new decoration chain with the specified key
* @param {string} key The identifier for the decorator
* @return {DecorationBuilderMid} Builder instance for method chaining
*/
for(key) {
this.key = key;
return this;
}
/**
* @description Adds decorators to the current context
* @summary Internal method to add decorators with addon support
* @param {boolean} [addon=false] Whether the decorators are addons
* @param decorators Array of decorators
* @return {this} Current instance for chaining
*/
decorate(addon = false, ...decorators) {
if (!this.key)
throw new Error("key must be provided before decorators can be added");
if ((!decorators || !decorators.length) &&
!addon &&
this.flavour !== constants_1.DefaultFlavour)
throw new Error("Must provide overrides or addons to override or extend decaf's decorators");
if (this.flavour === constants_1.DefaultFlavour && addon)
throw new Error("Default flavour cannot be extended");
this[addon ? "extras" : "decorators"] = new Set([
...(this[addon ? "extras" : "decorators"] || new Set()).values(),
...decorators,
]);
return this;
}
/**
* @description Defines the base decorators
* @summary Sets the primary decorators for the current context
* @param decorators Decorators to define
* @return Builder instance for finishing the chain
*/
define(...decorators) {
return this.decorate(false, ...decorators);
}
/**
* @description Extends existing decorators
* @summary Adds additional decorators to the current context
* @param decorators Additional decorators
* @return {DecorationBuilderBuild} Builder instance for building the decorator
*/
extend(...decorators) {
return this.decorate(true, ...decorators);
}
decoratorFactory(key, f = constants_1.DefaultFlavour) {
const contextDecorator = function contextDecorator(target, propertyKey, descriptor) {
const flavour = Decoration.flavourResolver(target);
const cache = Decoration.decorators[key];
let decorators;
const extras = cache[flavour]
? cache[flavour].extras
: cache[constants_1.DefaultFlavour].extras;
const extraArgs = [
...(cache[constants_1.DefaultFlavour].extras
? cache[constants_1.DefaultFlavour].extras.values()
: []),
].reduce((accum, e, i) => {
if (e.args)
accum[i] = e.args;
return accum;
}, {});
if (cache &&
cache[flavour] &&
cache[flavour].decorators &&
cache[flavour].decorators.size) {
decorators = cache[flavour].decorators;
}
else {
decorators = cache[constants_1.DefaultFlavour].decorators;
}
const decoratorArgs = [
...cache[constants_1.DefaultFlavour].decorators.values(),
].reduce((accum, e, i) => {
if (e.args)
accum[i] = e.args;
return accum;
}, {});
const toApply = [
...(decorators ? decorators.values() : []),
...(extras ? extras.values() : []),
];
return toApply.reduce((_, d, i) => {
switch (typeof d) {
case "object": {
const { decorator, args, transform } = d;
const argz = args || i < (decorators ? decorators.size : 0)
? decoratorArgs[i]
: extraArgs[i - (decorators ? decorators.size : 0)] ||
(decorators ? decoratorArgs[i - decorators.size] : []);
const transformed = transform
? transform(argz || [])
: argz || [];
return decorator(...transformed)(target, propertyKey, descriptor);
}
case "function":
return d(target, propertyKey, descriptor);
default:
throw new Error(`Unexpected decorator type: ${typeof d}`);
}
}, { target, propertyKey, descriptor });
};
Object.defineProperty(contextDecorator, "name", {
value: [f, key].join("_decorator_for_"),
writable: false,
});
return contextDecorator;
}
/**
* @description Creates the final decorator function
* @summary Builds and returns the decorator factory function
* @return {function(any, any?, TypedPropertyDescriptor?): any} The generated decorator function
*/
apply() {
if (!this.key)
throw new Error("No key provided for the decoration builder");
Decoration.register(this.key, this.flavour, this.decorators || new Set(), this.extras);
return this.decoratorFactory(this.key, this.flavour);
}
/**
* @description Registers decorators for a specific key and flavour
* @summary Internal method to store decorators in the static registry
* @param {string} key Decorator key
* @param {string} flavour Decorator flavour
* @param [decorators] Primary decorators
* @param [extras] Additional decorators
*/
static register(key, flavour, decorators, extras) {
if (!key) {
throw new Error("No key provided for the decoration builder");
}
if (!decorators)
throw new Error("No decorators provided for the decoration builder");
if (!flavour)
throw new Error("No flavour provided for the decoration builder");
if (!Decoration.decorators[key])
Decoration.decorators[key] = {};
if (!Decoration.decorators[key][flavour])
Decoration.decorators[key][flavour] = {};
if (decorators)
Decoration.decorators[key][flavour].decorators = decorators;
if (extras)
Decoration.decorators[key][flavour].extras = extras;
}
/**
* @description Sets the global flavour resolver
* @summary Configures the function used to determine decorator flavours
* @param {FlavourResolver} resolver Function to resolve flavours
*/
static setFlavourResolver(resolver) {
Decoration.flavourResolver = resolver;
}
static for(key) {
return new Decoration().for(key);
}
static flavouredAs(flavour) {
return new Decoration(flavour);
}
}
exports.Decoration = Decoration;
//# sourceMappingURL=Decoration.js.map