Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.outkit.dev/llms.txt

Use this file to discover all available pages before exploring further.

The @outkit-dev/core package provides OutkitStream — a framework-agnostic streaming protocol handler. Use it in Vue, Svelte, vanilla JavaScript, Node.js, or any environment where @outkit-dev/react doesn’t fit. Zero dependencies. 3 KB gzipped.

Install

The recommended way is the CLI — it detects Vue, Svelte, Astro, vanilla JS, or Node.js and wires the right setup automatically:
npm install -g @outkit-dev/cli@latest
outkit login
outkit init
To install the package manually:
npm install @outkit-dev/core
If you’re using React, install @outkit-dev/react instead — it wraps @outkit-dev/core with React state management automatically.

Quick Start

import { OutkitStream } from '@outkit-dev/core';

const stream = new OutkitStream({
  onBlocks: (blocks) => renderBlocks(blocks),
  onDesign: (tokens) => applyDesignTokens(tokens),
  onComplete: () => console.log('Stream complete'),
  onError: (err) => console.error(err),
});

// Tier 1: One line — feed a fetch Response
const response = await fetch('/api/enhance/123');
await stream.feedResponse(response);
That’s it. feedResponse reads the SSE stream, extracts design tokens, parses blocks incrementally, and signals completion.

OutkitStream

Constructor

const stream = new OutkitStream(callbacks);
CallbackTypeDescription
onBlocks(blocks: ContentBlock[]) => voidCalled whenever new blocks are parsed. Receives the full current array.
onDesign(tokens: Record<string, string>) => voidCalled when design tokens arrive from a meta event.
onComplete() => voidCalled when the stream finishes (either [DONE] or reader exhausted).
onError(error: Error) => voidCalled on stream errors. Optional.
onStateChange(state: OutkitStreamState) => voidCalled on every lifecycle transition. Optional.

Properties

PropertyTypeDescription
isStreamingbooleanWhether the stream is actively receiving data
streamStateOutkitStreamStateCurrent lifecycle: 'idle' | 'streaming' | 'done' | 'error' | 'destroyed'

Four-Tier Feed API

Pick the tier that matches your transport:
feedResponse(res)   ← "Here's a fetch Response, you do everything"
       ↓ internally calls
feedSSE(rawText)    ← "Here are raw bytes in SSE format"
       ↓ internally calls
feedEvent(data)     ← "Here's one unwrapped event payload"
       ↓ internally calls
feedChunk / feedMeta / feedDone   ← "I control everything"

Tier 1: feedResponse(response: Response)

Reads a fetch Response body as an SSE stream. Handles everything end-to-end.
const response = await fetch('/api/enhance/123');
const { blocks, design } = await stream.feedResponse(response);
console.log(`Rendered ${blocks.length} blocks`);
  • Returns Promise<{ blocks: ContentBlock[], design: Record<string, string> }> — the final state after stream completion
  • Auto-resets if a previous stream is in progress
  • Throws on non-ok HTTP responses (status >= 400)
  • Abortable via stream.reset() or stream.destroy()

Tier 2: feedSSE(rawText: string)

Feed raw SSE-formatted bytes. Handles \n\n splitting, data: prefix stripping, and fragment buffering.
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  stream.feedSSE(decoder.decode(value, { stream: true }));
}

Tier 3: feedEvent(data: string)

Feed one unwrapped SSE event payload (the value after data:). Handles [DONE] detection and meta event routing.
// WebSocket integration
ws.onmessage = (event) => {
  stream.feedEvent(event.data);
};

Tier 4: feedChunk / feedMeta / feedDone

Full manual control:
stream.feedChunk(rawLLMToken);                    // accumulate + parse
stream.feedMeta({ '--outkit-primary': '#b30069' }); // set design tokens
stream.feedDone();                                 // finalize

Lifecycle

idle → streaming → done
                → error

reset()

Returns to idle. Clears all accumulated state, buffered blocks, and design tokens. Aborts any in-flight feedResponse.
stream.reset();
// stream is now ready for a new feedResponse/feedSSE/etc.

destroy()

Permanently shuts down the instance. All feed methods become no-ops. Use in cleanup/unmount code.
stream.destroy();
stream.streamState; // 'destroyed'

Examples

Vue 3 Composition API

<script setup lang="ts">
import { ref, onUnmounted } from 'vue';
import { OutkitStream } from '@outkit-dev/core';

const blocks = ref([]);
const design = ref({});
const isStreaming = ref(false);

const stream = new OutkitStream({
  onBlocks: (b) => { blocks.value = b; isStreaming.value = true; },
  onDesign: (d) => { design.value = d; },
  onComplete: () => { isStreaming.value = false; },
});

onUnmounted(() => stream.destroy());

async function enhance(messageId: string) {
  stream.reset();
  blocks.value = [];
  const response = await fetch(`/api/enhance/${messageId}`);
  await stream.feedResponse(response);
}
</script>

Svelte

<script>
  import { OutkitStream } from '@outkit-dev/core';
  import { onDestroy } from 'svelte';

  let blocks = $state([]);
  let design = $state({});
  let isStreaming = $state(false);

  const stream = new OutkitStream({
    onBlocks: (b) => { blocks = b; isStreaming = true; },
    onDesign: (d) => { design = d; },
    onComplete: () => { isStreaming = false; },
  });

  onDestroy(() => stream.destroy());

  async function enhance(messageId) {
    stream.reset();
    blocks = [];
    const response = await fetch(`/api/enhance/${messageId}`);
    await stream.feedResponse(response);
  }
</script>

Vanilla JavaScript

import { OutkitStream } from '@outkit-dev/core';

const container = document.getElementById('output');

const stream = new OutkitStream({
  onBlocks: (blocks) => {
    container.innerHTML = blocks
      .map(block => `<div>${JSON.stringify(block)}</div>`)
      .join('');
  },
  onDesign: (tokens) => {
    for (const [key, value] of Object.entries(tokens)) {
      container.style.setProperty(key, value);
    }
  },
  onComplete: () => console.log('Done'),
});

document.getElementById('btn').onclick = async () => {
  stream.reset();
  const response = await fetch('/api/enhance/123');
  await stream.feedResponse(response);
};

Node.js (Server-Side Processing)

import { OutkitStream } from '@outkit-dev/core';

const blocks = [];
let design = {};

const stream = new OutkitStream({
  onBlocks: (b) => { blocks.length = 0; blocks.push(...b); },
  onDesign: (d) => { design = d; },
  onComplete: () => {
    console.log(`Received ${blocks.length} blocks`);
    console.log('Design tokens:', design);
  },
});

const response = await fetch('https://api.outkit.dev/render/enhance', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-outkit-api-key': process.env.OUTKIT_API_KEY,
  },
  body: JSON.stringify({ content: aiText }),
});

await stream.feedResponse(response);
stream.destroy();

Exports

// Class
import { OutkitStream } from '@outkit-dev/core';

// Types
import type {
  OutkitStreamCallbacks,
  OutkitStreamResult,
  OutkitStreamState,
  ContentBlock,
  ComponentBlock,
  SkeletonBlock,
} from '@outkit-dev/core';

// Streaming utilities
import {
  parseStreamingBlocks,
  completePartialJson,
} from '@outkit-dev/core';