# LaunchDarkly Client

Manage feature flags, get flag evaluations, and interact with LaunchDarkly's feature management platform.

## Methods

| Method                                   | Description                                              |
| ---------------------------------------- | -------------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any LaunchDarkly API request with schema validation |

## Usage

### Get Feature Flag

```typescript
import { api, z, launchDarkly } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_LAUNCHDARKLY = "a1b2c3d4-5678-90ab-cdef-launchda0001";

const FlagSchema = z.object({
  key: z.string(),
  name: z.string(),
  description: z.string(),
  kind: z.string(), // boolean, multivariate
  creationDate: z.number(),
  variations: z.array(
    z.object({
      value: z.unknown(),
      name: z.string().optional(),
      description: z.string().optional(),
    }),
  ),
  temporary: z.boolean(),
  tags: z.array(z.string()),
  environments: z.record(
    z.object({
      on: z.boolean(),
      archived: z.boolean(),
      lastModified: z.number(),
    }),
  ),
});

export default api({
  integrations: {
    launchdarkly: launchDarkly(PROD_LAUNCHDARKLY),
  },
  name: "LaunchDarklyExample",
  input: z.object({
    projectKey: z.string(),
    flagKey: z.string(),
  }),
  output: z.object({
    flagKey: z.string(),
    name: z.string(),
    isEnabled: z.boolean(),
  }),
  async run(ctx, { projectKey, flagKey }) {
    const result = await ctx.integrations.launchdarkly.apiRequest(
      {
        method: "GET",
        path: `/api/v2/flags/${projectKey}/${flagKey}`,
      },
      { response: FlagSchema },
    );

    return {
      flagKey: result.key,
      name: result.name,
      isEnabled: result.environments.production?.on ?? false,
    };
  },
});
```

### List Feature Flags

```typescript
const ListFlagsResponseSchema = z.object({
  items: z.array(FlagSchema),
  totalCount: z.number(),
  _links: z.object({
    self: z.object({ href: z.string() }),
    next: z.object({ href: z.string() }).optional(),
  }),
});

const result = await ctx.integrations.launchdarkly.apiRequest(
  {
    method: "GET",
    path: `/api/v2/flags/${projectKey}`,
    params: {
      limit: 20,
      filter: "query:new-feature", // Search by name/key
      tag: "release", // Filter by tag
    },
  },
  { response: ListFlagsResponseSchema },
);

result.items.forEach((flag) => {
  console.log(`${flag.key}: ${flag.name}`);
});
```

### Create Feature Flag

```typescript
const result = await ctx.integrations.launchdarkly.apiRequest(
  {
    method: "POST",
    path: `/api/v2/flags/${projectKey}`,
    body: {
      key: "new-checkout-flow",
      name: "New Checkout Flow",
      description: "Enables the redesigned checkout experience",
      tags: ["checkout", "experiment"],
      variations: [
        { value: true, name: "Enabled" },
        { value: false, name: "Disabled" },
      ],
      defaults: {
        onVariation: 0,
        offVariation: 1,
      },
      temporary: true,
    },
  },
  { response: FlagSchema },
);
```

### Toggle Flag On/Off

```typescript
const PatchResponseSchema = z.object({
  key: z.string(),
  environments: z.record(z.object({ on: z.boolean() })),
});

// Turn flag ON in production
await ctx.integrations.launchdarkly.apiRequest(
  {
    method: "PATCH",
    path: `/api/v2/flags/${projectKey}/${flagKey}`,
    body: {
      patch: [
        {
          op: "replace",
          path: "/environments/production/on",
          value: true,
        },
      ],
    },
  },
  { response: PatchResponseSchema },
);
```

### Update Flag Targeting

```typescript
// Add a user target
await ctx.integrations.launchdarkly.apiRequest(
  {
    method: "PATCH",
    path: `/api/v2/flags/${projectKey}/${flagKey}`,
    body: {
      patch: [
        {
          op: "add",
          path: "/environments/production/targets/0/values/-",
          value: "user-123", // Add user to variation 0 targets
        },
      ],
    },
  },
  { response: PatchResponseSchema },
);

// Add a targeting rule
await ctx.integrations.launchdarkly.apiRequest(
  {
    method: "PATCH",
    path: `/api/v2/flags/${projectKey}/${flagKey}`,
    body: {
      patch: [
        {
          op: "add",
          path: "/environments/production/rules/-",
          value: {
            clauses: [
              {
                attribute: "country",
                op: "in",
                values: ["US", "CA"],
                negate: false,
              },
            ],
            variation: 0,
          },
        },
      ],
    },
  },
  { response: PatchResponseSchema },
);
```

