Skip to content

composable-generator

Generates Nuxt 3 composables for reusable logic

From plugin
f5-framework
24104 skills104 agents69 commands
Install
$ npx -y skills add Fujigo-Software/f5-framework-claude --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

Generates Nuxt 3 composables for reusable logic

Agent definition

composable-generator.md
name: nuxt-composable-generator
description: Generates Nuxt 3 composables for reusable logic
applies_to: nuxt

Nuxt Composable Generator Agent

Purpose

Generate reusable Vue 3 composables for Nuxt applications following best practices for state management, data fetching, and logic encapsulation.

Capabilities

  • Data fetching composables
  • State management composables
  • Utility composables
  • Auto-import integration

Input Requirements

  • Composable purpose
  • State requirements
  • API endpoints if data fetching
  • Return interface

Output Deliverables

1. Composable file (composables/) 2. Type definitions 3. Usage examples

Generation Process

1. Analyze Requirements

composable_analysis:
  - name: "useProducts"
  - type: "data-fetching"
  - api_endpoint: "/api/products"
  - features: ["pagination", "search", "crud"]

2. Generate Composable Patterns

Data Fetching Composable

// composables/use{{Entities}}.ts
import type { {{Entity}}, PaginatedResponse, Create{{Entity}}Input } from '~/types';

interface Use{{Entities}}Options {
  immediate?: boolean;
  pageSize?: number;
}

export function use{{Entities}}(options: Use{{Entities}}Options = {}) {
  const { immediate = true, pageSize = 20 } = options;

  // State
  const items = ref<{{Entity}}[]>([]);
  const total = ref(0);
  const page = ref(1);
  const isLoading = ref(false);
  const error = ref<Error | null>(null);

  // Computed
  const totalPages = computed(() => Math.ceil(total.value / pageSize));
  const hasMore = computed(() => page.value < totalPages.value);
  const isEmpty = computed(() => !isLoading.value && items.value.length === 0);

  // Fetch list
  async function fetch(pageNum = 1) {
    isLoading.value = true;
    error.value = null;

    try {
      const response = await $fetch<PaginatedResponse<{{Entity}}>>('/api/{{entities}}', {
        query: { page: pageNum, limit: pageSize },
      });

      items.value = response.items;
      total.value = response.total;
      page.value = pageNum;
    } catch (e) {
      error.value = e instanceof Error ? e : new Error('Failed to fetch');
    } finally {
      isLoading.value = false;
    }
  }

  // Load more (infinite scroll)
  async function loadMore() {
    if (!hasMore.value || isLoading.value) return;

    isLoading.value = true;
    try {
      const response = await $fetch<PaginatedResponse<{{Entity}}>>('/api/{{entities}}', {
        query: { page: page.value + 1, limit: pageSize },
      });

      items.value.push(...response.items);
      page.value += 1;
    } catch (e) {
      error.value = e instanceof Error ? e : new Error('Failed to load more');
    } finally {
      isLoading.value = false;
    }
  }

  // Create
  async function create(input: Create{{Entity}}Input) {
    const item = await $fetch<{{Entity}}>('/api/{{entities}}', {
      method: 'POST',
      body: input,
    });

    items.value.unshift(item);
    total.value += 1;
    return item;
  }

  // Update
  async function update(id: string, input: Partial<{{Entity}}>) {
    const updated = await $fetch<{{Entity}}>(`/api/{{entities}}/${id}`, {
      method: 'PUT',
      body: input,
    });

    const index = items.value.findIndex((item) => item.id === id);
    if (index !== -1) {
      items.value[index] = updated;
    }
    return updated;
  }

  // Delete
  async function remove(id: string) {
    await $fetch(`/api/{{entities}}/${id}`, { method: 'DELETE' });

    items.value = items.value.filter((item) => item.id !== id);
    total.value -= 1;
  }

  // Refresh
  function refresh() {
    return fetch(page.value);
  }

  // Initialize
  if (immediate) {
    fetch();
  }

  return {
    // State (readonly)
    items: readonly(items),
    total: readonly(total),
    page: readonly(page),
    isLoading: readonly(isLoading),
    error: readonly(error),
    // Computed
    totalPages,
    hasMore,
    isEmpty,
    // Actions
    fetch,
    loadMore,
    create,
    update,
    remove,
    refresh,
  };
}

Single Resource Composable

// composables/use{{Entity}}.ts
import type { {{Entity}} } from '~/types';

export function use{{Entity}}(id: MaybeRefOrGetter<string>) {
  const resolvedId = toRef(id);

  const { data, pending, error, refresh } = useFetch<{{Entity}}>(
    () => `/api/{{entities}}/${resolvedId.value}`,
    {
      key: `{{entity}}-${resolvedId.value}`,
      watch: [resolvedId],
    }
  );

  const item = computed(() => data.value);

  return {
    item,
    isLoading: pending,
    error,
    refresh,
  };
}

State Composable

// composables/useAuth.ts
interface User {
  id: string;
  email: string;
  name: string;
  role: string;
}

export function useAuth() {
  const user = useState<User | null>('auth-user', () => null);
  const isAuthenticated = computed(() => !!user.value);

  async function login(email: string, password: string) {
    const data = await $fetch<{ user: User; token: string }>('/api/auth/login', {
      method: 'POST',
      body: { email, password },
    });

    user.value = data.user;
    return data;
  }

  async function logout() {
    await $fetch('/api/auth/logout', { method: 'POST' });
    user.value = null;
    navigateTo('/login');
  }

  async function fetchUser() {
    try {
      const data = await $fetch<User>('/api/auth/me');
      user.value = data;
    } catch {
      user.value = null;
    }
  }

  return {
    user: readonly(user),
    isAuthenticated,
    login,
    logout,
    fetchUser,
  };
}

Utility Composable

// composables/useAsync.ts
export function useAsync<T>() {
  const isLoading = ref(false);
  const error = ref<Error | null>(null);
  const data = ref<T | null>(null);

  async function execute(fn: () => Promise<T>) {
    isLoading.value = true;
    error.value = null;

    try {
      data.value = await fn();
      return data.value;
    } catch (e) {
      error.value = e instanceof Error ? e : new Error('Unknown error')
Read more
Ships withf5-framework

AI-Powered Development Framework for Claude Code

Get the whole plugin, auto-invoked
Stats
24
Stars
0
Views
8
Forks
Quiet
Maintenance
Python
Language
MIT
License
6mo ago
Last commit
6mo ago
Created

Repo: Fujigo-Software/f5-framework-claude