### Install @zendrex/annotate and reflect-metadata Source: https://github.com/zendrex/annotate/blob/main/README.md Install the @zendrex/annotate package and reflect-metadata using your preferred package manager (Bun, npm, Yarn, or pnpm). ```bash # Bun bun add @zendrex/annotate reflect-metadata # npm npm install @zendrex/annotate reflect-metadata # Yarn yarn add @zendrex/annotate reflect-metadata # pnpm pnpm add @zendrex/annotate reflect-metadata ``` -------------------------------- ### Quick Start: Create and Apply Typed Decorators Source: https://github.com/zendrex/annotate/blob/main/README.md Demonstrates creating typed class and method decorators using createClassDecorator and createMethodDecorator, applying them to a class, and reflecting on the applied metadata. ```typescript import { createClassDecorator, createMethodDecorator } from "@zendrex/annotate"; // Create typed decorators const Controller = createClassDecorator(); const Route = createMethodDecorator<{ path: string; method: string }>(); // Apply them @Controller("users") class UserController { @Route({ path: "/", method: "GET" }) list() {} @Route({ path: "/:id", method: "GET" }) get() {} } // Reflect on the metadata const routes = Route.reflect(UserController).methods(); for (const route of routes) { console.log(route.name, route.metadata); } // list [{ path: "/", method: "GET" }] // get [{ path: "/:id", method: "GET" }] ``` -------------------------------- ### User Model with Property Decorators Source: https://context7.com/zendrex/annotate/llms.txt Example demonstrating the use of Observable, Coerce, and Lazy property decorators on a UserModel class. Shows how properties can be observed, type-coerced, and lazily initialized. ```typescript class UserModel { @Observable("user") name: string = ""; @Observable("user") @Coerce("number") age: number = 0; @Lazy(() => new ExpensiveService()) service!: ExpensiveService; } const user = new UserModel(); user.name = "Alice"; // [user] name changed: -> Alice user.name = "Bob"; // [user] name changed: Alice -> Bob user.age = "25" as any; // Coerced to 25, logs: [user] age changed: 0 -> 25 // Lazy property is computed on first access console.log(user.service); // ExpensiveService instance (created on first access) console.log(user.service); // Same instance (cached) ``` -------------------------------- ### Api Client with Method Decorators Source: https://context7.com/zendrex/annotate/llms.txt Example of applying Timed, Retry, and Cached method decorators to class methods in an ApiClient. Demonstrates asynchronous operations and caching. ```typescript class ApiClient { @Timed("api") @Retry(3, 200) async fetchUser(id: string) { const response = await fetch(`/api/users/${id}`); if (!response.ok) throw new Error("Fetch failed"); return response.json(); } @Cached({ ttl: 60000 }) @Timed("cache") getConfig() { return { apiVersion: "v1", timeout: 5000 }; } } const client = new ApiClient(); await client.fetchUser("123"); // [api] fetchUser took 150.23ms (with automatic retries on failure) client.getConfig(); // [cache] getConfig took 0.05ms (first call) client.getConfig(); // Returns cached value, no timing log ``` -------------------------------- ### Create Class Decorator with @zendrex/annotate Source: https://context7.com/zendrex/annotate/llms.txt Demonstrates creating and using class decorators with @zendrex/annotate. Shows basic usage, compose functions for argument transformation, unique constraints, and reflection APIs including inheritance. ```typescript import { createClassDecorator } from "@zendrex/annotate"; // Basic class decorator with string metadata const Controller = createClassDecorator(); // Class decorator with compose function for multiple arguments const Service = createClassDecorator({ compose: (name: string, scope: "singleton" | "transient") => ({ name, scope }), }); // Unique class decorator (throws on second application) const Entity = createClassDecorator<{ tableName: string }>({ unique: true }); @Controller("users") @Service("UserService", "singleton") @Entity({ tableName: "users" }) class UserController { list() {} } // Reflection API console.log(Controller.metadata(UserController)); // "users" console.log(Controller.applied(UserController)); // true console.log(Controller.appliedOwn(UserController)); // true // Inheritance example class AdminController extends UserController {} console.log(Controller.metadata(AdminController)); // "users" (inherited) console.log(Controller.appliedOwn(AdminController)); // false // Require metadata (throws AnnotateError if missing) const path = Controller.requireMetadata(UserController); // "users" // Using ScopedReflector const reflector = Controller.reflect(UserController); const classInfo = reflector.class(); // { kind: "class", name: "UserController", metadata: ["users"], target: UserController } ``` -------------------------------- ### Class Metadata Inheritance and Application Source: https://github.com/zendrex/annotate/blob/main/README.md Explains how to use createClassDecorator to apply metadata to classes and how to use metadata(), applied(), and appliedOwn() to inspect this metadata, including inherited values. ```typescript const Controller = createClassDecorator(); @Controller("users") class UserController {} class AdminController extends UserController {} Controller.metadata(AdminController); // => "users" (inherited) Controller.applied(AdminController); // => true Controller.appliedOwn(AdminController); // => false when only the parent is decorated ``` -------------------------------- ### Create Method Decorator with @zendrex/annotate Source: https://context7.com/zendrex/annotate/llms.txt Illustrates creating and applying method decorators using @zendrex/annotate. Covers route decorators with compose, event handlers, unique endpoint decorators, and various reflection methods for inspecting decorated methods. ```typescript import { createMethodDecorator } from "@zendrex/annotate"; // Route decorator with compose function const Route = createMethodDecorator({ compose: (path: string, method: "GET" | "POST" | "PUT" | "DELETE") => ({ path, method, }), }); // Simple event handler decorator const EventHandler = createMethodDecorator<{ event: string; priority?: number }>(); // Unique method decorator const Endpoint = createMethodDecorator({ unique: true }); class UserApi { @Route("/users", "GET") @EventHandler({ event: "userList", priority: 1 }) list() { return []; } @Route("/users/:id", "GET") get(id: string) { return { id }; } @Route("/users", "POST") create(data: object) { return data; } @Endpoint("static-info") static info() { return "API v1"; } } // Get metadata for specific method console.log(Route.metadata(UserApi, "list")); // { path: "/users", method: "GET" } console.log(Route.applied(UserApi, "get")); // true // Reflect all decorated methods const methods = Route.reflect(UserApi).methods(); for (const method of methods) { console.log(`${method.name}: ${method.metadata[0].method} ${method.metadata[0].path}`); // list: GET /users // get: GET /users/:id // create: POST /users // info: GET static-info (static: true) } // Singular metadata access (first value only) const singular = EventHandler.reflect(UserApi).methodsSingular(); for (const { name, metadata } of singular) { console.log(name, metadata.event); // list userList } ``` -------------------------------- ### createMethodDecorator API Source: https://context7.com/zendrex/annotate/llms.txt Factory for creating typed method decorators to attach metadata to class methods. ```APIDOC ## createMethodDecorator ### Description Creates a typed method decorator factory for attaching metadata to class methods. Supports both instance and static methods with separate storage, inheritance-aware reflection, and optional unique constraints. ### Method Factory function ### Endpoint N/A (Decorator factory) ### Parameters #### Path Parameters N/A #### Query Parameters N/A #### Request Body N/A ### Request Example ```typescript import { createMethodDecorator } from "@zendrex/annotate"; // Route decorator with compose function const Route = createMethodDecorator({ compose: (path: string, method: "GET" | "POST" | "PUT" | "DELETE") => ({ path, method, }), }); // Simple event handler decorator const EventHandler = createMethodDecorator<{ event: string; priority?: number }>(); // Unique method decorator const Endpoint = createMethodDecorator({ unique: true }); class UserApi { @Route("/users", "GET") @EventHandler({ event: "userList", priority: 1 }) list() { return []; } @Route("/users/:id", "GET") get(id: string) { return { id }; } @Route("/users", "POST") create(data: object) { return data; } @Endpoint("static-info") static info() { return "API v1"; } } // Get metadata for specific method console.log(Route.metadata(UserApi, "list")); // { path: "/users", method: "GET" } console.log(Route.applied(UserApi, "get")); // true // Reflect all decorated methods const methods = Route.reflect(UserApi).methods(); for (const method of methods) { console.log(`${method.name}: ${method.metadata[0].method} ${method.metadata[0].path}`); // list: GET /users // get: GET /users/:id // create: POST /users // info: GET static-info (static: true) } // Singular metadata access (first value only) const singular = EventHandler.reflect(UserApi).methodsSingular(); for (const { name, metadata } of singular) { console.log(name, metadata.event); // list userList } ``` ### Response #### Success Response (200) N/A (Decorator factory) #### Response Example N/A ``` -------------------------------- ### Compose Decorator Arguments into Metadata Source: https://github.com/zendrex/annotate/blob/main/README.md Shows how to use the 'compose' option in createMethodDecorator to transform multiple decorator arguments into a single metadata object. ```typescript const Route = createMethodDecorator({ compose: (path: string, method: "GET" | "POST") => ({ path, method }), }); class Api { @Route("/users", "GET") getUsers() {} } ``` -------------------------------- ### Singular Method Lists for Event Handlers Source: https://github.com/zendrex/annotate/blob/main/README.md Demonstrates using createMethodDecorator to define event handlers and then using reflect().methodsSingular() to iterate over unique event handlers applied to a class. ```typescript const EventHandler = createMethodDecorator(); class Component { @EventHandler({ event: "click" }) onClick() {} } for (const { name, metadata } of EventHandler.reflect(Component).methodsSingular()) { bind(name, metadata); } EventHandler.metadata(Component, "onClick"); EventHandler.requireMetadata(Component, "onClick"); ``` -------------------------------- ### Reflection on Property Decorators Source: https://github.com/zendrex/annotate/blob/main/README.md Demonstrates using createPropertyDecorator to add metadata to class properties and then reflecting on this metadata to retrieve column information. ```typescript const Column = createPropertyDecorator<{ type: string; nullable?: boolean }>(); class User { @Column({ type: "varchar" }) name!: string; @Column({ type: "int", nullable: true }) age!: number; } const columns = Column.reflect(User).properties(); for (const col of columns) { console.log(col.name, col.metadata); } ``` -------------------------------- ### Enable Decorator Metadata in tsconfig.json Source: https://github.com/zendrex/annotate/blob/main/README.md Ensure that 'experimentalDecorators' and 'emitDecoratorMetadata' are set to true in your tsconfig.json file to use decorators and metadata reflection. ```jsonc { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, "//": "...other options as needed" } } ``` -------------------------------- ### Property Injection with Decorators Source: https://github.com/zendrex/annotate/blob/main/README.md Illustrates using createPropertyDecorator to inject dependencies into class properties and then reflecting on these properties to resolve dependencies from a container. ```typescript import { createPropertyDecorator } from "@zendrex/annotate"; const Inject = createPropertyDecorator(); class UserService { @Inject("database") db!: Database; @Inject("logger") logger!: Logger; } const deps = Inject.reflect(UserService).properties(); const instance = new UserService(); for (const dep of deps) { (instance as any)[dep.name] = container.get(dep.metadata[0]); } ``` -------------------------------- ### Decorator Factories for Typed Decorators Source: https://github.com/zendrex/annotate/blob/main/README.md Illustrates the use of various decorator factories (createClassDecorator, createMethodDecorator, createPropertyDecorator, createParameterDecorator) to create typed decorators with built-in reflection. ```typescript import { createClassDecorator, createMethodDecorator, createPropertyDecorator, createParameterDecorator, } from "@zendrex/annotate"; const Tag = createClassDecorator(); const Route = createMethodDecorator(); const Column = createPropertyDecorator(); const Param = createParameterDecorator(); ``` -------------------------------- ### Create Property Interceptor for Observability Source: https://context7.com/zendrex/annotate/llms.txt Use createPropertyInterceptor to make properties observable. The onSet hook logs changes to the property value. Requires a metadata string for identification. ```typescript import { createPropertyInterceptor } from "@zendrex/annotate"; // Observable property that notifies on change const Observable = createPropertyInterceptor({ onSet: (original, metadata, ctx) => function (this: unknown, value: unknown) { const oldValue = (this as any)[`_${String(ctx.name)}`]; original.call(this, value); if (oldValue !== value) { console.log(`[${metadata[0]}] ${String(ctx.name)} changed: ${oldValue} -> ${value}`); } }, }); ``` -------------------------------- ### createClassDecorator API Source: https://context7.com/zendrex/annotate/llms.txt Factory for creating typed class decorators with metadata storage and reflection. ```APIDOC ## createClassDecorator ### Description Creates a typed class decorator factory with automatic metadata storage and reflection helpers. Supports inheritance-aware metadata lookup, unique constraint enforcement, and compose functions for argument transformation. ### Method Factory function ### Endpoint N/A (Decorator factory) ### Parameters #### Path Parameters N/A #### Query Parameters N/A #### Request Body N/A ### Request Example ```typescript import { createClassDecorator } from "@zendrex/annotate"; // Basic class decorator with string metadata const Controller = createClassDecorator(); // Class decorator with compose function for multiple arguments const Service = createClassDecorator({ compose: (name: string, scope: "singleton" | "transient") => ({ name, scope }), }); // Unique class decorator (throws on second application) const Entity = createClassDecorator<{ tableName: string }>({ unique: true }); @Controller("users") @Service("UserService", "singleton") @Entity({ tableName: "users" }) class UserController { list() {} } // Reflection API console.log(Controller.metadata(UserController)); // "users" console.log(Controller.applied(UserController)); // true console.log(Controller.appliedOwn(UserController)); // true // Inheritance example class AdminController extends UserController {} console.log(Controller.metadata(AdminController)); // "users" (inherited) console.log(Controller.appliedOwn(AdminController)); // false // Require metadata (throws AnnotateError if missing) const path = Controller.requireMetadata(UserController); // "users" // Using ScopedReflector const reflector = Controller.reflect(UserController); const classInfo = reflector.class(); // { kind: "class", name: "UserController", metadata: ["users"], target: UserController } ``` ### Response #### Success Response (200) N/A (Decorator factory) #### Response Example N/A ``` -------------------------------- ### Create Method Interceptor for Timing Source: https://context7.com/zendrex/annotate/llms.txt Use createMethodInterceptor to create a timing decorator. It wraps method execution to log duration. Requires performance API. ```typescript import { createMethodInterceptor } from "@zendrex/annotate"; // Timing interceptor const Timed = createMethodInterceptor({ intercept: (original, metadata, ctx) => function (this: unknown, ...args: unknown[]) { const start = performance.now(); const result = original.apply(this, args); const duration = performance.now() - start; console.log(`[${metadata[0]}] ${String(ctx.name)} took ${duration.toFixed(2)}ms`); return result; }, }); ``` -------------------------------- ### Create a Method Interceptor Source: https://github.com/zendrex/annotate/blob/main/README.md Use `createMethodInterceptor` to define custom logic that runs before, after, or around method execution. The `intercept` function receives the original method, metadata, and context, and should return a new function that wraps the original. ```typescript import { createMethodInterceptor } from "@zendrex/annotate"; const Timed = createMethodInterceptor({ intercept: (original, metadata, ctx) => function (...args) { const start = performance.now(); const result = original.apply(this, args); console.log(`${String(ctx.name)} took ${performance.now() - start}ms`); return result; }, }); ``` -------------------------------- ### Create Method Interceptor for Caching Source: https://context7.com/zendrex/annotate/llms.txt Develop a caching decorator with createMethodInterceptor. It stores results based on arguments and respects a TTL. Uses a Map for cache storage. ```typescript // Caching interceptor const Cached = createMethodInterceptor<{ ttl: number }>({ intercept: (original, metadata, ctx) => { const cache = new Map(); return function (this: unknown, ...args: unknown[]) { const key = JSON.stringify(args); const cached = cache.get(key); if (cached && cached.expires > Date.now()) { return cached.value; } const result = original.apply(this, args); cache.set(key, { value: result, expires: Date.now() + metadata[0].ttl }); return result; }; }, }); ``` -------------------------------- ### Low-Level Metadata Storage Functions Source: https://context7.com/zendrex/annotate/llms.txt Use low-level metadata storage functions like `defineMetadata`, `appendMetadata`, `getMetadata`, `getOwnMetadata`, `getMetadataArray`, `getParameterMap`, and `setParameterMap` for custom integrations and advanced use cases. ```typescript import { getMetadata, getOwnMetadata, getMetadataArray, defineMetadata, appendMetadata, getParameterMap, setParameterMap, } from "@zendrex/annotate"; const MY_KEY = Symbol("myKey"); class Example { method() {} } // Define metadata directly defineMetadata(MY_KEY, { custom: "data" }, Example); defineMetadata(MY_KEY, ["route", "/users"], Example.prototype, "method"); // Read metadata (walks prototype chain) console.log(getMetadata(MY_KEY, Example)); // { custom: "data" } // Read own metadata only (no inheritance) class Child extends Example {} console.log(getOwnMetadata(MY_KEY, Child)); // undefined console.log(getMetadata(MY_KEY, Child)); // { custom: "data" } (inherited) // Work with metadata arrays appendMetadata(MY_KEY, "first", Example.prototype, "method"); appendMetadata(MY_KEY, "second", Example.prototype, "method"); const arr = getMetadataArray(MY_KEY, Example.prototype, "method"); console.log(arr); // ["first", "second"] // Parameter metadata maps const paramMap = getParameterMap(MY_KEY, Example.prototype, "method"); paramMap.set(0, ["paramName"]); paramMap.set(1, ["anotherParam"]); setParameterMap(MY_KEY, Example.prototype, paramMap, "method"); // Retrieve parameter map const stored = getParameterMap(MY_KEY, Example.prototype, "method"); console.log(stored.get(0)); // ["paramName"] console.log(stored.get(1)); // ["anotherParam"] ``` -------------------------------- ### Create Typed Parameter Decorators Source: https://context7.com/zendrex/annotate/llms.txt Use createParameterDecorator to create factories for decorators that attach metadata to constructor and method parameters. Metadata is stored per parameter index and supports multiple applications on the same slot. ```typescript import { createParameterDecorator } from "@zendrex/annotate"; // Param decorator for route parameters const Param = createParameterDecorator(); // Query decorator with compose function const Query = createParameterDecorator({ compose: (name: string, required: boolean = false) => ({ name, required }), }); // Body decorator for request body const Body = createParameterDecorator<{ validate?: boolean }>(); // Constructor injection const InjectService = createParameterDecorator(); class UserController { constructor( @InjectService("userService") private userService: UserService, @InjectService("logger") private logger: Logger ) {} getUser( @Param("id") id: string, @Query("fields", false) fields?: string ) { return this.userService.find(id, fields); } createUser( @Body({ validate: true }) data: CreateUserDto ) { return this.userService.create(data); } } // Reflect constructor parameters const params = InjectService.reflect(UserController).parameters(); for (const param of params) { if (param.kind === "constructor-parameter") { console.log(`Constructor param ${param.parameterIndex}: ${param.metadata[0]}`); // Constructor param 0: userService // Constructor param 1: logger } } // Get specific parameter metadata console.log(Param.metadata(UserController, 0, "getUser")); // "id" console.log(Query.metadata(UserController, 1, "getUser")); // { name: "fields", required: false } console.log(Body.metadata(UserController, 0, "createUser")); // { validate: true } // Check if parameter is decorated console.log(Param.applied(UserController, 0, "getUser")); // true console.log(Param.applied(UserController, 1, "getUser")); // false // Require metadata (throws if missing) const paramName = Param.requireMetadata(UserController, 0, "getUser"); // "id" ``` -------------------------------- ### Create Typed Property Decorators Source: https://context7.com/zendrex/annotate/llms.txt Use createPropertyDecorator to create factories for decorators that attach metadata to class properties. It ensures property discovery via reflection, even for uninitialized fields. ```typescript import { createPropertyDecorator } from "@zendrex/annotate"; // Column decorator for ORM-like patterns const Column = createPropertyDecorator<{ type: string; nullable?: boolean; default?: unknown; }>(); // Inject decorator for dependency injection const Inject = createPropertyDecorator(); // Validation decorator const Validate = createPropertyDecorator({ compose: (type: "email" | "url" | "phone", message?: string) => ({ type, message: message ?? `Invalid ${type} format`, }), }); class User { @Column({ type: "int", nullable: false }) id!: number; @Column({ type: "varchar", nullable: false }) @Validate("email") email!: string; @Column({ type: "varchar", nullable: true, default: null }) @Validate("phone", "Please enter a valid phone number") phone?: string; @Inject("database") db!: Database; @Inject("logger") logger!: Logger; } // Reflect all decorated properties const columns = Column.reflect(User).properties(); for (const col of columns) { console.log(`${String(col.name)}: ${col.metadata[0].type}`); // id: int // email: varchar // phone: varchar } // Property injection pattern const instance = new User(); const deps = Inject.reflect(User).properties(); for (const dep of deps) { (instance as any)[dep.name] = container.get(dep.metadata[0]); } // Singular properties access const validations = Validate.reflect(User).propertiesSingular(); for (const { name, metadata } of validations) { console.log(name, metadata.type, metadata.message); // email email Invalid email format // phone phone Please enter a valid phone number } ``` -------------------------------- ### Create Property Interceptor for Lazy Loading Source: https://context7.com/zendrex/annotate/llms.txt Implement a lazy-loaded property decorator with createPropertyInterceptor. The onGet hook computes the value only on first access, caching it using a WeakMap. ```typescript // Lazy-loaded property const Lazy = createPropertyInterceptor<() => unknown>({ onGet: (original, metadata, ctx) => { const cache = new WeakMap(); return function (this: object) { if (!cache.has(this)) { cache.set(this, metadata[0]()); } return cache.get(this); }; }, }); ``` -------------------------------- ### Create Property Interceptor for Type Coercion Source: https://context7.com/zendrex/annotate/llms.txt Create a property decorator for type coercion using createPropertyInterceptor. The onSet hook converts the incoming value to a specified type (number, string, boolean). ```typescript // Validated property with type coercion const Coerce = createPropertyInterceptor({ compose: (type: "number" | "string" | "boolean") => type, onSet: (original, metadata, ctx) => function (this: unknown, value: unknown) { let coerced: unknown; switch (metadata[0]) { case "number": coerced = Number(value); break; case "string": coerced = String(value); break; case "boolean": coerced = Boolean(value); break; } original.call(this, coerced); }, }); ``` -------------------------------- ### Create Method Interceptor for Retries Source: https://context7.com/zendrex/annotate/llms.txt Implement a retry decorator using createMethodInterceptor. It allows configuring attempts and delay for asynchronous operations. Handles errors by retrying. ```typescript const Retry = createMethodInterceptor({ compose: (attempts: number, delay: number = 100) => ({ attempts, delay }), intercept: (original, metadata, ctx) => async function (this: unknown, ...args: unknown[]) { const { attempts, delay } = metadata[metadata.length - 1]; let lastError: Error; for (let i = 0; i < attempts; i++) { try { return await original.apply(this, args); } catch (err) { lastError = err as Error; if (i < attempts - 1) { await new Promise((r) => setTimeout(r, delay * (i + 1))); } } } throw lastError!; }, }); ``` -------------------------------- ### Create and Use a Reflector for Class Metadata Source: https://context7.com/zendrex/annotate/llms.txt Use the `reflect` function to create an unscoped reflector for querying metadata across multiple decorator factories on a class. This is useful for inspecting class, property, method, and parameter decorators. ```typescript import { reflect, createClassDecorator, createMethodDecorator, createPropertyDecorator, createParameterDecorator } from "@zendrex/annotate"; const Entity = createClassDecorator(); const Column = createPropertyDecorator<{ type: string }>(); const Route = createMethodDecorator<{ path: string }>(); const Param = createParameterDecorator(); @Entity("users") class User { @Column({ type: "int" }) id!: number; @Column({ type: "varchar" }) name!: string; @Route({ path: "/greet" }) greet(@Param("message") message: string) { return `Hello, ${message}`; } } // Create unscoped reflector const reflector = reflect(User); // Query with different metadata keys const classInfo = reflector.class(Entity.key); console.log(classInfo?.metadata); // ["users"] const columns = reflector.properties(Column.key); for (const col of columns) { console.log(`${String(col.name)}: ${col.metadata[0].type}`); } const routes = reflector.methods(Route.key); for (const route of routes) { console.log(`${String(route.name)}: ${route.metadata[0].path}`); } const params = reflector.parameters(Param.key); for (const param of params) { if (param.kind === "method-parameter") { console.log(`${String(param.methodName)}[${param.parameterIndex}]: ${param.metadata[0]}`); } } // Get all decorated items at once const all = reflector.all(Column.key); // Returns array of DecoratedItem: class, methods, properties, parameters ``` -------------------------------- ### Handle AnnotateError for Decorator Invariants Source: https://context7.com/zendrex/annotate/llms.txt Utilize the custom `AnnotateError` class for handling invariant violations thrown by decorator factories, such as duplicate applications or missing metadata. Inspect `err.code`, `err.kind`, `err.target`, and `err.message` for details. ```typescript import { createClassDecorator, createMethodDecorator, AnnotateError, AnnotateErrorCode } from "@zendrex/annotate"; // Unique decorator that throws on duplicate application const Singleton = createClassDecorator({ unique: true, name: "Singleton" }); const Endpoint = createMethodDecorator({ unique: true, name: "Endpoint" }); @Singleton() // @Singleton() // Would throw: AnnotateError with code: "duplicate" class Service {} class Api { @Endpoint("/users") // @Endpoint("/users/list") // Would throw: duplicate on same method list() {} } // Handling missing metadata const Controller = createClassDecorator({ name: "Controller" }); class Undecorated {} try { Controller.requireMetadata(Undecorated); } catch (err) { if (err instanceof AnnotateError) { console.log(err.code); // "missing" console.log(err.kind); // "class" console.log(err.target); // Undecorated console.log(err.message); // "Controller: missing metadata on class Undecorated" } } // Error codes console.log(AnnotateErrorCode.DUPLICATE); // "duplicate" console.log(AnnotateErrorCode.MISSING); // "missing" // Safely check without throwing if (!Controller.applied(Undecorated)) { console.log("Class is not decorated"); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.