Nuxt Crouton provides comprehensive TypeScript types for type-safe collection management, UI configuration, and data operations. This reference documents all core types, interfaces, and configuration patterns.
The master configuration interface for registering collections in app.config.ts.
type CollectionKind = 'data' | 'content' | 'media'
interface CollectionConfig {
// Basic Collection Info
name?: string
layer?: string
componentName?: string
/** Collection kind: determines behavior and available features. */
kind?: CollectionKind
/** Container type for create/update forms. Defaults to 'slideover'. */
container?: 'slideover' | 'modal' | 'dialog' | 'inline'
apiPath?: string
displayName?: string
// Pagination Settings
defaultPagination?: {
currentPage: number
pageSize: number
sortBy: string
sortDirection: 'asc' | 'desc'
}
// Relationship Declarations
references?: Record<string, string>
// Custom Component Mapping
dependentFieldComponents?: Record<string, string>
// Hierarchy (tree-enabled collections)
hierarchy?: {
enabled: boolean
parentField?: string
pathField?: string
depthField?: string
orderField?: string
}
// Sortable (flat collections with ordering, without hierarchy)
sortable?: {
enabled: boolean
orderField?: string
}
// Admin sidebar navigation
adminNav?: {
enabled?: boolean
icon?: string
label?: string
order?: number
}
// Display config: maps display roles to field names
display?: DisplayConfig
// Runtime field metadata for display components
fields?: RuntimeFieldMeta[]
// When true, collection auto-registers as a page type in crouton-pages
publishable?: boolean
// Extensible for custom properties
[key: string]: any
}
| Property | Type | Description |
|---|---|---|
name | string | Collection name (usually auto-set by generator) |
layer | string | Nuxt layer this collection belongs to |
componentName | string | Name of the form component (e.g., 'ShopProductsForm') |
kind | CollectionKind | Collection kind: 'data', 'content', or 'media'. Determines behavior and available features |
container | 'slideover' | 'modal' | 'dialog' | 'inline' | Container type for create/update forms (default: 'slideover') |
apiPath | string | API endpoint path (defaults to collection name) |
displayName | string | Human-readable display name for the collection |
defaultPagination | object | Default pagination settings for this collection |
references | Record<string, string> | Field-to-collection mappings for automatic cache refresh |
dependentFieldComponents | Record<string, string> | Custom component mappings for dependent fields |
hierarchy | object | Hierarchy config for tree-enabled collections (generated by CLI --hierarchy flag) |
sortable | object | Sortable config for flat collections with drag-drop ordering |
adminNav | object | Controls how the collection appears in the admin sidebar |
display | DisplayConfig | Maps display roles (title, subtitle, image, badge, description) to field names |
fields | RuntimeFieldMeta[] | Lightweight runtime field metadata: name, type, label, area, displayAs |
publishable | boolean | When true, auto-registers as a page type in crouton-pages |
// app.config.ts
export default defineAppConfig({
croutonCollections: {
shopProducts: {
name: 'shopProducts',
layer: 'shop',
componentName: 'ShopProductsForm',
apiPath: 'products',
defaultPagination: {
currentPage: 1,
pageSize: 25,
sortBy: 'name',
sortDirection: 'asc'
}
}
}
})
When a collection has fields that reference other collections, declare them using references. This enables automatic cache refresh when related items are updated.
// app.config.ts
export default defineAppConfig({
croutonCollections: {
bookingsEvents: {
name: 'bookingsEvents',
layer: 'bookings',
componentName: 'BookingsEventsForm',
references: {
location: 'bookingsLocations', // 'location' field references 'bookingsLocations'
host: 'users', // 'host' field references 'users'
category: 'bookingsCategories' // 'category' field references 'bookingsCategories'
}
}
}
})
How references work:
{ location: 'location-123' }collection:bookingsEvents:* (the event collection)collection:bookingsLocations:* (the referenced location collection)For complex field relationships, map field names to custom components:
// app.config.ts
export default defineAppConfig({
croutonCollections: {
bookingsEvents: {
name: 'bookingsEvents',
layer: 'bookings',
componentName: 'BookingsEventsForm',
dependentFieldComponents: {
slots: 'SlotSelect', // Use SlotSelect component for 'slots' field
recurringPattern: 'RecurringPatternEditor' // Custom editor for recurring events
}
}
}
})
The FormDependentFieldLoader component uses this mapping to dynamically load the correct component for each field.
You can extend CollectionConfig with any custom properties your application needs:
export default defineAppConfig({
croutonCollections: {
shopProducts: {
name: 'shopProducts',
layer: 'shop',
componentName: 'ShopProductsForm',
// Custom properties
features: {
enableInventoryTracking: true,
enableVariants: true
},
permissions: {
create: 'product:create',
update: 'product:update',
delete: 'product:delete'
},
metadata: {
icon: 'i-lucide-package',
displayName: 'Products',
singularName: 'Product'
}
}
}
})
Access custom properties via useCollections():
const collections = useCollections()
const config = collections.getConfig('shopProducts')
if (config?.features?.enableInventoryTracking) {
// Show inventory fields
}
Configuration for external collections (e.g., users from auth system, third-party APIs).
interface ExternalCollectionConfig {
name: string
schema: z.ZodSchema
apiPath?: string
fetchStrategy?: 'query' | 'restful'
readonly?: boolean
meta?: {
label?: string
description?: string
}
proxy?: {
enabled: boolean
sourceEndpoint: string
transform: (item: any) => { id: string; title: string; [key: string]: any }
}
}
| Property | Type | Default | Description |
|---|---|---|---|
name | string | required | Collection name (must match app.config.ts key) |
schema | z.ZodSchema | required | Zod schema for validation and types |
apiPath | string | name | API endpoint path |
fetchStrategy | 'query' | 'restful' | 'query' | Fetch method: ?ids= vs /{id} |
readonly | boolean | true | Hide edit/delete buttons in CardMini |
meta | object | {} | Display metadata |
proxy | object | undefined | Proxy configuration for external endpoints |
// composables/useExternalCollections.ts
import { defineExternalCollection } from '@fyit/crouton'
import { z } from 'zod'
const userSchema = z.object({
id: z.string(),
title: z.string(), // Required for CroutonReferenceSelect display
email: z.string().optional(),
avatarUrl: z.string().optional()
})
export const usersConfig = defineExternalCollection({
name: 'users',
schema: userSchema,
apiPath: 'users',
readonly: true, // Users managed by auth system
meta: {
label: 'Team Members',
description: 'Users from the authentication system'
}
})
// app.config.ts
import { usersConfig } from '~/composables/useExternalCollections'
export default defineAppConfig({
croutonCollections: {
users: usersConfig,
// ... other collections
}
})
Proxy external endpoints and transform data to Crouton format:
import { defineExternalCollection } from '@fyit/crouton'
import { z } from 'zod'
const memberSchema = z.object({
id: z.string(),
title: z.string(),
role: z.string(),
joinedAt: z.string()
})
export const membersConfig = defineExternalCollection({
name: 'members',
schema: memberSchema,
proxy: {
enabled: true,
sourceEndpoint: 'members', // → /api/teams/[id]/members
transform: (item) => ({
id: item.userId,
title: `${item.firstName} ${item.lastName}`, // Transform to required 'title' field
role: item.memberRole,
joinedAt: item.createdAt
})
}
})
How proxy works:
/api/teams/[teamId]/memberstransform functionid and title fields (required by Crouton)For APIs using /resource/{id} pattern instead of /resource?ids=:
export const customersConfig = defineExternalCollection({
name: 'customers',
schema: customerSchema,
fetchStrategy: 'restful', // Use /{id} instead of ?ids=
apiPath: 'customers'
})
This changes how single items are fetched:
'query' (default): GET /api/teams/[id]/customers?ids=customer-123'restful': GET /api/teams/[id]/customers/customer-123Basic layout modes for displaying collections.
type LayoutType = 'table' | 'list' | 'grid' | 'tree' | 'kanban' | 'workspace'
| Layout | Description | Use Case |
|---|---|---|
table | Traditional data table | Desktop, detailed data with many columns |
list | Compact list view | Mobile, simple data, quick scanning |
grid | Grid of cards (2-3 columns) | Mobile/tablet, visual content |
tree | Hierarchical tree view | Parent-child relationships, nested data |
kanban | Kanban board view | Status-based workflows, task management |
workspace | Workspace layout | Complex multi-panel interfaces |
Responsive layout configuration with breakpoint support.
interface ResponsiveLayout {
base: LayoutType
sm?: LayoutType // 640px+
md?: LayoutType // 768px+
lg?: LayoutType // 1024px+
xl?: LayoutType // 1280px+
'2xl'?: LayoutType // 1536px+
}
<script setup lang="ts">
const layout = {
base: 'list', // Mobile: list
md: 'grid', // Tablet: grid
lg: 'table' // Desktop: table
}
</script>
<template>
<CroutonCollection
:layout="layout"
:rows="products"
:columns="columns"
collection="shopProducts"
/>
</template>
// Built-in presets
const layoutPresets = {
'responsive': { base: 'list', md: 'grid', lg: 'table' },
'mobile-friendly': { base: 'list', lg: 'table' },
'compact': { base: 'list', xl: 'table' },
'tree-default': { base: 'tree' }
}
<template>
<!-- Use preset by name -->
<CroutonCollection
layout="responsive"
:rows="products"
:columns="columns"
collection="shopProducts"
/>
</template>
<script setup lang="ts">
import type { ResponsiveLayout } from '@fyit/crouton'
// Fine-tuned for different screen sizes
const layout: ResponsiveLayout = {
base: 'list', // Phone (< 640px)
sm: 'list', // Large phone (640px+)
md: 'grid', // Tablet (768px+)
lg: 'grid', // Small laptop (1024px+)
xl: 'table', // Desktop (1280px+)
'2xl': 'table' // Large desktop (1536px+)
}
</script>
<template>
<CroutonCollection
:layout="layout"
:rows="products"
:columns="columns"
collection="shopProducts"
/>
</template>
Column definition for table layouts.
interface TableColumn {
id?: string
accessorKey?: string
header: string | ((props: any) => any)
cell?: (props: any) => any
sortable?: boolean
enableSorting?: boolean
enableHiding?: boolean
}
| Property | Type | Default | Description |
|---|---|---|---|
id | string | accessorKey | Unique column identifier |
accessorKey | string | - | Object property to access (dot notation supported) |
header | string | function | required | Column header text or render function |
cell | function | - | Custom cell renderer (receives { row, value }) |
sortable | boolean | false | Enable sorting for this column |
enableSorting | boolean | sortable | TanStack Table sorting flag |
enableHiding | boolean | true | Allow hiding this column |
const columns: TableColumn[] = [
{
accessorKey: 'name',
header: 'Product Name',
sortable: true
},
{
accessorKey: 'price',
header: 'Price',
sortable: true
},
{
accessorKey: 'inStock',
header: 'In Stock'
}
]
import type { TableColumn } from '@fyit/crouton'
const columns: TableColumn[] = [
{
accessorKey: 'price',
header: 'Price',
cell: ({ value }) => `$${value.toFixed(2)}`
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const status = row.original.status
const color = status === 'active' ? 'green' : 'gray'
return h('span', { class: `text-${color}-600` }, status)
}
}
]
const columns: TableColumn[] = [
{
accessorKey: 'user.name', // Dot notation for nested properties
header: 'User Name'
},
{
accessorKey: 'location.city',
header: 'City'
}
]
const columns: TableColumn[] = [
{
accessorKey: 'price',
header: () => h('div', [
h('span', 'Price '),
h('span', { class: 'text-xs text-gray-500' }, '(USD)')
])
}
]
Props interface for CroutonCollection component.
interface CollectionProps {
// Layout Configuration
layout?: LayoutType | ResponsiveLayout | keyof typeof layoutPresets
card?: 'Card' | 'CardMini' | 'CardSmall' | 'CardTree' | string // Card variant
/** Direct card component (skips name resolution, for stateless mode) */
cardComponent?: any
// Data
rows?: any[]
columns?: TableColumn[]
collection: string
// Pagination
serverPagination?: boolean
paginationData?: PaginationData | null
refreshFn?: () => Promise<void> | null
// UI Options
create?: boolean
/** Hierarchy configuration for tree layouts */
hierarchy?: HierarchyConfig
/** Enable drag-and-drop row reordering (table layout only) */
sortable?: boolean | SortableOptions
/**
* Grid size for grid layout
* - compact: 4 columns, tight spacing
* - comfortable: 3 columns, medium spacing (default)
* - spacious: 2 columns, generous spacing
*/
gridSize?: 'compact' | 'comfortable' | 'spacious'
hideDefaultColumns?: {
select?: boolean
createdAt?: boolean
updatedAt?: boolean
createdBy?: boolean
updatedBy?: boolean
presence?: boolean
actions?: boolean
}
/** Stateless mode: no config lookup, no mutations, just renders data */
stateless?: boolean
/** Show collaboration presence badges per row (requires @fyit/crouton-collab) */
showCollabPresence?: boolean | CollabPresenceConfig
}
The card prop allows specifying which card variant to use:
card="CardSmall" resolves to {Collection}CardSmall (e.g., BookingsCardSmall)card="CardTree" resolves to {Collection}CardTreecard prop uses {Collection}Card with the layout prop passed to ituseCollectionQuery patterns, see Querying Data.<script setup lang="ts">
import type { CollectionProps, TableColumn } from '@fyit/crouton'
// See /fundamentals/querying for query patterns
const { items, pending, refresh } = await useCollectionQuery('shopProducts')
const columns: TableColumn[] = [
{ accessorKey: 'name', header: 'Name', sortable: true },
{ accessorKey: 'price', header: 'Price', sortable: true }
]
const paginationData = computed(() => ({
currentPage: page.value,
pageSize: 25,
totalItems: totalItems.value
}))
</script>
<template>
<CroutonCollection
layout="responsive"
:rows="items"
:columns="columns"
collection="shopProducts"
:server-pagination="true"
:pagination-data="paginationData"
:refresh-fn="refresh"
create
:hide-default-columns="{
createdBy: true,
updatedBy: true
}"
/>
</template>
Pagination metadata for server-side pagination.
interface PaginationData {
currentPage: number
pageSize: number
totalItems: number
totalPages?: number
sortBy?: string
sortDirection?: 'asc' | 'desc'
}
| Property | Type | Required | Description |
|---|---|---|---|
currentPage | number | Yes | Current page number (1-indexed) |
pageSize | number | Yes | Items per page |
totalItems | number | Yes | Total number of items across all pages |
totalPages | number | No | Total pages (auto-calculated if omitted) |
sortBy | string | No | Current sort column |
sortDirection | 'asc' | 'desc' | No | Current sort direction |
useCollectionQuery, see Querying Data.<script setup lang="ts">
// See /fundamentals/querying for query patterns
const { items, data } = await useCollectionQuery('shopProducts')
const paginationData = computed(() => ({
currentPage: page.value,
pageSize: 25,
totalItems: data.value?.pagination?.totalItems || 0
}))
</script>
<template>
<CroutonCollection
:rows="items"
:columns="columns"
collection="shopProducts"
:server-pagination="true"
:pagination-data="paginationData"
/>
</template>
Your API should return data in this format:
// GET /api/teams/[id]/products?page=1&pageSize=25
{
items: [
{ id: '1', name: 'Product 1', price: 29.99 },
// ... more items
],
pagination: {
currentPage: 1,
pageSize: 25,
totalItems: 156,
totalPages: 7,
sortBy: 'name',
sortDirection: 'asc'
}
}
Internal pagination state (used within composables).
interface PaginationState {
currentPage: number
pageSize: number
sortBy: string
sortDirection: 'asc' | 'desc'
totalItems?: number
totalPages?: number
}
Used internally by useCrouton() to manage pagination across multiple collections.
Return type of useCollectionQuery().
interface CollectionQueryReturn<T = any> {
items: ComputedRef<T[]>
data: Ref<any>
refresh: () => Promise<void>
pending: Ref<boolean>
error: Ref<any>
}
import type { ShopProduct } from '~/layers/shop/types/products'
const {
items, // ComputedRef<ShopProduct[]>
pending, // Ref<boolean>
error, // Ref<any>
refresh // () => Promise<void>
} = await useCollectionQuery<ShopProduct>('shopProducts')
Options for useCollectionQuery().
interface CollectionQueryOptions {
query?: ComputedRef<Record<string, any>> | Ref<Record<string, any>>
watch?: boolean
}
| Property | Type | Default | Description |
|---|---|---|---|
query | ComputedRef | Ref | {} | Reactive query parameters |
watch | boolean | true | Auto-refetch when query changes |
const page = ref(1)
const search = ref('')
const { items } = await useCollectionQuery('shopProducts', {
query: computed(() => ({
page: page.value,
search: search.value
})),
watch: true // Auto-refetch when page or search changes
})
Return type of useCollectionMutation().
interface CollectionMutation {
create: (data: any) => Promise<any>
update: (id: string, data: any) => Promise<any>
deleteItems: (ids: string[]) => Promise<void>
}
See Mutation Composables API for usage examples.
Props for custom card components.
interface CardProps {
item: any
layout: 'list' | 'grid' | 'tree' | 'kanban' | 'workspace'
collection: string
pending?: boolean
error?: any
}
<script setup lang="ts">
import type { CardProps } from '@fyit/crouton'
const props = defineProps<CardProps>()
</script>
<template>
<div :class="layout === 'list' ? 'py-2' : 'p-4 border rounded'">
<h3>{{ item.name }}</h3>
<p v-if="layout !== 'list'">{{ item.description }}</p>
</div>
</template>
Props for CroutonTableSearch component.
interface TableSearchProps {
modelValue: string
placeholder?: string
debounceMs?: number
}
Props for CroutonTablePagination component.
interface TablePaginationProps {
page: number
pageCount: number
totalItems: number
loading?: boolean
pageSizes?: number[]
}
Props for CroutonTableActions component.
interface TableActionsProps {
selectedRows: any[]
collection: string
table?: any
onDelete?: (ids: string[]) => void
onColumnVisibilityChange?: (column: string, visible: boolean) => void
}
Nuxt hook emitted after successful mutations. Use for event tracking, analytics, or custom cache invalidation.
// Hook payload type (defined as CroutonMutationEvent in crouton-hooks.d.ts)
interface CroutonMutationEvent {
operation: 'create' | 'update' | 'delete' | 'move' | 'reorder'
collection: string
itemId?: string
itemIds?: string[]
data?: Record<string, unknown>
updates?: Record<string, unknown>
/** The item data before the mutation (for update operations, enables change tracking) */
beforeData?: Record<string, unknown>
result?: unknown
/** Correlation ID for linking related operations and events */
correlationId?: string
/** Timestamp when the mutation was initiated */
timestamp?: number
}
// plugins/crouton-events.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hooks.hook('crouton:mutation', async (payload) => {
const { operation, collection, itemId, data, result } = payload
// Track analytics
if (operation === 'create') {
console.log(`Created ${collection} item:`, itemId)
await $fetch('/api/analytics/track', {
method: 'POST',
body: {
event: `${collection}_created`,
properties: { itemId }
}
})
}
// Custom cache invalidation
if (collection === 'shopProducts' && operation === 'update') {
// Invalidate related caches
await clearNuxtData('product-analytics')
await clearNuxtData('trending-products')
}
})
})
Create operation:
{
operation: 'create',
collection: 'shopProducts',
itemId: 'product-123',
data: {
name: 'New Product',
price: 49.99
},
result: {
id: 'product-123',
name: 'New Product',
price: 49.99,
createdAt: '2025-01-15T10:30:00Z'
},
correlationId: 'abc-123',
timestamp: 1705312200000
}
Update operation:
{
operation: 'update',
collection: 'shopProducts',
itemId: 'product-123',
updates: {
price: 39.99
},
beforeData: {
id: 'product-123',
name: 'New Product',
price: 49.99
},
result: {
id: 'product-123',
name: 'New Product',
price: 39.99,
updatedAt: '2025-01-15T11:00:00Z'
},
correlationId: 'abc-456',
timestamp: 1705314000000
}
Delete operation:
{
operation: 'delete',
collection: 'shopProducts',
itemIds: ['product-123', 'product-456'],
result: undefined,
correlationId: 'abc-789',
timestamp: 1705315800000
}
Move operation (tree/hierarchy):
{
operation: 'move',
collection: 'shopCategories',
itemId: 'category-5',
data: { parentId: 'category-2', order: 3 },
correlationId: 'abc-012',
timestamp: 1705317600000
}
Reorder operation (sortable):
{
operation: 'reorder',
collection: 'shopProducts',
itemIds: ['product-1', 'product-3', 'product-2'],
correlationId: 'abc-345',
timestamp: 1705319400000
}
1. Event Tracking / Analytics
nuxtApp.hooks.hook('crouton:mutation', async ({ operation, collection, itemId }) => {
await $fetch('/api/analytics/events', {
method: 'POST',
body: {
event: `${collection}.${operation}`,
userId: user.value?.id,
timestamp: new Date().toISOString(),
metadata: { itemId }
}
})
})
2. Audit Logging
nuxtApp.hooks.hook('crouton:mutation', async (payload) => {
await $fetch('/api/audit-log', {
method: 'POST',
body: {
action: payload.operation,
resource: payload.collection,
resourceId: payload.itemId,
changes: payload.data,
performedBy: user.value?.id,
timestamp: new Date()
}
})
})
3. Custom Cache Invalidation
nuxtApp.hooks.hook('crouton:mutation', async ({ collection, operation }) => {
// When products change, refresh dashboard stats
if (collection === 'shopProducts') {
await clearNuxtData('dashboard-stats')
await clearNuxtData('inventory-summary')
}
// When orders change, refresh customer data
if (collection === 'shopOrders') {
await clearNuxtData('customer-orders')
await clearNuxtData('revenue-stats')
}
})
4. Webhook Notifications
nuxtApp.hooks.hook('crouton:mutation', async (payload) => {
// Notify external systems of changes
if (payload.operation === 'create' && payload.collection === 'shopOrders') {
await $fetch('/api/webhooks/order-created', {
method: 'POST',
body: {
orderId: payload.itemId,
order: payload.result
}
})
}
})
5. Real-time Updates (WebSocket)
nuxtApp.hooks.hook('crouton:mutation', async (payload) => {
// Broadcast changes to connected clients
websocket.broadcast({
type: 'collection:mutation',
collection: payload.collection,
operation: payload.operation,
itemId: payload.itemId
})
})
Internal state for modal/form management (used by useCrouton()).
interface CroutonState {
id: string
action: CroutonAction
collection: string | null
activeItem: any
items: any[]
loading: LoadingState
isOpen: boolean
containerType: 'slideover' | 'modal' | 'dialog' | 'inline'
}
type CroutonAction = 'create' | 'update' | 'delete' | 'view' | undefined
type LoadingState =
| 'notLoading'
| 'create_send' | 'update_send' | 'delete_send' | 'view_send'
| 'create_open' | 'update_open' | 'delete_open' | 'view_open'
Configuration for proxying external collections.
interface ProxyConfig {
enabled: boolean
sourceEndpoint: string
transform: (item: any) => { id: string; title: string; [key: string]: any }
}
Type for collection configuration registry.
type ConfigsMap = {
[K in CollectionName]?: CollectionConfig
}
const collections = useCollections()
const config = collections.getConfig('shopProducts')
if (!config) {
throw new Error('Collection not found')
}
// Access config properties
const apiPath = config.apiPath || 'products'
const references = config.references || {}
import type { PaginationData } from '@fyit/crouton'
function buildPaginationData(
page: number,
pageSize: number,
total: number
): PaginationData {
return {
currentPage: page,
pageSize,
totalItems: total,
totalPages: Math.ceil(total / pageSize)
}
}
import type { TableColumn } from '@fyit/crouton'
import type { ShopProduct } from '~/layers/shop/types/products'
function defineProductColumns(): TableColumn[] {
return [
{
accessorKey: 'name',
header: 'Product Name',
sortable: true
},
{
accessorKey: 'price',
header: 'Price',
cell: ({ row }: { row: { original: ShopProduct } }) =>
`$${row.original.price.toFixed(2)}`
}
]
}
// 1. Define your data type
import type { ShopProduct } from '~/layers/shop/types/products'
// 2. Register collection config
// app.config.ts
export default defineAppConfig({
croutonCollections: {
shopProducts: {
name: 'shopProducts',
layer: 'shop',
componentName: 'ShopProductsForm'
}
}
})
// 3. Use with type parameter
const { items, pending } = await useCollectionQuery<ShopProduct>('shopProducts')
// items is ComputedRef<ShopProduct[]>
// 1. Define external collection
import { defineExternalCollection } from '@fyit/crouton'
export const membersConfig = defineExternalCollection({
name: 'members',
schema: z.object({
id: z.string(),
title: z.string()
}),
proxy: {
enabled: true,
sourceEndpoint: 'members',
transform: (item) => ({
id: item.userId,
title: `${item.firstName} ${item.lastName}`
})
}
})
// 2. Register in app.config.ts
export default defineAppConfig({
croutonCollections: {
members: membersConfig
}
})
// 3. Use in components
const { items } = await useCollectionQuery('members')
import type { ResponsiveLayout, TableColumn } from '@fyit/crouton'
const layout: ResponsiveLayout = {
base: 'list',
md: 'grid',
lg: 'table'
}
const columns: TableColumn[] = [
{ accessorKey: 'name', header: 'Name' }
]
import type { PaginationData } from '@fyit/crouton'
const page = ref(1)
const { items, data } = await useCollectionQuery('shopProducts', {
query: computed(() => ({ page: page.value }))
})
const paginationData = computed<PaginationData>(() => ({
currentPage: page.value,
pageSize: 25,
totalItems: data.value?.pagination?.totalItems || 0
}))
// Plugin for mutation tracking
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hooks.hook('crouton:mutation', async (payload) => {
// Type-safe payload access
const { operation, collection, itemId, data, result } = payload
console.log(`[${operation}] ${collection}:`, itemId)
})
})
{
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"strict": true,
"types": ["@nuxt/types", "@fyit/crouton"]
}
}
Extend Crouton types for your app:
// types/crouton.d.ts
declare module '@fyit/crouton' {
interface CollectionConfig {
// Add custom properties
permissions?: {
create?: string
update?: string
delete?: string
}
metadata?: {
icon?: string
displayName?: string
}
}
}
Always run type checking after making changes:
# Type check your application
npx nuxt typecheck
# Watch mode for development
npx nuxt typecheck --watch