{"version":3,"file":"client.mjs","sources":["../../../src/lib/extensions/client.ts"],"sourcesContent":["/**\n * @file Extension Client\n * @description Enzyme client with Prisma-like $extends API for immutable extension composition\n *\n * Key patterns implemented:\n * - Prisma: Immutable $extends API with type-safe method merging\n * - Fluent API: Method chaining for extension composition\n * - Proxy Pattern: Dynamic method delegation to extensions\n *\n * @module lib/extensions/client\n */\n\nimport {\n  EnzymeExtensionManager,\n  createExtensionManager,\n} from './manager.js';\nimport type {\n  EnzymeExtension,\n  LifecycleHooks,\n  IExtensionEventEmitter,\n} from './types.js';\n\n// ============================================================================\n// Extension Client Interface\n// ============================================================================\n\n/**\n * Configuration for enzyme extension client\n */\nexport interface EnzymeExtensionClientConfig {\n  /** Enable debug logging */\n  debug?: boolean;\n  /** Custom configuration */\n  [key: string]: unknown;\n}\n\n/**\n * Enzyme client with Prisma-like $extends method\n *\n * @example\n * ```ts\n * const client = createExtensionClient()\n *   .$extends(loggingExtension)\n *   .$extends(cacheExtension)\n *   .$extends(metricsExtension);\n *\n * // Extensions are immutable - each $extends returns a new client\n * const clientWithLogging = client.$extends(loggingExtension);\n * const clientWithAll = clientWithLogging.$extends(cacheExtension);\n *\n * // Use client methods (added by extensions)\n * await client.myCustomMethod();\n * ```\n */\nexport class EnzymeExtensionClient {\n  private readonly manager: EnzymeExtensionManager;\n  private readonly config: EnzymeExtensionClientConfig;\n\n  constructor(\n    config: EnzymeExtensionClientConfig = {},\n    manager?: EnzymeExtensionManager\n  ) {\n    this.config = config;\n    this.manager = manager ?? createExtensionManager(config);\n\n    // Bind client methods from existing extensions\n    this.bindClientMethods();\n  }\n\n  /**\n   * Extend the client with an extension (Prisma pattern)\n   * Returns a new client instance with the extension applied (immutable)\n   *\n   * @param extension - The extension to apply\n   * @returns A new client with the extension registered\n   *\n   * @example\n   * ```ts\n   * const loggingExtension: EnzymeExtension = {\n   *   name: 'logging',\n   *   hooks: {\n   *     beforeOperation: async (ctx) => {\n   *       console.log(`[${ctx.operation}] Starting...`);\n   *     },\n   *   },\n   *   client: {\n   *     log: (message: string) => {\n   *       console.log(`[CLIENT] ${message}`);\n   *     },\n   *   },\n   * };\n   *\n   * const client = new EnzymeExtensionClient().$extends(loggingExtension);\n   * client.log('Hello, world!'); // Uses extension method\n   * ```\n   */\n  $extends(extension: EnzymeExtension): this {\n    // Create a new client with copied extensions\n    const newManager = createExtensionManager(this.config);\n\n    // Copy existing extensions to new manager\n    for (const ext of this.manager.getExtensions()) {\n      newManager.register(ext);\n    }\n\n    // Register new extension\n    newManager.register(extension);\n\n    // Create new client instance\n    const newClient = new (this.constructor as typeof EnzymeExtensionClient)(\n      this.config,\n      newManager\n    );\n\n    // Bind all client methods (including new ones)\n    newClient.bindClientMethods();\n\n    return newClient as this;\n  }\n\n  /**\n   * Get the extension manager (for advanced use cases)\n   *\n   * @example\n   * ```ts\n   * const manager = client.$manager;\n   * console.log(manager.count); // Number of registered extensions\n   * ```\n   */\n  get $manager(): EnzymeExtensionManager {\n    return this.manager;\n  }\n\n  /**\n   * Get the event emitter for inter-extension communication\n   *\n   * @example\n   * ```ts\n   * client.$events.on('user:login', (event) => {\n   *   console.log('User logged in:', event.payload);\n   * });\n   *\n   * client.$events.emit('user:login', { userId: 123 });\n   * ```\n   */\n  get $events(): IExtensionEventEmitter {\n    return this.manager.events;\n  }\n\n  /**\n   * Execute lifecycle hooks manually\n   *\n   * @example\n   * ```ts\n   * await client.$executeHooks('onMount', {\n   *   component: 'MyComponent',\n   *   // ...\n   * });\n   * ```\n   */\n  async $executeHooks<T = void>(\n    hookName: keyof LifecycleHooks,\n    context: unknown\n  ): Promise<T | void> {\n    return this.manager.executeHooks<T>(hookName, context);\n  }\n\n  /**\n   * Execute beforeOperation hooks\n   * Returns modified args and cancellation status\n   *\n   * @example\n   * ```ts\n   * const { args, cancelled } = await client.$beforeOperation('fetchUser', {\n   *   userId: 123,\n   * });\n   *\n   * if (!cancelled) {\n   *   // Proceed with operation using modified args\n   * }\n   * ```\n   */\n  async $beforeOperation<TArgs = unknown>(\n    operation: string,\n    args: TArgs,\n    metadata?: Record<string, unknown>\n  ): Promise<{ args: TArgs; cancelled: boolean }> {\n    return this.manager.executeBeforeOperationHooks(operation, args, metadata);\n  }\n\n  /**\n   * Execute afterOperation hooks\n   * Returns modified result\n   *\n   * @example\n   * ```ts\n   * const startTime = Date.now();\n   * const result = await fetchUser(userId);\n   * const duration = Date.now() - startTime;\n   *\n   * const modifiedResult = await client.$afterOperation(\n   *   'fetchUser',\n   *   { userId },\n   *   result,\n   *   duration\n   * );\n   * ```\n   */\n  async $afterOperation<TArgs = unknown, TResult = unknown>(\n    operation: string,\n    args: TArgs,\n    result: TResult,\n    duration: number,\n    metadata?: Record<string, unknown>\n  ): Promise<TResult> {\n    return this.manager.executeAfterOperationHooks(\n      operation,\n      args,\n      result,\n      duration,\n      metadata\n    );\n  }\n\n  /**\n   * Execute mount hooks (React lifecycle)\n   *\n   * @example\n   * ```ts\n   * useEffect(() => {\n   *   client.$mount('MyComponent');\n   *   return () => client.$unmount('MyComponent');\n   * }, []);\n   * ```\n   */\n  async $mount(component?: string, data?: Record<string, unknown>): Promise<void> {\n    await this.manager.executeMountHooks(component, data);\n  }\n\n  /**\n   * Execute unmount hooks (React lifecycle)\n   */\n  async $unmount(component?: string, data?: Record<string, unknown>): Promise<void> {\n    await this.manager.executeUnmountHooks(component, data);\n  }\n\n  /**\n   * Get list of registered extension names\n   *\n   * @example\n   * ```ts\n   * const extensions = client.$list();\n   * console.log(`Registered extensions: ${extensions.join(', ')}`);\n   * ```\n   */\n  $list(): string[] {\n    return this.manager.list();\n  }\n\n  /**\n   * Get extension by name\n   *\n   * @example\n   * ```ts\n   * const extension = client.$get('logging');\n   * if (extension) {\n   *   console.log('Logging extension version:', extension.version);\n   * }\n   * ```\n   */\n  $get(name: string): EnzymeExtension | undefined {\n    return this.manager.getExtension(name);\n  }\n\n  /**\n   * Check if extension is registered\n   *\n   * @example\n   * ```ts\n   * if (client.$has('analytics')) {\n   *   // Analytics extension is available\n   * }\n   * ```\n   */\n  $has(name: string): boolean {\n    return this.manager.hasExtension(name);\n  }\n\n  /**\n   * Get count of registered extensions\n   *\n   * @example\n   * ```ts\n   * console.log(`${client.$count} extensions registered`);\n   * ```\n   */\n  get $count(): number {\n    return this.manager.count;\n  }\n\n  /**\n   * Wrap an async operation with before/after hooks\n   *\n   * @example\n   * ```ts\n   * const fetchUser = async (userId: number) => {\n   *   return client.$wrap('fetchUser', { userId }, async (args) => {\n   *     const response = await fetch(`/api/users/${args.userId}`);\n   *     return response.json();\n   *   });\n   * };\n   * ```\n   */\n  async $wrap<TArgs extends Record<string, unknown>, TResult>(\n    operation: string,\n    args: TArgs,\n    fn: (args: TArgs) => Promise<TResult>,\n    metadata?: Record<string, unknown>\n  ): Promise<TResult> {\n    // Execute beforeOperation hooks\n    const { args: modifiedArgs, cancelled } = await this.$beforeOperation(\n      operation,\n      args,\n      metadata\n    );\n\n    if (cancelled) {\n      throw new Error(`Operation \"${operation}\" was cancelled by an extension`);\n    }\n\n    // Execute the operation\n    const startTime = Date.now();\n    let result: TResult;\n\n    try {\n      result = await fn(modifiedArgs);\n    } catch (error) {\n      // Re-throw but allow afterOperation hooks to run\n      throw error;\n    } finally {\n      const duration = Date.now() - startTime;\n\n      // Execute afterOperation hooks\n      if (result!) {\n        result = await this.$afterOperation(\n          operation,\n          modifiedArgs,\n          result,\n          duration,\n          metadata\n        );\n      }\n    }\n\n    return result;\n  }\n\n  // ==========================================================================\n  // Private Methods\n  // ==========================================================================\n\n  /**\n   * Bind client methods from all registered extensions\n   * This makes extension methods available directly on the client\n   */\n  private bindClientMethods(): void {\n    const clientMethods = this.manager.getClientMethods();\n\n    for (const [methodName, method] of Object.entries(clientMethods)) {\n      // Skip if method name conflicts with built-in client methods\n      if (methodName.startsWith('$') || methodName in this) {\n        console.warn(\n          `Cannot bind extension method \"${methodName}\": conflicts with built-in method`\n        );\n        continue;\n      }\n\n      // Bind method to client\n      (this as any)[methodName] = method.bind(this);\n    }\n  }\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Create a new enzyme extension client\n *\n * @param config - Optional client configuration\n * @returns A new extension client\n *\n * @example\n * ```ts\n * const client = createExtensionClient({ debug: true });\n * ```\n */\nexport function createExtensionClient(\n  config?: EnzymeExtensionClientConfig\n): EnzymeExtensionClient {\n  return new EnzymeExtensionClient(config);\n}\n\n/**\n * Create a pre-configured enzyme client with extensions\n *\n * @param extensions - Extensions to register\n * @param config - Optional client configuration\n * @returns A new extension client with extensions applied\n *\n * @example\n * ```ts\n * const client = createClientWithExtensions(\n *   [loggingExtension, cacheExtension],\n *   { debug: true }\n * );\n * ```\n */\nexport function createClientWithExtensions(\n  extensions: EnzymeExtension[],\n  config?: EnzymeExtensionClientConfig\n): EnzymeExtensionClient {\n  let client = new EnzymeExtensionClient(config);\n\n  for (const extension of extensions) {\n    client = client.$extends(extension);\n  }\n\n  return client;\n}\n\n/**\n * Define an extension with the fluent builder API\n *\n * @example\n * ```ts\n * import { defineExtension } from '@/lib/extensions';\n *\n * const myExtension = defineExtension({\n *   name: 'my-extension',\n *   version: '1.0.0',\n *   hooks: {\n *     onInit: async (ctx) => {\n *       console.log('Extension initialized!');\n *     },\n *   },\n *   client: {\n *     myMethod: () => {\n *       console.log('Custom method from extension');\n *     },\n *   },\n * });\n *\n * const client = createExtensionClient().$extends(myExtension);\n * client.myMethod(); // \"Custom method from extension\"\n * ```\n */\nexport function defineExtension<T extends EnzymeExtension>(extension: T): T {\n  if (!extension.name) {\n    throw new TypeError('Extension name is required');\n  }\n  return extension;\n}\n"],"names":["EnzymeExtensionClient","config","manager","createExtensionManager","extension","newManager","ext","newClient","hookName","context","operation","args","metadata","result","duration","component","data","name","fn","modifiedArgs","cancelled","startTime","error","clientMethods","methodName","method","createExtensionClient","createClientWithExtensions","extensions","client","defineExtension"],"mappings":"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"}