# TuffTransport API

## Overview

TuffTransport is the next-generation IPC communication system for Tuff plugins, providing type-safe, high-performance messaging with built-in support for batching and streaming.

::alert{type="info"}
TuffTransport is the supported IPC entry point for current plugin development. The old Channel API is retained only as a historical migration reference.
::

## Why TuffTransport?

| Feature | Legacy Channel | TuffTransport |
|---------|---------------|---------------|
| Type Safety | ❌ String-based events | ✅ Compile-time type checking |
| Autocomplete | ❌ No IDE support | ✅ Full IntelliSense |
| Batching | ❌ Manual | ✅ Automatic request batching |
| Streaming | ❌ Not supported | ✅ MessagePort streaming |
| Error Handling | ❌ Generic errors | ✅ Structured error types |

---

## Introduction

**Quick Start**

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { useTuffTransport, CoreBoxEvents, StorageEvents } from '@talex-touch/utils/transport'

  const transport = useTuffTransport()

  // Send a typed request
  await transport.send(CoreBoxEvents.ui.hide)

  // Send with payload
  const result = await transport.send(CoreBoxEvents.search.query, {
    query: { text: 'hello' }
  })

  // Batch requests (automatic)
  const [theme, lang] = await Promise.all([
    transport.send(StorageEvents.app.get, { key: 'theme' }),
    transport.send(StorageEvents.app.get, { key: 'language' }),
  ])
---
:::

---

## Core Concepts

**TuffEvent**

Every communication in TuffTransport uses a `TuffEvent` - a type-safe event definition that encodes request/response types at compile time.

:::TuffCodeBlock{lang="ts"}
---
code: |
  // ❌ Retired: string-based, no type safety
  channel.send('core-box:search:query', { text: 'hello' })

  // ✅ TuffTransport: Type-safe, autocomplete
  transport.send(CoreBoxEvents.search.query, { query: { text: 'hello' } })
  //                                          ↑ TypeScript enforces correct payload
---
:::

**Event Builder**

Create custom events using the `defineEvent` builder:

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { defineEvent } from '@talex-touch/utils/transport'

  // Define a typed event
  const MyPluginEvents = {
    data: {
      fetch: defineEvent('my-plugin')
        .module('data')
        .event('fetch')
        .define<{ id: string }, { name: string; value: number }>()
    }
  }

  // Usage - fully typed!
  const result = await transport.send(MyPluginEvents.data.fetch, { id: '123' })
  console.log(result.name, result.value) // ✅ Type-safe access
---
:::

---

## API Reference

**useTuffTransport()**

Get the transport instance in a plugin renderer.

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { useTuffTransport } from '@talex-touch/utils/transport'

  const transport = useTuffTransport()
---
:::

**transport.send(event, payload?, options?)**

Send a request and wait for response.

**Parameters:**
- `event` - A TuffEvent instance (required)
- `payload` - Request payload matching event's request type
- `options` - Send options

**Options:**
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `immediate` | `boolean` | `false` | Skip batching, send immediately |
| `timeout` | `number` | `10000` | Request timeout in ms |

**Examples:**

:::TuffCodeBlock{lang="ts"}
---
code: |
  // Simple request
  await transport.send(CoreBoxEvents.ui.hide)

  // Request with payload
  const result = await transport.send(StorageEvents.app.get, { key: 'theme' })

  // Immediate (skip batching)
  await transport.send(StorageEvents.app.set, 
    { key: 'urgent', value: true },
    { immediate: true }
  )

  // Custom timeout
  await transport.send(SlowEvent, data, { timeout: 30000 })
---
:::

**transport.stream(event, payload, options)**

Initiate a streaming request via MessagePort.

:::TuffCodeBlock{lang="ts"}
---
code: |
  const controller = await transport.stream(
    CoreBoxEvents.search.query,
    { query: { text: 'hello' } },
    {
      onData: (result) => {
        console.log('Received:', result)
      },
      onEnd: () => {
        console.log('Stream complete')
      },
      onError: (err) => {
        console.error('Stream error:', err)
      }
    }
  )

  // Cancel stream if needed
  controller.cancel()
---
:::

**MessagePort Upgrade & Fallback**

- `stream/on` auto-upgrades to MessagePort on allowlisted channels; falls back to channel if unavailable.
- Runtime switch: `TALEX_TRANSPORT_PORT_CHANNELS` (comma-separated event names), empty disables. Default:
  `ClipboardEvents.change`, `AppEvents.fileIndex.progress`, `CoreBoxEvents.search.update`,
  `CoreBoxEvents.search.end`, `CoreBoxEvents.search.noResults`.
- Manual control: set `options.port = false` to force channel, or configure `options.port.timeoutMs` /
  `options.port.scope`.

:::TuffCodeBlock{lang="ts"}
---
code: |
  // Custom port timeout
  const controller = await transport.stream(
    AppEvents.fileIndex.progress,
    undefined,
    {
      onData: (data) => console.log(data),
      port: { timeoutMs: 800 }
    }
  )
---
:::

