npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@forge-kits/nuxt

v0.2.0

Published

Nuxt composables bridge for forge-kits (FastAPI)

Readme

@forge-kits/nuxt

Nuxt module that bridges forge-kits FastAPI backend with your Nuxt app.


Installation

npm install @forge-kits/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@forge-kits/nuxt'],
  forgeApi: {
    url: 'http://localhost:8000',
    prefix: '/api/v1',   // or false to omit
    default: 'api',      // default guard name
    guards: {
      api:   { strategy: 'cookie' },
      admin: { strategy: 'cookie' },
    },
  },
})

Guards mirror your Python config/auth.py – same names, same strategies. Secrets and model paths stay on the backend only.

All options

| Option | Default | Description | |---|---|---| | url | http://localhost:8000 | Backend base URL | | prefix | /api/v1 | API prefix. Set to false to omit | | credentials | true | Send cookies with every request | | default | 'api' | Default guard used when none is specified | | guards | { api: { strategy: 'cookie' } } | Guard definitions |

Guard options

| Option | Default | Description | |---|---|---| | strategy | — | 'cookie' or 'telegram' | | autoFetch | false | Fetch user on app start | | endpoints.login | /auth/login (default guard) or /{name}/auth/login | Override login path | | endpoints.logout | convention | Override logout path | | endpoints.me | convention | Override me path |

Endpoint convention:

  • Default guard → /auth/login, /auth/logout, /auth/me
  • Other guards → /{name}/auth/login, /{name}/auth/logout, /{name}/auth/me

telegram strategy only generates me – no login or logout.


Auth — useForgeAuth

const { user, isAuthenticated, login, logout, fetchUser } = useForgeAuth()
// or for a specific guard:
const { user, isAuthenticated } = useForgeAuth('admin')

Login page

<script setup lang="ts">
const { login, isAuthenticated } = useForgeAuth()
const { form, errors, serverError, loading, submit } = useForgeForm({
  email: '',
  password: '',
})

if (isAuthenticated.value) navigateTo('/')

async function handleLogin() {
  await submit(async (data) => {
    await login(data)
    navigateTo('/')
  })
}
</script>

<template>
  <form @submit.prevent="handleLogin">
    <input v-model="form.email" type="email" />
    <span v-if="errors.email">{{ errors.email }}</span>
    <input v-model="form.password" type="password" />
    <span v-if="errors.password">{{ errors.password }}</span>
    <p v-if="serverError">{{ serverError }}</p>
    <button :disabled="loading">{{ loading ? 'Signing in…' : 'Sign in' }}</button>
  </form>
</template>

| Return | Description | |---|---| | user | Backend-verified user or null | | isAuthenticated | computed(() => !!user.value) | | login(creds) | POST to login endpoint, then fetchUser | | logout() | POST to logout endpoint, clears user | | fetchUser() | GET /me, hydrates user state |

Auth middleware

// factory (recommended)
definePageMeta({
  middleware: [ForgeAuthMiddleware({ redirect: '/login' })],
})

// specific guard
definePageMeta({
  middleware: [ForgeAuthMiddleware({ guard: 'admin', redirect: '/admin/login' })],
})

// manual
export default defineNuxtRouteMiddleware(() => {
  const { isAuthenticated } = useForgeAuth('admin')
  if (!isAuthenticated.value) return navigateTo('/admin/login')
})

<ForgeAuth> component

Renders slot only when authenticated. Optional guard prop.

<ForgeAuth>
  <UserDashboard />
  <template #fallback>
    <NuxtLink to="/login">Login</NuxtLink>
  </template>
</ForgeAuth>

<ForgeAuth guard="admin">
  <AdminPanel />
  <template #fallback>
    <NuxtLink to="/admin/login">Admin login</NuxtLink>
  </template>
</ForgeAuth>

Telegram Mini App — useForgeTg

Wraps window.Telegram.WebApp. Use for UI helpers independent of auth.

