# AGENTS.md - TypeScript/Vue/Nuxt This document provides guidance for AI assistants working with TypeScript, Vue 3, and Nuxt 3 code in this repository. ## Project Context This repository contains TypeScript applications using Vue 3 and Nuxt 3 for building modern web applications, dashboards, and user interfaces. Code should follow TypeScript best practices, be fully typed, maintainable, and production-ready. ## Core Principles ### 1. TypeScript First (Mandatory) - **ALL code must use TypeScript** - No `.js` files in src/ - Use strict mode (`"strict": true` in tsconfig.json) - Prefer explicit types over inference when it improves clarity - Use type guards and discriminated unions - Avoid `any` - use `unknown` when type is truly unknown ### 2. Vue 3 Composition API - **Always use Composition API** with ` ``` ### Composable with TypeScript ````typescript // composables/useAzureResources.ts import { ref, computed, type Ref, type ComputedRef } from "vue"; export interface ResourceGroup { id: string; name: string; location: string; tags: Record; createdAt: string; updatedAt: string; } export interface UseAzureResourcesOptions { subscriptionId: string; autoLoad?: boolean; } export interface UseAzureResourcesReturn { resourceGroups: Ref; loading: Ref; error: Ref; total: ComputedRef; loadResourceGroups: (filters?: ResourceFilters) => Promise; getResourceGroup: (id: string) => Promise; createResourceGroup: ( data: CreateResourceGroupData, ) => Promise; deleteResourceGroup: (id: string) => Promise; } export interface ResourceFilters { tags?: Record; location?: string; } export interface CreateResourceGroupData { name: string; location: string; tags?: Record; } /** * Composable for managing Azure resource groups. * * @example * ```ts * const { resourceGroups, loading, loadResourceGroups } = useAzureResources({ * subscriptionId: 'sub-123', * autoLoad: true, * }) * ``` */ export function useAzureResources( options: UseAzureResourcesOptions, ): UseAzureResourcesReturn { const { subscriptionId, autoLoad = false } = options; // State const resourceGroups = ref([]); const loading = ref(false); const error = ref(null); // Computed const total = computed(() => resourceGroups.value.length); // Methods async function loadResourceGroups(filters?: ResourceFilters): Promise { loading.value = true; error.value = null; try { const params = new URLSearchParams({ subscription_id: subscriptionId, }); if (filters?.tags) { params.append("tags", JSON.stringify(filters.tags)); } if (filters?.location) { params.append("location", filters.location); } const response = await fetch(`/api/resources?${params}`); if (!response.ok) { throw new Error( `Failed to load resource groups: ${response.statusText}`, ); } const data = await response.json(); resourceGroups.value = data.resource_groups; } catch (e) { error.value = e instanceof Error ? e : new Error("Unknown error"); console.error("Failed to load resource groups:", e); throw e; } finally { loading.value = false; } } async function getResourceGroup( id: string, ): Promise { loading.value = true; error.value = null; try { const response = await fetch(`/api/resources/${id}`); if (!response.ok) { if (response.status === 404) { return undefined; } throw new Error(`Failed to get resource group: ${response.statusText}`); } return await response.json(); } catch (e) { error.value = e instanceof Error ? e : new Error("Unknown error"); console.error("Failed to get resource group:", e); throw e; } finally { loading.value = false; } } async function createResourceGroup( data: CreateResourceGroupData, ): Promise { loading.value = true; error.value = null; try { const response = await fetch("/api/resources", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ subscription_id: subscriptionId, ...data, }), }); if (!response.ok) { throw new Error( `Failed to create resource group: ${response.statusText}`, ); } const resourceGroup: ResourceGroup = await response.json(); resourceGroups.value.push(resourceGroup); return resourceGroup; } catch (e) { error.value = e instanceof Error ? e : new Error("Unknown error"); console.error("Failed to create resource group:", e); throw e; } finally { loading.value = false; } } async function deleteResourceGroup(id: string): Promise { loading.value = true; error.value = null; try { const response = await fetch(`/api/resources/${id}`, { method: "DELETE", }); if (!response.ok) { throw new Error( `Failed to delete resource group: ${response.statusText}`, ); } resourceGroups.value = resourceGroups.value.filter((rg) => rg.id !== id); } catch (e) { error.value = e instanceof Error ? e : new Error("Unknown error"); console.error("Failed to delete resource group:", e); throw e; } finally { loading.value = false; } } // Auto-load if requested if (autoLoad) { loadResourceGroups(); } return { resourceGroups, loading, error, total, loadResourceGroups, getResourceGroup, createResourceGroup, deleteResourceGroup, }; } ```` ### Pinia Store with TypeScript ```typescript // stores/auth.ts import { defineStore } from "pinia"; import { ref, computed, type Ref, type ComputedRef } from "vue"; export interface User { id: string; email: string; name: string; roles: string[]; } export interface AuthState { user: User | null; token: string | null; refreshToken: string | null; } export interface LoginCredentials { email: string; password: string; } export interface AuthStore { // State user: Ref; token: Ref; refreshToken: Ref; // Getters isAuthenticated: ComputedRef; hasRole: (role: string) => boolean; // Actions login: (credentials: LoginCredentials) => Promise; logout: () => Promise; refreshAccessToken: () => Promise; loadUser: () => Promise; } /** * Authentication store using Pinia. * Handles user authentication, token management, and role-based access. */ export const useAuthStore = defineStore("auth", (): AuthStore => { // State const user = ref(null); const token = ref(null); const refreshToken = ref(null); // Getters const isAuthenticated = computed(() => { return token.value !== null && user.value !== null; }); function hasRole(role: string): boolean { return user.value?.roles.includes(role) ?? false; } // Actions async function login(credentials: LoginCredentials): Promise { try { const response = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(credentials), }); if (!response.ok) { throw new Error("Login failed"); } const data = await response.json(); token.value = data.access_token; refreshToken.value = data.refresh_token; user.value = data.user; // Store tokens localStorage.setItem("token", data.access_token); localStorage.setItem("refreshToken", data.refresh_token); } catch (error) { console.error("Login error:", error); throw error; } } async function logout(): Promise { try { if (token.value) { await fetch("/api/auth/logout", { method: "POST", headers: { Authorization: `Bearer ${token.value}`, }, }); } } catch (error) { console.error("Logout error:", error); } finally { // Clear state regardless of API call result user.value = null; token.value = null; refreshToken.value = null; localStorage.removeItem("token"); localStorage.removeItem("refreshToken"); } } async function refreshAccessToken(): Promise { if (!refreshToken.value) { throw new Error("No refresh token available"); } try { const response = await fetch("/api/auth/refresh", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh_token: refreshToken.value }), }); if (!response.ok) { throw new Error("Token refresh failed"); } const data = await response.json(); token.value = data.access_token; localStorage.setItem("token", data.access_token); } catch (error) { console.error("Token refresh error:", error); // Clear auth state on refresh failure await logout(); throw error; } } async function loadUser(): Promise { const storedToken = localStorage.getItem("token"); if (!storedToken) { return; } token.value = storedToken; refreshToken.value = localStorage.getItem("refreshToken"); try { const response = await fetch("/api/auth/me", { headers: { Authorization: `Bearer ${storedToken}`, }, }); if (!response.ok) { throw new Error("Failed to load user"); } user.value = await response.json(); } catch (error) { console.error("Load user error:", error); await logout(); } } return { // State user, token, refreshToken, // Getters isAuthenticated, hasRole, // Actions login, logout, refreshAccessToken, loadUser, }; }); ``` ### Type Definitions ```typescript // types/models.ts /** * Azure resource group representation. */ export interface ResourceGroup { id: string; name: string; location: string; tags: Record; createdAt: string; updatedAt: string; managedBy?: string; provisioningState: ProvisioningState; } /** * Azure resource provisioning states. */ export type ProvisioningState = | "Creating" | "Running" | "Updating" | "Deleting" | "Failed" | "Succeeded"; /** * Azure storage account representation. */ export interface StorageAccount { id: string; name: string; location: string; resourceGroup: string; sku: StorageSku; kind: StorageKind; tags: Record; primaryEndpoints: StorageEndpoints; creationTime: string; } /** * Storage SKU names. */ export type StorageSku = | "Standard_LRS" | "Standard_GRS" | "Standard_RAGRS" | "Standard_ZRS" | "Premium_LRS"; /** * Storage account kinds. */ export type StorageKind = | "Storage" | "StorageV2" | "BlobStorage" | "FileStorage" | "BlockBlobStorage"; /** * Storage account endpoints. */ export interface StorageEndpoints { blob?: string; file?: string; queue?: string; table?: string; dfs?: string; web?: string; } /** * API response wrapper. */ export interface ApiResponse { data: T; message?: string; error?: string; } /** * Paginated API response. */ export interface PaginatedResponse { items: T[]; total: number; page: number; pageSize: number; hasMore: boolean; nextPageToken?: string; } /** * API error response. */ export interface ApiError { code: string; message: string; details?: Record; } ``` ```typescript // types/api.ts /** * HTTP methods. */ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; /** * API request configuration. */ export interface ApiRequestConfig { method: HttpMethod; url: string; headers?: Record; params?: Record; data?: unknown; timeout?: number; } /** * API client interface. */ export interface ApiClient { get(url: string, config?: Partial): Promise; post( url: string, data?: unknown, config?: Partial, ): Promise; put( url: string, data?: unknown, config?: Partial, ): Promise; patch( url: string, data?: unknown, config?: Partial, ): Promise; delete(url: string, config?: Partial): Promise; } /** * Type guard to check if error is an ApiError. */ export function isApiError(error: unknown): error is ApiError { return ( typeof error === "object" && error !== null && "code" in error && "message" in error ); } ``` ### Nuxt 3 Page with TypeScript ```vue ``` ### Nuxt 3 API Route with TypeScript ```typescript // server/api/resources/[id].ts import type { H3Event } from "h3"; import type { ResourceGroup } from "@/types/models"; /** * GET /api/resources/:id * Get a specific resource group by ID. */ export default defineEventHandler( async (event: H3Event): Promise => { const id = getRouterParam(event, "id"); if (!id) { throw createError({ statusCode: 400, statusMessage: "Resource ID is required", }); } try { // Get resource from Azure const resource = await getResourceGroup(id); if (!resource) { throw createError({ statusCode: 404, statusMessage: "Resource group not found", }); } return resource; } catch (error) { console.error("Failed to get resource group:", error); if (error instanceof H3Error) { throw error; } throw createError({ statusCode: 500, statusMessage: "Failed to retrieve resource group", }); } }, ); /** * PUT /api/resources/:id * Update a resource group. */ export async function put(event: H3Event): Promise { const id = getRouterParam(event, "id"); if (!id) { throw createError({ statusCode: 400, statusMessage: "Resource ID is required", }); } try { const body = await readBody>(event); // Validate body if (!body.name || !body.location) { throw createError({ statusCode: 400, statusMessage: "Name and location are required", }); } // Update resource in Azure const updated = await updateResourceGroup(id, body); return updated; } catch (error) { console.error("Failed to update resource group:", error); if (error instanceof H3Error) { throw error; } throw createError({ statusCode: 500, statusMessage: "Failed to update resource group", }); } } /** * DELETE /api/resources/:id * Delete a resource group. */ export async function del(event: H3Event): Promise<{ success: boolean }> { const id = getRouterParam(event, "id"); if (!id) { throw createError({ statusCode: 400, statusMessage: "Resource ID is required", }); } try { await deleteResourceGroup(id); return { success: true }; } catch (error) { console.error("Failed to delete resource group:", error); if (error instanceof H3Error) { throw error; } throw createError({ statusCode: 500, statusMessage: "Failed to delete resource group", }); } } // Helper functions (would be in separate modules) async function getResourceGroup(id: string): Promise { // Implementation here return null; } async function updateResourceGroup( id: string, data: Partial, ): Promise { // Implementation here return {} as ResourceGroup; } async function deleteResourceGroup(id: string): Promise { // Implementation here } ``` ## Testing Patterns ### Component Testing with Vitest ```typescript // tests/unit/components/ResourceGroupDetail.spec.ts import { describe, it, expect, vi, beforeEach } from "vitest"; import { mount, VueWrapper } from "@vue/test-utils"; import ResourceGroupDetail from "@/components/ResourceGroupDetail.vue"; import type { ResourceGroup } from "@/types/models"; describe("ResourceGroupDetail", () => { let wrapper: VueWrapper; const mockResourceGroup: ResourceGroup = { id: "/subscriptions/test/resourceGroups/rg1", name: "rg1", location: "eastus", tags: { env: "test" }, createdAt: "2024-01-01T00:00:00Z", updatedAt: "2024-01-01T00:00:00Z", provisioningState: "Succeeded", }; beforeEach(() => { wrapper = mount(ResourceGroupDetail, { props: { resourceGroupId: "rg1", mode: "view", }, }); }); it("renders resource group name", () => { expect(wrapper.text()).toContain(mockResourceGroup.name); }); it("emits update event when save button clicked", async () => { await wrapper.setProps({ mode: "edit" }); const button = wrapper.find('button[type="submit"]'); await button.trigger("click"); expect(wrapper.emitted("update")).toBeTruthy(); }); it("emits delete event when delete button clicked", async () => { await wrapper.setProps({ mode: "edit" }); const button = wrapper.find("button.danger"); await button.trigger("click"); expect(wrapper.emitted("delete")).toBeTruthy(); expect(wrapper.emitted("delete")?.[0]).toEqual(["rg1"]); }); it("disables inputs in view mode", () => { const inputs = wrapper.findAll("input"); inputs.forEach((input) => { expect(input.attributes("disabled")).toBeDefined(); }); }); it("enables inputs in edit mode", async () => { await wrapper.setProps({ mode: "edit" }); const inputs = wrapper.findAll("input"); inputs.forEach((input) => { expect(input.attributes("disabled")).toBeUndefined(); }); }); }); ``` ### Composable Testing ```typescript // tests/unit/composables/useAzureResources.spec.ts import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { useAzureResources } from "@/composables/useAzureResources"; import type { ResourceGroup } from "@/types/models"; // Mock fetch global.fetch = vi.fn(); describe("useAzureResources", () => { const mockResourceGroups: ResourceGroup[] = [ { id: "1", name: "rg1", location: "eastus", tags: {}, createdAt: "2024-01-01T00:00:00Z", updatedAt: "2024-01-01T00:00:00Z", provisioningState: "Succeeded", }, ]; beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { vi.restoreAllMocks(); }); it("loads resource groups successfully", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => ({ resource_groups: mockResourceGroups }), } as Response); const { resourceGroups, loading, loadResourceGroups } = useAzureResources({ subscriptionId: "test-sub", }); expect(resourceGroups.value).toEqual([]); expect(loading.value).toBe(false); await loadResourceGroups(); expect(loading.value).toBe(false); expect(resourceGroups.value).toEqual(mockResourceGroups); expect(fetch).toHaveBeenCalledWith( expect.stringContaining("/api/resources"), ); }); it("handles load error", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: false, statusText: "Internal Server Error", } as Response); const { error, loadResourceGroups } = useAzureResources({ subscriptionId: "test-sub", }); await expect(loadResourceGroups()).rejects.toThrow(); expect(error.value).not.toBeNull(); }); it("computes total correctly", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => ({ resource_groups: mockResourceGroups }), } as Response); const { total, loadResourceGroups } = useAzureResources({ subscriptionId: "test-sub", }); expect(total.value).toBe(0); await loadResourceGroups(); expect(total.value).toBe(1); }); it("creates resource group successfully", async () => { const newResourceGroup: ResourceGroup = { id: "2", name: "rg2", location: "westus", tags: { env: "prod" }, createdAt: "2024-01-02T00:00:00Z", updatedAt: "2024-01-02T00:00:00Z", provisioningState: "Succeeded", }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => newResourceGroup, } as Response); const { resourceGroups, createResourceGroup } = useAzureResources({ subscriptionId: "test-sub", }); const result = await createResourceGroup({ name: "rg2", location: "westus", tags: { env: "prod" }, }); expect(result).toEqual(newResourceGroup); expect(resourceGroups.value).toContainEqual(newResourceGroup); }); }); ``` ### Store Testing ```typescript // tests/unit/stores/auth.spec.ts import { describe, it, expect, vi, beforeEach } from "vitest"; import { setActivePinia, createPinia } from "pinia"; import { useAuthStore } from "@/stores/auth"; // Mock fetch global.fetch = vi.fn(); describe("Auth Store", () => { beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks(); localStorage.clear(); }); it("initializes with null user", () => { const store = useAuthStore(); expect(store.user).toBeNull(); expect(store.token).toBeNull(); expect(store.isAuthenticated).toBe(false); }); it("logs in successfully", async () => { const mockUser = { id: "1", email: "test@example.com", name: "Test User", roles: ["user"], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => ({ access_token: "token123", refresh_token: "refresh123", user: mockUser, }), } as Response); const store = useAuthStore(); await store.login({ email: "test@example.com", password: "password", }); expect(store.user).toEqual(mockUser); expect(store.token).toBe("token123"); expect(store.isAuthenticated).toBe(true); expect(localStorage.getItem("token")).toBe("token123"); }); it("logs out successfully", async () => { const store = useAuthStore(); // Set up authenticated state store.user = { id: "1", email: "test@example.com", name: "Test User", roles: ["user"], }; store.token = "token123"; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, } as Response); await store.logout(); expect(store.user).toBeNull(); expect(store.token).toBeNull(); expect(store.isAuthenticated).toBe(false); expect(localStorage.getItem("token")).toBeNull(); }); it("checks user roles", () => { const store = useAuthStore(); store.user = { id: "1", email: "test@example.com", name: "Test User", roles: ["user", "admin"], }; expect(store.hasRole("user")).toBe(true); expect(store.hasRole("admin")).toBe(true); expect(store.hasRole("superadmin")).toBe(false); }); }); ``` ## Vue Router with TypeScript ```typescript // router/index.ts import { createRouter, createWebHistory, type RouteRecordRaw, type NavigationGuardNext, type RouteLocationNormalized, } from "vue-router"; import { useAuthStore } from "@/stores/auth"; // Type-safe route names export enum RouteNames { Home = "home", Dashboard = "dashboard", Resources = "resources", ResourceDetail = "resource-detail", Login = "login", NotFound = "not-found", } // Extend route meta types declare module "vue-router" { interface RouteMeta { requiresAuth?: boolean; roles?: string[]; layout?: "default" | "admin" | "auth"; title?: string; } } const routes: RouteRecordRaw[] = [ { path: "/", name: RouteNames.Home, component: () => import("@/views/Home.vue"), meta: { title: "Home", }, }, { path: "/dashboard", name: RouteNames.Dashboard, component: () => import("@/views/Dashboard.vue"), meta: { requiresAuth: true, title: "Dashboard", }, }, { path: "/resources", name: RouteNames.Resources, component: () => import("@/views/Resources.vue"), meta: { requiresAuth: true, title: "Resources", }, }, { path: "/resources/:id", name: RouteNames.ResourceDetail, component: () => import("@/views/ResourceDetail.vue"), meta: { requiresAuth: true, title: "Resource Details", }, props: true, }, { path: "/login", name: RouteNames.Login, component: () => import("@/views/Login.vue"), meta: { layout: "auth", title: "Login", }, }, { path: "/:pathMatch(.*)*", name: RouteNames.NotFound, component: () => import("@/views/NotFound.vue"), meta: { title: "Not Found", }, }, ]; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes, }); // Navigation guard with TypeScript router.beforeEach( async ( to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, ): Promise => { const authStore = useAuthStore(); // Set page title document.title = to.meta.title ? `${to.meta.title} - My App` : "My App"; // Check authentication if (to.meta.requiresAuth && !authStore.isAuthenticated) { next({ name: RouteNames.Login, query: { redirect: to.fullPath } }); return; } // Check roles if ( to.meta.roles && !to.meta.roles.some((role) => authStore.hasRole(role)) ) { next({ name: RouteNames.Home }); return; } next(); }, ); export default router; ``` ## Nuxt 3 Specific Patterns ### Middleware with TypeScript ```typescript // middleware/auth.ts export default defineNuxtRouteMiddleware((to, from) => { const authStore = useAuthStore(); // Check if user is authenticated if (!authStore.isAuthenticated) { // Redirect to login with return URL return navigateTo({ path: "/login", query: { redirect: to.fullPath }, }); } // Check role-based access if specified const requiredRoles = to.meta.roles as string[] | undefined; if (requiredRoles) { const hasAccess = requiredRoles.some((role) => authStore.hasRole(role)); if (!hasAccess) { return abortNavigation("Insufficient permissions"); } } }); ``` ### Plugin with TypeScript ```typescript // plugins/api.ts import type { $Fetch } from "nitropack"; interface ApiInstance { $api: $Fetch; } export default defineNuxtPlugin((nuxtApp): ApiInstance => { const config = useRuntimeConfig(); const authStore = useAuthStore(); // Create custom fetch instance const $api = $fetch.create({ baseURL: config.public.apiBase as string, onRequest({ options }) { // Add auth token to all requests const token = authStore.token; if (token) { options.headers = { ...options.headers, Authorization: `Bearer ${token}`, }; } }, onResponseError({ response }) { // Handle 401 errors if (response.status === 401) { authStore.logout(); navigateTo("/login"); } }, }); return { provide: { api: $api, }, }; }); // Augment Nuxt types declare module "#app" { interface NuxtApp { $api: $Fetch; } } ``` ### Nuxt Config with TypeScript ```typescript // nuxt.config.ts export default defineNuxtConfig({ devtools: { enabled: true }, modules: ["@nuxt/ui", "@pinia/nuxt", "@vueuse/nuxt"], typescript: { strict: true, typeCheck: true, }, runtimeConfig: { // Private keys (server-only) azureClientSecret: process.env.AZURE_CLIENT_SECRET, // Public keys (client-accessible) public: { apiBase: process.env.API_BASE_URL || "http://localhost:3000/api", azureSubscriptionId: process.env.AZURE_SUBSCRIPTION_ID, azureTenantId: process.env.AZURE_TENANT_ID, }, }, app: { head: { title: "My App", meta: [ { charset: "utf-8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }, ], link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }], }, }, css: ["~/assets/styles/main.css"], vite: { server: { hmr: { protocol: "ws", host: "localhost", }, }, }, }); ``` ## Advanced TypeScript Patterns ### Utility Types ```typescript // types/utils.ts /** * Make all properties optional recursively. */ export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; }; /** * Make all properties required recursively. */ export type DeepRequired = { [P in keyof T]-?: T[P] extends object ? DeepRequired : T[P]; }; /** * Make all properties readonly recursively. */ export type DeepReadonly = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P]; }; /** * Extract promise type. */ export type Awaited = T extends Promise ? U : T; /** * Extract function return type. */ export type ReturnTypeOf = T extends (...args: any[]) => infer R ? R : never; /** * Extract function parameters. */ export type ParametersOf = T extends (...args: infer P) => any ? P : never; /** * Omit properties from union types. */ export type DistributiveOmit = T extends any ? Omit : never; /** * Pick properties from union types. */ export type DistributivePick = T extends any ? Pick> : never; /** * Make specific properties optional. */ export type PartialBy = Omit & Partial>; /** * Make specific properties required. */ export type RequiredBy = Omit & Required>; ``` ### Type Guards ```typescript // types/guards.ts /** * Type guard to check if value is defined. */ export function isDefined(value: T | undefined | null): value is T { return value !== undefined && value !== null; } /** * Type guard to check if value is a string. */ export function isString(value: unknown): value is string { return typeof value === "string"; } /** * Type guard to check if value is a number. */ export function isNumber(value: unknown): value is number { return typeof value === "number" && !isNaN(value); } /** * Type guard to check if value is an array. */ export function isArray(value: unknown): value is T[] { return Array.isArray(value); } /** * Type guard to check if value is an object. */ export function isObject(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } /** * Type guard to check if error is an Error instance. */ export function isError(error: unknown): error is Error { return error instanceof Error; } /** * Type guard to check if value has a specific property. */ export function hasProperty( obj: unknown, key: K, ): obj is Record { return isObject(obj) && key in obj; } /** * Type guard for discriminated unions. */ export function isResourceGroup(resource: unknown): resource is ResourceGroup { return ( isObject(resource) && hasProperty(resource, "id") && hasProperty(resource, "name") && hasProperty(resource, "location") ); } ``` ### Generic Components ```vue ``` ## Build Configuration ### vite.config.ts ```typescript import { fileURLToPath, URL } from "node:url"; import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import vueDevTools from "vite-plugin-vue-devtools"; export default defineConfig({ plugins: [vue(), vueDevTools()], resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)), }, }, server: { port: 3000, strictPort: false, host: true, proxy: { "/api": { target: "http://localhost:8000", changeOrigin: true, secure: false, }, }, }, build: { target: "esnext", minify: "terser", sourcemap: true, rollupOptions: { output: { manualChunks: { "vue-vendor": ["vue", "vue-router", "pinia"], "azure-sdk": [ "@azure/identity", "@azure/storage-blob", "@azure/arm-resources", ], }, }, }, }, optimizeDeps: { include: ["vue", "vue-router", "pinia"], }, }); ``` ### ESLint Configuration ```typescript // eslint.config.js import js from "@eslint/js"; import typescript from "@typescript-eslint/eslint-plugin"; import typescriptParser from "@typescript-eslint/parser"; import vue from "eslint-plugin-vue"; import vueParser from "vue-eslint-parser"; export default [ js.configs.recommended, { files: ["**/*.ts", "**/*.tsx", "**/*.vue"], languageOptions: { parser: vueParser, parserOptions: { parser: typescriptParser, ecmaVersion: "latest", sourceType: "module", }, }, plugins: { "@typescript-eslint": typescript, vue, }, rules: { ...typescript.configs.strict.rules, ...vue.configs["vue3-recommended"].rules, // TypeScript specific "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/explicit-function-return-type": "warn", "@typescript-eslint/no-unused-vars": [ "error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", }, ], // Vue specific "vue/multi-word-component-names": "off", "vue/require-default-prop": "off", "vue/component-api-style": ["error", ["script-setup"]], "vue/block-lang": [ "error", { script: { lang: "ts" }, }, ], }, }, ]; ``` ## package.json Scripts ```json { "name": "vue-nuxt-app", "version": "1.0.0", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vue-tsc && vite build", "preview": "vite preview", "test": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "format": "prettier --write src/", "type-check": "vue-tsc --noEmit" }, "dependencies": { "@azure/arm-resources": "^5.0.0", "@azure/identity": "^4.0.0", "@azure/storage-blob": "^12.0.0", "pinia": "^2.1.0", "vue": "^3.4.0", "vue-router": "^4.2.0" }, "devDependencies": { "@tsconfig/node20": "^20.1.0", "@types/node": "^20.11.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", "@vue/test-utils": "^2.4.0", "@vue/tsconfig": "^0.5.0", "eslint": "^8.56.0", "eslint-plugin-vue": "^9.20.0", "jsdom": "^24.0.0", "prettier": "^3.2.0", "typescript": "^5.3.0", "vite": "^5.0.0", "vite-plugin-vue-devtools": "^7.0.0", "vitest": "^1.2.0", "vue-tsc": "^1.8.0" } } ``` ## AI Assistant Guidelines ### When Reviewing Code 1. **Check TypeScript Usage** - Verify all files use `.ts` or `.vue` with `lang="ts"` - Check for `any` types - should be minimal - Ensure proper generic types usage - Verify interface definitions are complete - Check for proper type guards 2. **Check Vue Patterns** - Verify `