> ## Documentation Index
> Fetch the complete documentation index at: https://noorle.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript

> Build Noorle plugins with TypeScript using ComponentizeJS

Build WebAssembly plugins using TypeScript. Noorle uses **ComponentizeJS** to compile TypeScript to WASI Preview 2 WebAssembly.

## Setup

### Install Tools

```bash theme={null}
# Install Noorle CLI
curl -L cli.noorle.dev | sh

# Create a new TypeScript plugin
noorle plugin new my-ts-plugin --lang typescript
cd my-ts-plugin
```

### Project Structure

```
my-ts-plugin/
├── src/
│   ├── index.ts              # Entry point
│   ├── tools.ts              # Tool implementations
│   └── utils.ts              # Utilities
├── tests/
│   └── tools.test.ts
├── package.json              # Dependencies
├── tsconfig.json             # TypeScript config
├── noorle.yaml               # Plugin config
├── world.wit                 # Component interface
├── .env
└── README.md
```

## Define Tools

Export functions as plugin tools.

### Simple Functions

```typescript theme={null}
// src/tools.ts
export function greet(name: string): string {
  return `Hello, ${name}!`;
}

export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}
```

### Complex Types

```typescript theme={null}
interface Item {
  id: string;
  name: string;
  count: number;
}

interface Config {
  [key: string]: string;
}

export function processItems(
  items: Item[],
  config: Config
): Item[] {
  return items.map(item => ({
    ...item,
    name: `${config.prefix}${item.name}`
  }));
}

export function searchItems(
  query: string,
  limit: number = 10
): Item[] {
  return Array.from({ length: Math.min(limit, 5) }, (_, i) => ({
    id: `${i}`,
    name: `${query}_result_${i}`,
    count: i + 1
  }));
}
```

### Type Definitions

Define reusable types:

```typescript theme={null}
// src/types.ts
export interface User {
  id: string;
  name: string;
  email: string;
  active: boolean;
}

export interface Result<T> {
  success: boolean;
  data?: T;
  error?: string;
}

export interface ApiResponse {
  status: number;
  body: string;
  headers: Record<string, string>;
}

// Use in tools
export function validateUser(user: User): Result<User> {
  if (!user.email.includes("@")) {
    return { success: false, error: "Invalid email" };
  }
  return { success: true, data: user };
}
```

## Working with Dependencies

### Add Dependencies

Edit `package.json`:

```json theme={null}
{
  "name": "my-ts-plugin",
  "version": "0.1.0",
  "dependencies": {
    "axios": "^1.6.0",
    "lodash-es": "^4.17.21",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "@types/node": "^20.0.0"
  }
}
```

### Install and Build

```bash theme={null}
# Install dependencies
npm install

# Build the plugin
noorle plugin build
```

### Size Optimization

Choose lightweight dependencies:

```json theme={null}
{
  "dependencies": {
    // ❌ Heavy
    "axios": "^1.6.0",        // ~60KB with dependencies
    "lodash": "^4.17.0",       // ~50KB

    // ✅ Lightweight
    "uuid": "^9.0.0",          // ~10KB
    "date-fns": "^2.30.0"      // ~20KB
  }
}
```

## File I/O

Access filesystem via standard APIs.

```typescript theme={null}
import * as fs from "fs";
import * as path from "path";

export function readConfig(): string {
  const configPath = "/allowed/path/config.json";
  return fs.readFileSync(configPath, "utf-8");
}

export function writeCache(key: string, value: string): boolean {
  const cacheDir = process.env.CACHE_DIR || "/cache";
  const filePath = path.join(cacheDir, `${key}.txt`);
  fs.writeFileSync(filePath, value);
  return true;
}

export function listFiles(directory: string): string[] {
  return fs.readdirSync(directory)
    .filter(f => fs.statSync(path.join(directory, f)).isFile())
    .map(f => f);
}
```

### Configure Permissions

In `noorle.yaml`:

```yaml theme={null}
permissions:
  filesystem:
    allow:
      - uri: "fs://cache/**"
        access: [read, write]
      - uri: "fs://config/**"
        access: [read]

  environment:
    allow:
      - key: "CACHE_DIR"
```

## Environment Variables

Access configuration via environment variables.

```typescript theme={null}
export function getApiCredentials(): Record<string, string> {
  return {
    apiKey: process.env.API_KEY || "",
    apiSecret: process.env.API_SECRET || "",
  };
}

export function getDatabaseUrl(): string {
  return process.env.DATABASE_URL || "sqlite:///:memory:";
}

export function getConfig(): Record<string, string> {
  return {
    debug: process.env.DEBUG === "true" ? "true" : "false",
    logLevel: process.env.LOG_LEVEL || "info",
    timeout: process.env.TIMEOUT || "30000",
  };
}
```

Configure in `.env`:

```
API_KEY=sk-1234567890
API_SECRET=secret-value
DATABASE_URL=postgres://localhost/mydb
DEBUG=true
LOG_LEVEL=debug
```

## Network Access

Make HTTP requests to allowed hosts.

```typescript theme={null}
export async function fetchData(url: string): Promise<string> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  return response.text();
}

export async function postJson(
  url: string,
  data: Record<string, unknown>
): Promise<string> {
  const response = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
  return response.text();
}

export async function callApi(
  endpoint: string,
  params: Record<string, string>
): Promise<Record<string, unknown>> {
  const url = new URL(`https://api.example.com/${endpoint}`);
  Object.entries(params).forEach(([key, value]) => {
    url.searchParams.append(key, value);
  });

  const response = await fetch(url.toString());
  return response.json();
}
```

Or use a library like `axios` (add to `package.json`):

```typescript theme={null}
import axios from "axios";

