# @conduit-client/service-bindings-lwc

Conduit services for Lightning Web Components (LWC) bindings, providing wire adapter integration for commands.

## Overview

This package provides specialized services for integrating Conduit data fetching with Lightning Web Components. It includes wire adapter services for creating LWC-compatible wire adapters from commands, with specialized support for GraphQL queries.

## Key Features

- **Wire Adapter Support**: Service for creating LWC wire adapter constructors from commands
- **GraphQL Integration**: Specialized GraphQL wire adapter with query validation
- **JSON Schema Validation**: Configuration validation using JSON schemas
- **Subscribable Results**: Support for real-time data updates via subscriptions
- **Refresh Support**: Optional refresh functionality for updating wire data
- **Type Safety**: Full TypeScript support with proper type definitions

## Module Structure

### Core Services

#### `LWCWireBindingService`

Base wire adapter service for integrating commands with LWC wire adapters.

- Creates wire adapter constructors from commands
- Manages wire adapter lifecycle
- Handles configuration validation with JSON schemas
- Supports subscribable and refreshable results

#### `LWCGraphQLWireBindingService`

GraphQL-specific wire adapter service extending the base wire functionality.

- Validates GraphQL operations (queries only, no mutations/subscriptions)
- Maps GraphQL responses to LWC wire format
- Handles GraphQL errors appropriately
- Supports refresh functionality for GraphQL queries

## Usage Examples

### Basic Wire Adapter

First, create the wire adapter outside your component:

```javascript
// userDataAdapter.js
import { buildLWCWireBindingsServiceDescriptor } from '@conduit-client/services/bindings-lwc/v1';

// Build the service
const { service: lwcWireBindingsService } = buildLWCWireBindingsServiceDescriptor();

// Define the JSON schema for your wire adapter configuration
const userDataSchema = {
  type: 'object',
  properties: {
    userId: { type: 'string' },
  },
  required: ['userId'],
  additionalProperties: false,
};

// Create a command factory
const getUserDataCommand = (config) => ({
  execute: async () => {
    // Fetch user data based on config.userId
    const response = await fetch(`/api/users/${config.userId}`);
    const data = await response.json();

    return {
      isOk: () => true,
      value: {
        data: data,
        // Optional: Add subscription support
        subscribe: (callback) => {
          // Set up subscription logic
          const interval = setInterval(() => {
            // Fetch updated data and call callback
            callback({ isOk: () => true, value: { data: updatedData } });
          }, 5000);

          // Return unsubscribe function
          return () => clearInterval(interval);
        },
      },
    };
  },
});

// Bind the command to create a wire adapter constructor
export const getUserData = lwcWireBindingsService.bind(
  getUserDataCommand,
  userDataSchema,
  false // Set to true to expose refresh function
);
```

Then use it in your LWC component:

```javascript
// MyComponent.js
import { LightningElement, wire } from 'lwc';
import { getUserData } from './userDataAdapter';

export default class MyComponent extends LightningElement {
  userId = '123';

  @wire(getUserData, { userId: '$userId' })
  userData;

  get formattedData() {
    if (this.userData.data) {
      return this.userData.data;
    }
    return undefined;
  }

  get error() {
    return this.userData.error;
  }
}
```

### GraphQL Wire Adapter

```javascript
// graphqlAdapter.js
import { buildLWCGraphQLWireBindingsServiceDescriptor } from '@conduit-client/services/bindings-lwc/v1';
import { gql } from '@conduit-client/onestore-graphql-parser/v1';

// Build the GraphQL service
const { service: lwcGraphQLWireBindingsService } = buildLWCGraphQLWireBindingsServiceDescriptor();

// Create a GraphQL command factory
const getGraphQLCommand = (config) => ({
  execute: async () => {
    // Execute GraphQL query
    const response = await fetch('/graphql', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query: config.query,
        variables: config.variables,
        operationName: config.operationName,
      }),
    });

    const graphqlResponse = await response.json();

    return {
      isOk: () => !graphqlResponse.errors || graphqlResponse.errors.length === 0,
      value: {
        data: graphqlResponse,
        subscribe: (callback) => {
          // Set up GraphQL subscription if needed
          return () => {}; // Return unsubscribe
        },
      },
    };
  },
});

// Bind to create a GraphQL wire adapter constructor
export const graphqlWire = lwcGraphQLWireBindingsService.bind(
  getGraphQLCommand,
  undefined, // Schema is handled internally for GraphQL
  true // Enable refresh support
);
```

Then use it in your LWC component:

```javascript
// UserProfile.js
import { LightningElement, wire } from 'lwc';
import { graphqlWire } from './graphqlAdapter';
import { gql } from '@conduit-client/onestore-graphql-parser/v1';

const USER_QUERY = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
    }
  }
`;

export default class UserProfile extends LightningElement {
  userId = '123';

  @wire(graphqlWire, {
    query: USER_QUERY,
    variables: '$variables',
    operationName: 'GetUser',
  })
  userData;

  get variables() {
    return { id: this.userId };
  }

  get user() {
    return this.userData.data?.user;
  }

  get errors() {
    return this.userData.errors;
  }

  async handleRefresh() {
    // If refresh is enabled, you can manually refresh
    if (this.userData.refresh) {
      await this.userData.refresh();
    }
  }
}
```

## API Reference

### Service Descriptors

#### `buildLWCWireBindingsServiceDescriptor()`

Returns a service descriptor for creating LWC wire adapters.

```typescript
const { service } = buildLWCWireBindingsServiceDescriptor();
```

#### `buildLWCGraphQLWireBindingsServiceDescriptor()`

Returns a service descriptor for creating GraphQL-specific LWC wire adapters.

```typescript
const { service } = buildLWCGraphQLWireBindingsServiceDescriptor();
```

### Service Methods

#### `bind(getCommand, schema?, exposeRefresh?)`

Binds a command factory to create a wire adapter constructor.

Parameters:

- `getCommand`: Function that returns a command object with an `execute` method
- `schema`: Optional JSON schema for configuration validation (required for base wire, optional for GraphQL)
- `exposeRefresh`: Optional boolean to expose refresh functionality (default: false)

Returns: A wire adapter constructor class that can be used with LWC's `@wire` decorator

### Wire Adapter Constructor

The constructor returned by `bind` has the following interface:

```typescript
class WireAdapterConstructor {
  constructor(dataCallback: (result: WireAdapterResult) => void);
  connect(): void;
  disconnect(): void;
  update(config: any): void;
}
```

### Wire Adapter Result

The data passed to the LWC component has this shape:

```typescript
interface WireAdapterResult {
  data?: any;
  error?: Error;
  refresh?: () => Promise<void>; // Only if exposeRefresh is true
}
```

### GraphQL-Specific Result

For GraphQL wire adapters, the result follows the GraphQL response format:

```typescript
interface GraphQLWireResult {
  data?: any;
  errors?: Array<{
    message: string;
    locations?: Array<{ line: number; column: number }>;
  }>;
  refresh?: () => Promise<void>; // Only if exposeRefresh is true
}
```

### Command Interface

Commands must implement the interface from `@conduit-client/command-base`:

## Dependencies

- `@conduit-client/utils`: Core utility functions
- `@conduit-client/bindings-utils`: Shared binding utilities
- `@conduit-client/jsonschema-validate`: JSON schema validation
- `@conduit-client/onestore-graphql-parser`: GraphQL parsing utilities