<script setup lang="ts">
const {
  tgUser,           // from initDataUnsafe — display only, not verified
  tgUserId,
  tgUsername,
  tgFullName,
  tgPhotoUrl,
  tgLanguageCode,
  tgIsPremium,
  tgAllowsWriteToPm,
  isWebApp,
  tgReady,          // call WebApp.ready()
  tgHaptic,         // 'light' | 'medium' | 'heavy' | 'rigid' | 'soft'
  tgHapticSuccess,
} = useForgeTg()

onMounted(tgReady)
</script>

Auth (useForgeAuth) with strategy: 'telegram' sends X-Telegram-Init-Data header automatically. The backend verifies it and returns the backend user. useForgeTg is for client-side WebApp UI only.

Local development – paste a real initData string to .env:

VITE_TELEGRAM_INIT_DATA=user=%7B%22id%22%3A...&hash=abc123

<ForgeTg> component

Renders slot only inside a Telegram Mini App.

<ForgeTg>
  <TelegramUI />
  <template #fallback>
    <p>Open in Telegram to continue.</p>
  </template>
</ForgeTg>

RBAC — useForgePermissions

const { can, canAll, hasRole, hasAllRoles, permissions, roles } = useForgePermissions()
// or for a specific guard:
const { can } = useForgePermissions('admin')

The guard's /me response must include permissions and roles arrays.

| Method | Returns true when | |---|---| | can(...perms) | user has any of the permissions | | canAll(...perms) | user has all permissions | | hasRole(...roles) | user has any of the roles | | hasAllRoles(...roles) | user has all roles |

<ForgeCan> / <ForgeRole> components

<ForgeCan perm="edit:posts">
  <button>Edit</button>
</ForgeCan>

<ForgeCan :perm="['edit:posts', 'publish:posts']" :all="true">
  <PublishPanel />
</ForgeCan>

<ForgeRole role="admin">
  <AdminPanel />
  <template #fallback><p>Admins only.</p></template>
</ForgeRole>

Optional guard prop on both components.

RBAC route middleware

definePageMeta({
  middleware: [
    PermissionMiddleware('edit:posts', { redirect: '/403' }),
  ],
})

definePageMeta({
  middleware: [PermissionAllMiddleware(['edit:posts', 'publish:posts'])],
})

definePageMeta({
  middleware: [RoleMiddleware('admin', { redirect: '/403' })],
})

Without redirect – throws 403 Forbidden.


API calls — useForgeApi

const api = useForgeApi()          // default guard
const api = useForgeApi('admin')   // specific guard

const posts = await api.get<Post[]>('/posts', { params: { page: 1 } })
const post  = await api.post<Post>('/posts', { title: 'Hello' })
await api.patch<Post>(`/posts/${id}`, { title: 'Updated' })
await api.put<Post>(`/posts/${id}`, { title: 'Replaced', body: '...' })
await api.delete(`/posts/${id}`)

CRUD — useForgeCrud

Wraps the standard forge-kits Controller convention: GET /url, POST /url, GET /url/{id}, PATCH /url/{id}, DELETE /url/{id}.

const { list, get, create, update, replace, remove, loading } = useForgeCrud<Post>('/posts')

const posts  = await list({ page: 1, search: 'nuxt' })
const post   = await get(42)
const newPost = await create({ title: 'Hello' })
await update(42, { title: 'Updated' })
await replace(42, { title: 'Replaced', body: '...' })
await remove(42)

Per-action loading states:

loading.list.value    // true while list() is in-flight
loading.get.value
loading.create.value
loading.update.value
loading.remove.value

Two independent instances on the same page:

const posts    = useForgeCrud('/posts')
const comments = useForgeCrud('/comments')
// loading states are fully isolated

Optional guard option:

const crud = useForgeCrud('/admin/posts', { guard: 'admin' })

Pagination — useForgePagination

Offset-based pagination. Fetches on mount (SSR-compatible).