:::TuffCodeBlock{lang="ts"}
---
code: |
  // Advanced: explicitly open a port (use only when needed)
  const handle = await transport.openPort({
    channel: CoreBoxEvents.search.update.toEventName(),
    scope: 'window'
  })
  handle?.port.postMessage({ type: 'data', payload: { hello: 'world' } })
---
:::

**transport.on(event, handler)**

Register an event handler for incoming messages.

:::TuffCodeBlock{lang="ts"}
---
code: |
  const cleanup = transport.on(SomeEvent, (payload) => {
    console.log('Received:', payload)
    return { success: true } // Return response
  })

  // Cleanup when done
  onUnmounted(() => cleanup())
---
:::

**transport.flush()**

Force flush all pending batch requests.

:::TuffCodeBlock{lang="ts"}
---
code: |
  await transport.flush()
---
:::

---

## Predefined Events

**CoreBoxEvents**

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { CoreBoxEvents } from '@talex-touch/utils/transport'

  // UI Control
  CoreBoxEvents.ui.show       // Show CoreBox
  CoreBoxEvents.ui.hide       // Hide CoreBox
  CoreBoxEvents.ui.expand     // Expand/collapse

  // Search
  CoreBoxEvents.search.query  // Execute search (streaming)
  CoreBoxEvents.search.cancel // Cancel search

  // Input
  CoreBoxEvents.input.get     // Get input value
  CoreBoxEvents.input.set     // Set input value
  CoreBoxEvents.input.clear   // Clear input

  // Provider
  CoreBoxEvents.provider.deactivate    // Deactivate provider
  CoreBoxEvents.provider.getDetails    // Get provider details (batching)
---
:::

**StorageEvents**

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { StorageEvents } from '@talex-touch/utils/transport'

  // App Storage (with batching)
  StorageEvents.app.get       // Get value
  StorageEvents.app.set       // Set value
  StorageEvents.app.delete    // Delete value

  // Plugin Storage (with batching)
  StorageEvents.plugin.get    // Get plugin value
  StorageEvents.plugin.set    // Set plugin value
---
:::

**PluginEvents**

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { PluginEvents } from '@talex-touch/utils/transport'

  // Lifecycle
  PluginEvents.lifecycle.load     // Load plugin
  PluginEvents.lifecycle.unload   // Unload plugin
  PluginEvents.lifecycle.reload   // Reload plugin

  // Features
  PluginEvents.feature.trigger    // Trigger feature

  // Logging (batched)
  PluginEvents.log.write          // Write log entry
---
:::

**BoxItemEvents**

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { BoxItemEvents } from '@talex-touch/utils/transport'

  // CRUD (with batching)
  BoxItemEvents.crud.create   // Create item
  BoxItemEvents.crud.update   // Update item
  BoxItemEvents.crud.upsert   // Create or update
  BoxItemEvents.crud.delete   // Delete item

  // Batch operations
  BoxItemEvents.batch.upsert  // Batch upsert
  BoxItemEvents.batch.delete  // Batch delete
  BoxItemEvents.batch.clear   // Clear by source
---
:::

**ClipboardEvents :badge[v0.9.0]{type="info"}**

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { ClipboardEvents } from '@talex-touch/utils/transport'

  // Monitor (streaming)
  ClipboardEvents.change      // Subscribe to clipboard changes

  // History (with batching)
  ClipboardEvents.getHistory  // Query history with pagination
  ClipboardEvents.getLatest   // Get most recent item

  // Actions
  ClipboardEvents.apply       // Apply item to active app
  ClipboardEvents.delete      // Delete history item
  ClipboardEvents.setFavorite // Toggle favorite status
  ClipboardEvents.clearHistory // Clear history
  ClipboardEvents.getImageUrl // Resolve original image URL
  ClipboardEvents.write       // Write to system clipboard
  ClipboardEvents.read        // Read current clipboard snapshot
  ClipboardEvents.readImage   // Read clipboard image
  ClipboardEvents.readFiles   // Read clipboard file paths
  ClipboardEvents.clear       // Clear current clipboard data
  ClipboardEvents.copyAndPaste // Write and auto-paste
---
:::

**Example: Subscribe to clipboard changes**

:::TuffCodeBlock{lang="ts"}
---
code: |
  const controller = await transport.stream(
    ClipboardEvents.change,
    undefined,
    {
      onData: ({ latest, history }) => {
        console.log('latest:', latest?.type, latest?.value)
        console.log('history size:', history.length)
      }
    }
  )

  onUnmounted(() => controller.cancel())
---
:::

**Example: Query clipboard history**

:::TuffCodeBlock{lang="ts"}
---
code: |
  const { items, total } = await transport.send(ClipboardEvents.getHistory, {
    page: 1,
    pageSize: 20,
    type: 'text',
    keyword: 'search term'
  })

  console.log(items[0]?.value, total)
---
:::

---

## Batching

TuffTransport automatically batches requests for events that support it:

:::TuffCodeBlock{lang="ts"}
---
code: |
  // These 3 requests are automatically combined into 1 IPC call
  const [a, b, c] = await Promise.all([
    transport.send(StorageEvents.app.get, { key: 'a' }),
    transport.send(StorageEvents.app.get, { key: 'b' }),
    transport.send(StorageEvents.app.get, { key: 'c' }),
  ])
  // Result: Single IPC with 3 requests, 500%+ faster