export async function callApiAxios(endpoint: string): Promise<string> {
  const response = await axios.get(
    `https://api.example.com/${endpoint}`,
    { timeout: 5000 }
  );
  return response.data;
}
```

Configure network permissions:

```yaml theme={null}
permissions:
  network:
    allow:
      - host: "api.example.com"
      - host: "*.github.com"
```

## Error Handling

Return errors gracefully with typed results:

```typescript theme={null}
interface ErrorResult {
  success: false;
  error: string;
}

interface SuccessResult<T> {
  success: true;
  data: T;
}

type Result<T> = SuccessResult<T> | ErrorResult;

export function safeOperation(input: string): Result<string> {
  try {
    if (!input) {
      return { success: false, error: "Empty input" };
    }

    const result = process(input);
    return { success: true, data: result };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : "Unknown error"
    };
  }
}

function process(input: string): string {
  if (typeof input !== "string") {
    throw new Error("Expected string input");
  }
  return input.toUpperCase();
}
```

## Testing

Test locally before uploading.

### Unit Tests

```typescript theme={null}
// tests/tools.test.ts
import { greet, add, processItems } from "../src/tools";

describe("Tools", () => {
  test("greet returns greeting", () => {
    expect(greet("Alice")).toBe("Hello, Alice!");
  });

  test("add performs addition", () => {
    expect(add(2, 3)).toBe(5);
    expect(add(1.5, 2.5)).toBe(4.0);
  });

  test("processItems transforms items", () => {
    const items = [{ id: "1", name: "test", count: 5 }];
    const config = { prefix: "new_" };
    const result = processItems(items, config);
    expect(result[0].name).toBe("new_test");
  });
});
```

Run tests:

```bash theme={null}
npm test
```

### Integration Tests

```bash theme={null}
# Build and test the plugin
noorle plugin build
noorle plugin test my_tool --input '{"param": "value"}'
```

## Building and Deploying

### Build

```bash theme={null}
noorle plugin build

# Output:
# ✓ Building plugin...
# ✓ Compiling TypeScript to WebAssembly
# ✓ Creating my-ts-plugin.npack (156KB)
```

### Upload

```bash theme={null}
noorle plugin push --api-key YOUR_KEY

# Output:
# ✓ Uploading plugin...
# ✓ Plugin ID: 550e8400-e29b-41d4-a716-446655440000
# ✓ Active version: v1
# ✓ Tools: greet, add, processItems
```

## Best Practices

### Input Validation

```typescript theme={null}
export function validateEmail(email: string): Record<string, unknown> {
  const errors: string[] = [];

  if (!email || email.trim().length === 0) {
    errors.push("Email cannot be empty");
  }

  if (!email.includes("@")) {
    errors.push("Email must contain @");
  }

  if (email.length > 255) {
    errors.push("Email too long");
  }

  return errors.length > 0
    ? { valid: false, errors }
    : { valid: true };
}
```

### Logging

```typescript theme={null}
function log(message: string, level: "INFO" | "DEBUG" | "ERROR" = "INFO") {
  console.error(`[${level}] ${message}`);
}

export function myTool(input: string): string {
  log(`Processing: ${input}`);
  const result = process(input);
  log(`Completed`, "DEBUG");
  return result;
}
```

### Documentation

```typescript theme={null}
/**
 * Transform input text.
 *
 * @param text - The text to transform
 * @param uppercase - Convert to uppercase if true
 * @param trim - Remove whitespace if true
 * @returns Transformed text
 */
export function transformText(
  text: string,
  uppercase: boolean = false,
  trim: boolean = true
): string {
  if (trim) text = text.trim();
  if (uppercase) text = text.toUpperCase();
  return text;
}
```

## Example: Complete Plugin

```typescript theme={null}
// src/weather.ts
import * as fs from "fs";

interface WeatherData {
  city: string;
  temp: number;
  description: string;
}

export async function getWeather(
  city: string,
  units: "celsius" | "fahrenheit" = "celsius"
): Promise<Record<string, unknown>> {
  const apiKey = process.env.OPENWEATHER_API_KEY;
  if (!apiKey) {
    return { error: "API key not configured" };
  }

  const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=${units}`;

  try {
    const response = await fetch(url);
    const data = await response.json();

    return {
      city: data.name,
      temp: data.main.temp,
      description: data.weather[0].description,
    };
  } catch (error) {
    return {
      error: `Failed to fetch: ${error instanceof Error ? error.message : "Unknown error"}`,
    };
  }
}

export function cacheWeather(
  city: string,
  data: WeatherData
): boolean {
  const cacheDir = process.env.CACHE_DIR || "/cache";
  const filePath = `${cacheDir}/${city}.json`;
  fs.writeFileSync(filePath, JSON.stringify(data));
  return true;
}
```

## Troubleshooting

**Build fails with type errors:**

```bash theme={null}
# Check TypeScript compilation
npx tsc --noEmit
```

**Plugin too large (>5MB):**

* Review `node_modules` size
* Remove unused dependencies
* Use tree-shaking compatible libraries

**Runtime errors with environment variables:**

* Verify `.env` file is created
* Check `noorle.yaml` environment permissions
* Ensure variable names are exact match

## Next Steps

* [Configuration Reference](/build/configuration)
* [Permissions and Security](/build/plugins/permissions-and-security)
* [Publishing Your Plugin](/build/deployment/publishing-and-versioning)
