# Deno

Using KeyEnv with Deno applications.

Source: https://keyenv.dev/docs/sdks/deno/

Load KeyEnv secrets in your Deno applications using the CLI or direct API calls.

## Using the CLI (Recommended)

The simplest way to use KeyEnv with Deno is via the CLI's `run` command.

### Install the CLI

```bash
curl -fsSL https://keyenv.dev/install.sh | bash
```

### Configure deno.json

```json
{
  "tasks": {
    "dev": "keyenv run -p YOUR_PROJECT_ID -e development -- deno run -A src/main.ts",
    "start": "keyenv run -p YOUR_PROJECT_ID -e production -- deno run -A src/main.ts"
  }
}
```

### Run with Secrets

```bash
# Set your token
export KEYENV_TOKEN=your-service-token

# Run with secrets injected
deno task dev
```

## Direct API Usage

Deno has built-in `fetch`, so you can call the KeyEnv API directly without any dependencies.

### Fetch and Load Secrets

```typescript
const KEYENV_TOKEN = Deno.env.get("KEYENV_TOKEN");
const PROJECT_ID = "your-project-id";
const ENVIRONMENT = "production";

interface Secret {
  key: string;
  value: string;
}

interface ExportResponse {
  data: Secret[];
}

async function loadSecrets(): Promise<void> {
  const response = await fetch(
    `https://api.keyenv.dev/api/v1/projects/${PROJECT_ID}/environments/${ENVIRONMENT}/secrets/export`,
    {
      headers: {
        "Authorization": `Bearer ${KEYENV_TOKEN}`,
        "Content-Type": "application/json",
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to fetch secrets: ${response.status}`);
  }

  const data: ExportResponse = await response.json();

  for (const secret of data.secrets) {
    Deno.env.set(secret.key, secret.value);
  }

  console.log(`Loaded ${data.secrets.length} secrets`);
}

// Load at startup
await loadSecrets();

// Use secrets
console.log(Deno.env.get("DATABASE_URL"));
```

### Create a Reusable Module

```typescript
// keyenv.ts
const API_BASE = "https://api.keyenv.dev/api/v1";

export interface KeyEnvConfig {
  token: string;
  projectId: string;
  environment: string;
}

export interface Secret {
  key: string;
  value: string;
  description?: string;
}

export class KeyEnv {
  private token: string;
  private projectId: string;
  private environment: string;

  constructor(config: KeyEnvConfig) {
    this.token = config.token;
    this.projectId = config.projectId;
    this.environment = config.environment;
  }

  private async fetch<T>(path: string, options?: RequestInit): Promise<T> {
    const response = await fetch(`${API_BASE}${path}`, {
      ...options,
      headers: {
        "Authorization": `Bearer ${this.token}`,
        "Content-Type": "application/json",
        ...options?.headers,
      },
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(error.message || `HTTP ${response.status}`);
    }

    return response.json();
  }

  async exportSecrets(): Promise<Secret[]> {
    const data = await this.fetch<{ data: Secret[] }>(
      `/projects/${this.projectId}/environments/${this.environment}/secrets/export`
    );
    return data.data;
  }

  async loadEnv(): Promise<number> {
    const secrets = await this.exportSecrets();
    for (const secret of secrets) {
      Deno.env.set(secret.key, secret.value);
    }
    return secrets.length;
  }

  async getSecret(key: string): Promise<Secret> {
    return this.fetch<Secret>(
      `/projects/${this.projectId}/environments/${this.environment}/secrets/${key}`
    );
  }

  async setSecret(key: string, value: string, description?: string): Promise<void> {
    await this.fetch(
      `/projects/${this.projectId}/environments/${this.environment}/secrets/${key}`,
      {
        method: "PUT",
        body: JSON.stringify({ value, description }),
      }
    );
  }
}
```

### Using the Module

```typescript
// main.ts
import { KeyEnv } from "./keyenv.ts";

const keyenv = new KeyEnv({
  token: Deno.env.get("KEYENV_TOKEN")!,
  projectId: "your-project-id",
  environment: "production",
});

// Load all secrets into environment
await keyenv.loadEnv();

// Or get specific secrets
const dbUrl = await keyenv.getSecret("DATABASE_URL");
console.log(dbUrl.value);
```

## Deno Deploy

For Deno Deploy, use environment variables or the direct API approach.

### Using Environment Variables

Set `KEYENV_TOKEN` in your Deno Deploy project settings, then fetch secrets at startup:

```typescript
// main.ts
const TOKEN = Deno.env.get("KEYENV_TOKEN");
const PROJECT = Deno.env.get("KEYENV_PROJECT");

// Cache secrets in module scope
let secrets: Map<string, string> | null = null;

async function getSecrets(): Promise<Map<string, string>> {
  if (secrets) return secrets;

  const response = await fetch(
    `https://api.keyenv.dev/api/v1/projects/${PROJECT}/environments/production/secrets/export`,
    { headers: { "Authorization": `Bearer ${TOKEN}` } }
  );

  const data = await response.json();
  secrets = new Map(data.secrets.map((s: any) => [s.key, s.value]));
  return secrets;
}

Deno.serve(async (_req) => {
  const env = await getSecrets();
  // Use env.get("DATABASE_URL"), etc.
  return new Response("OK");
});
```

## Fresh Framework

For Fresh applications, load secrets in a plugin or middleware:

```typescript
// plugins/keyenv.ts
import { Plugin } from "$fresh/server.ts";

const TOKEN = Deno.env.get("KEYENV_TOKEN");
const PROJECT = Deno.env.get("KEYENV_PROJECT");

export default {
  name: "keyenv",
  async configureServer() {
    const response = await fetch(
      `https://api.keyenv.dev/api/v1/projects/${PROJECT}/environments/production/secrets/export`,
      { headers: { "Authorization": `Bearer ${TOKEN}` } }
    );

    const data = await response.json();
    for (const secret of data.secrets) {
      Deno.env.set(secret.key, secret.value);
    }

    console.log(`Loaded ${data.secrets.length} secrets from KeyEnv`);
  },
} satisfies Plugin;
```

## Best Practices

1. **Use the CLI for local development** - It handles token management and environment switching.

2. **Cache secrets in production** - Avoid fetching on every request:
   ```typescript
   let cachedSecrets: Map<string, string> | null = null;
   let cacheExpiry = 0;

   async function getSecrets() {
     if (cachedSecrets && Date.now() < cacheExpiry) {
       return cachedSecrets;
     }
     // Fetch and cache for 5 minutes
     cachedSecrets = await fetchSecrets();
     cacheExpiry = Date.now() + 5 * 60 * 1000;
     return cachedSecrets;
   }
   ```

3. **Handle errors gracefully**:
   ```typescript
   try {
     await loadSecrets();
   } catch (error) {
     console.error("Failed to load secrets:", error);
     Deno.exit(1);
   }
   ```

4. **Don't commit tokens** - Use environment variables or a `.env` file (gitignored).