---
:::

**Batch Configuration**

When defining custom events, configure batching:

:::TuffCodeBlock{lang="ts"}
---
code: |
  const MyEvent = defineEvent('my-plugin')
    .module('data')
    .event('fetch')
    .define<Request, Response>({
      batch: {
        enabled: true,
        windowMs: 50,        // Collect for 50ms
        maxSize: 20,         // Max 20 requests per batch
        mergeStrategy: 'dedupe' // 'queue' | 'dedupe' | 'latest'
      }
    })
---
:::

**Merge Strategies:**
- `queue` - All requests processed in order
- `dedupe` - Duplicate payloads share response
- `latest` - Only latest request for same key kept

---

## Streaming

For large or continuous data, use MessagePort streaming:

:::TuffCodeBlock{lang="ts"}
---
code: |
  const MyStreamEvent = defineEvent('my-plugin')
    .module('data')
    .event('stream')
    .define<{ filter: string }, AsyncIterable<DataItem>>({
      stream: {
        enabled: true,
        bufferSize: 100
      }
    })

  // Consume stream
  const controller = await transport.stream(
    MyStreamEvent,
    { filter: 'active' },
    {
      onData: (item) => items.push(item),
      onEnd: () => console.log('Done'),
      onError: (err) => console.error(err)
    }
  )

  // Cancel if needed
  onUnmounted(() => controller.cancel())
---
:::

---

## Error Handling

TuffTransport provides structured errors:

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { TuffTransportError, TuffTransportErrorCode } from '@talex-touch/utils/transport'

  try {
    await transport.send(SomeEvent, data)
  } catch (err) {
    if (err instanceof TuffTransportError) {
      switch (err.code) {
        case TuffTransportErrorCode.TIMEOUT:
          console.log('Request timed out')
          break
        case TuffTransportErrorCode.INVALID_EVENT:
          console.log('Invalid event - use defineEvent()')
          break
        case TuffTransportErrorCode.UNKNOWN_EVENT:
          console.log('No handler registered')
          break
      }
    }
  }
---
:::

**Error Codes:**
| Code | Description |
|------|-------------|
| `INVALID_EVENT` | Not a valid TuffEvent |
| `UNKNOWN_EVENT` | No handler registered |
| `TIMEOUT` | Request timed out |
| `STREAM_CANCELLED` | Stream was cancelled |
| `BATCH_FAILED` | Batch request failed |
| `SERIALIZE_FAILED` | Payload serialization failed |

---

## Migration from Legacy Channel

**Before (Legacy)**

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { useChannel } from '@talex-touch/utils/plugin/sdk'

  const channel = useChannel()
  const result = await channel.send('core-box:search:query', { text: 'hello' })
---
:::

**After (TuffTransport)**

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { useTuffTransport, CoreBoxEvents } from '@talex-touch/utils/transport'

  const transport = useTuffTransport()
  const result = await transport.send(CoreBoxEvents.search.query, { query: { text: 'hello' } })
---
:::

**Hard-Cut Migration**

Do not keep new plugin code on the Channel path. Replace string-based sends with typed TuffTransport events in the same change:

:::TuffCodeBlock{lang="ts"}
---
code: |
  await transport.send(StorageEvents.app.get, { key: 'theme' })
---
:::

---

## Technical Notes

- Events are defined via `defineEvent().module().event().define()` with compile-time request/response typing.
- Batching aggregates client requests and merges them by strategy before dispatch.
- Streaming uses MessagePort for continuous data delivery.

## Best Practices

1. **Always use TuffEvent** - Never pass strings to `transport.send()`
2. **Define custom events** - Use `defineEvent()` for plugin-specific communication
3. **Leverage batching** - Use `Promise.all()` for multiple requests
4. **Clean up handlers** - Call cleanup functions in `onUnmounted()`
5. **Handle errors** - Check `TuffTransportErrorCode` for specific handling
6. **Use streaming** - For large data or real-time updates

---

## Type Definitions

:::TuffCodeBlock{lang="ts"}
---
code: |
  interface ITuffTransport {
    send<TReq, TRes>(
      event: TuffEvent<TReq, TRes>,
      payload: TReq,
      options?: SendOptions
    ): Promise<TRes>

    stream<TReq, TChunk>(
      event: TuffEvent<TReq, AsyncIterable<TChunk>>,
      payload: TReq,
      options: StreamOptions<TChunk>
    ): Promise<StreamController>

    on<TReq, TRes>(
      event: TuffEvent<TReq, TRes>,
      handler: (payload: TReq) => TRes | Promise<TRes>
    ): () => void

    flush(): Promise<void>
    destroy(): void
  }

  interface SendOptions {
    immediate?: boolean
    timeout?: number
  }

  interface StreamOptions<T> {
    onData: (chunk: T) => void
    onError?: (error: Error) => void
    onEnd?: () => void
  }

  interface StreamController {
    cancel(): void
    readonly cancelled: boolean
    readonly streamId: string
  }
---
:::