<script setup lang="ts">
const { data, meta, loading, error, page, nextPage, prevPage, goToPage } =
  useForgePagination<Post>('/posts', { perPage: 20 })
</script>

<template>
  <ul>
    <li v-for="post in data" :key="post.id">{{ post.title }}</li>
  </ul>
  <button :disabled="page <= 1" @click="prevPage">← Prev</button>
  <span>{{ page }} / {{ meta?.last_page }}</span>
  <button :disabled="!meta || page >= meta.last_page" @click="nextPage">Next →</button>
</template>

Backend envelope: { data: T[], meta: { current_page, per_page, total, last_page, from, to }, links: { prev, next } }


Cursor pagination — useForgeCursorPagination

Cursor-based pagination with loadMore for infinite scroll.

<script setup lang="ts">
const { data, loading, hasMore, fetch, loadMore, reset } =
  useForgeCursorPagination<Post>('/posts', { perPage: 20 })
</script>

<template>
  <ul>
    <li v-for="post in data" :key="post.id">{{ post.title }}</li>
  </ul>
  <button v-if="hasMore" :disabled="loading" @click="loadMore">Load more</button>
</template>

| Return | Description | |---|---| | data | Accumulated items | | nextCursor / prevCursor | Current cursors | | hasMore | true when next_cursor is not null | | fetch(cursor?) | Replace data with fresh page | | loadMore() | Append next page to data | | reset() | Clear data and meta |

Backend envelope: { data: T[], meta: { next_cursor, prev_cursor, per_page } }


Forms — useForgeForm

Maps FastAPI 422 Pydantic errors to field-level errors.

const { form, errors, serverError, loading, clearErrors, reset, submit } = useForgeForm({
  title: '',
  body: '',
})

await submit(async (data) => {
  await api.post('/posts', data)
  navigateTo('/posts')
})

| Return | Description | |---|---| | form | Reactive form data | | errors | Field errors from Pydantic 422 | | serverError | Non-field error from detail string | | loading | true while submit is in-flight | | clearErrors() | Reset all errors | | reset() | Reset form to initial values and clear errors | | submit(fn) | Run fn, catch and map backend errors |


File uploads — useForgeUpload

Real-time progress via XMLHttpRequest.

const { progress, loading, error, result, upload, reset } = useForgeUpload('/files/upload')
// or for a specific guard:
const { upload } = useForgeUpload('/files/upload', 'admin')

const res = await upload(file)
console.log(res.url)

// with extra form fields
await upload(file, { category: 'avatars' })

API reference

Composables

| Composable | Signature | |---|---| | useForgeAuth | (guard?: string) | | useForgePermissions | (guard?: string) | | useForgeApi | (guard?: string) | | useForgeForm | <T>(initial: T) | | useForgeCrud | <T>(url, opts?: { guard? }) | | useForgePagination | <T>(url, opts?: { perPage?, immediate?, guard? }) | | useForgeCursorPagination | <T>(url, opts?: { perPage?, immediate?, guard? }) | | useForgeUpload | (path, guard?: string) | | useForgeTg | () |

Components

| Component | Props | Description | |---|---|---| | <ForgeAuth> | guard?: string | Renders slot if authenticated | | <ForgeTg> | — | Renders slot only inside Telegram Mini App | | <ForgeCan> | perm: string\|string[], all?: boolean, guard?: string | Permission check | | <ForgeRole> | role: string\|string[], all?: boolean, guard?: string | Role check |

All components accept a #fallback slot.

Middleware factories

| Factory | Signature | |---|---| | ForgeAuthMiddleware | (opts?: { guard?, redirect? }) | | PermissionMiddleware | (perm, opts?: { guard?, redirect? }) | | PermissionAllMiddleware | (perm, opts?: { guard?, redirect? }) | | RoleMiddleware | (role, opts?: { guard?, redirect? }) | | RoleAllMiddleware | (role, opts?: { guard?, redirect? }) |