### Get Flag Evaluation

```typescript
const EvaluationSchema = z.object({
  value: z.unknown(),
  variationIndex: z.number(),
  reason: z
    .object({
      kind: z.string(),
      ruleIndex: z.number().optional(),
      ruleId: z.string().optional(),
    })
    .optional(),
});

// Note: This typically uses the client-side SDK or server SDK
// The REST API is mainly for flag management
const result = await ctx.integrations.launchdarkly.apiRequest(
  {
    method: "POST",
    path: `/api/v2/flags/${projectKey}/${flagKey}/eval`,
    body: {
      environmentKey: "production",
      user: {
        key: "user-123",
        email: "user@example.com",
        custom: {
          plan: "enterprise",
          country: "US",
        },
      },
    },
  },
  { response: EvaluationSchema },
);

console.log(`Flag value: ${result.value}`);
```

### List Projects

```typescript
const ProjectSchema = z.object({
  key: z.string(),
  name: z.string(),
  tags: z.array(z.string()),
  environments: z.array(
    z.object({
      key: z.string(),
      name: z.string(),
      color: z.string(),
    }),
  ),
});

const ListProjectsResponseSchema = z.object({
  items: z.array(ProjectSchema),
});

const result = await ctx.integrations.launchdarkly.apiRequest(
  {
    method: "GET",
    path: "/api/v2/projects",
  },
  { response: ListProjectsResponseSchema },
);
```

### Get Environments

```typescript
const EnvironmentSchema = z.object({
  key: z.string(),
  name: z.string(),
  color: z.string(),
  defaultTtl: z.number(),
  secureMode: z.boolean(),
});

const result = await ctx.integrations.launchdarkly.apiRequest(
  {
    method: "GET",
    path: `/api/v2/projects/${projectKey}/environments`,
  },
  { response: z.object({ items: z.array(EnvironmentSchema) }) },
);
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await launchdarkly.getFlag({ ... });
await launchdarkly.evaluateFlag({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.launchdarkly.apiRequest(
  { method: "GET", path: `/api/v2/flags/${project}/${flag}` },
  { response: FlagSchema }
);
```

### JSON Patch for Updates

LaunchDarkly uses JSON Patch format for updates:

```typescript
// WRONG - Simple body
const body = {
  on: true,
};

// CORRECT - JSON Patch operations
const body = {
  patch: [{ op: "replace", path: "/environments/production/on", value: true }],
};

// Common operations:
// "add" - Add a value
// "replace" - Replace a value
// "remove" - Remove a value
// "move" - Move a value
// "copy" - Copy a value
```

### Environment-Specific Paths

Flag settings are per-environment:

```typescript
// Path to environment-specific setting
const path = "/environments/production/on";
const path = "/environments/staging/targets";

// NOT just
const wrongPath = "/on"; // Missing environment
```

### API Version in Path

Always include `/api/v2/`:

```typescript
// WRONG
const path = `/flags/${project}/${flag}`;

// CORRECT
const path = `/api/v2/flags/${project}/${flag}`;
```

### Variation Indices

Variations are referenced by index (0-based):

```typescript
// variations: [{ value: true }, { value: false }]
// Index 0 = true, Index 1 = false

const body = {
  patch: [
    {
      op: "replace",
      path: "/environments/production/fallthrough/variation",
      value: 0, // Serve variation index 0 (true)
    },
  ],
};
```

### User Context Format

When evaluating flags, users have specific format:

```typescript
const user = {
  key: "user-unique-id", // Required
  email: "user@example.com",
  name: "John Doe",
  firstName: "John",
  lastName: "Doe",
  ip: "1.2.3.4",
  country: "US",
  custom: {
    // Custom attributes
    plan: "enterprise",
    team: "engineering",
  },
};
```

### Rate Limits

LaunchDarkly has rate limits (varies by endpoint):

```typescript
// Check X-Ratelimit-* headers
// Typical limit: 10 requests/second for read, lower for write
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.launchdarkly.apiRequest(
    { method: "GET", path: `/api/v2/flags/${project}/${flag}` },
    { response: FlagSchema },
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [LaunchDarkly API Documentation](https://apidocs.launchdarkly.com/)
- [Feature Flags](https://apidocs.launchdarkly.com/tag/Feature-flags)
- [Projects](https://apidocs.launchdarkly.com/tag/Projects)
- [Environments](https://apidocs.launchdarkly.com/tag/Environments)
