npm package: @quvel-kit/core
Description: Core utilities for Quvel UI
Latest version: 1.3.23

# @quvel-kit/core

Core utilities and infrastructure for Quasar + Laravel applications.

## Features

### Service Container System
- **Isomorphic per-request container** with Initialize → Boot → Register lifecycle
- **SSR-aware services** that can boot with request context
- **Dependency injection** with type-safe service resolution
- **Service registration** and lifecycle management

### Core Services

All services are included and ready to use out of the box:
- **LogService** - Structured logging with trace correlation
- **ApiService** - Axios-based HTTP client with automatic trace headers
- **I18nService** - Internationalization with module support
- **ValidationService** - Zod-based validation with i18n integration
- **WebSocketService** - Laravel Echo + Pusher WebSocket support
- **TaskService** - Async task management with loading/error handling
- **ThemeService** - Light/dark mode management

**Configuration** is managed directly by the ServiceContainer as a first-class property, not as a service. All services receive `AppConfig` in their constructor.

### Module System
- Module-based architecture for routes, i18n, services, and build config
- Resource path helpers for module assets
- Dynamic service registration per module

### Utilities
- **Asset loading** - Dynamic CSS/JS injection for client and SSR
- **Theme management** - Light/dark mode with Quasar and Tailwind
- **Script loading** - Dynamic external script management
- **Error handling** - Laravel validation error processing
- **Config utilities** - Environment-based configuration merging

### Composables
- **useQuvel** - Access Quvel service container in components
- **useClient** - Client-side detection for SSR-aware components
- **useWindowEvent** - Safe window event listener management
- **useScript** - Dynamic script loading with state tracking
- **useMetaConfig** - Page meta tags with SEO optimization
- **useWebSockets** - WebSocket channel subscription
- **useScopedService** - Component-scoped service instances
- **useRecaptcha** - Google reCAPTCHA integration
- **useXsrf** - XSRF token management
- **useQueryMessageHandler** - URL query parameter message handling
- **useUrlQueryHandler** - Generic URL query parameter processing

## Installation

```bash
npm install @quvel-kit/core
# or
yarn add @quvel-kit/core
```

## Configuration

### Environment Variables

See [.env.example](./.env.example) for all available configuration options.

#### Required Variables

```bash
# Application name
VITE_APP_NAME="My App"

# Backend API URL (Laravel)
VITE_API_URL=http://localhost:8000

# Frontend app URL (Quasar)
VITE_APP_URL=http://localhost:9000
```

#### Optional Variables

```bash
# Application environment
VITE_APP_ENV=local

# Debug mode
VITE_DEBUG=true

# Application timezone
VITE_TIMEZONE=UTC

# Application locale
VITE_LOCALE=en

# Fallback locale
VITE_FALLBACK_LOCALE=en

# Session cookie name (must match Laravel)
VITE_SESSION_NAME=quvel_session

# Internal API URL for SSR server to backend communication
# Can be an internal network address for better performance
# Defaults to VITE_API_URL if not set
VITE_INTERNAL_API_URL=http://api-internal:8000

# Capacitor custom scheme for mobile apps
VITE_CAPACITOR_SCHEME=myapp
```

### AppConfig Structure

The configuration follows a structured format matching the backend:

```typescript
interface AppConfig {
  app: {
    name: string;              // Application name
    url: string;               // Backend API URL
    env?: string;              // Environment (local, development, production)
    debug?: boolean;           // Debug mode
    timezone?: string;         // Timezone (UTC, America/New_York, etc.)
    locale?: string;           // Default locale (en, es, fr, etc.)
    fallback_locale?: string;  // Fallback locale
  };
  frontend: {
    url: string;               // Frontend app URL
    custom_scheme?: string;    // Capacitor custom scheme
  };
  assets?: AppAssets;          // Dynamic CSS/JS injection
  meta?: AppMeta;              // Page meta configuration
  headers?: HeadersConfig;     // HTTP headers customization
  api?: AppApiConfig;          // API configuration (SSR key, etc.)
  broadcasting?: BroadcastingConfig; // WebSocket/Pusher configuration
  session?: SessionConfig;     // Session cookie configuration
  i18nCookie?: string;         // I18n locale cookie name
  userFactory?: UserFactory;   // Custom user model factory
  trace?: TraceInfo;           // Request trace context (SSR-injected)
  [key: string]: any;          // Extensible for custom config
}
```

**Note:** The `VITE_INTERNAL_API_URL` environment variable is used internally by `@quvel-kit/ssr`'s SSRApiService and is not part of the `AppConfig` structure exposed to your application. It configures how the SSR server communicates with the backend.

## Usage

### Service Container

```typescript
import { createContainer, LogService } from '@quvel-kit/core';

// Create container with services
const container = createContainer(
  undefined, // SSR context (optional)
  new Map([
    ['LogService', LogService],
  ])
);

// Access config (managed by container)
const config = container.config;

// Access services
const log = container.get(LogService);
```

### Using Services in Components

```typescript
import { useQuvel } from '@quvel-kit/core';

export default defineComponent({
  setup() {
    const { config, log, api, i18n, task } = useQuvel();

    const fetchData = async () => {
      const data = await api.get('/users');
      log.info('Fetched users', { count: data.length });
      return data;
    };

    return { fetchData };
  }
});
```

### TaskService Example

```typescript
import { useQuvel } from '@quvel-kit/core';

export default defineComponent({
  setup() {
    const { task, api } = useQuvel();

    const loginTask = task({
      task: (credentials) => api.post('/login', credentials),
      showLoading: true,
      showNotification: {
        success: 'Login successful!',
        error: true, // Use default error message
      },
      handleLaravelError: true, // Auto-handle Laravel validation errors
    });

    const login = async () => {
      const result = await loginTask.run();
      if (result) {
        router.push('/dashboard');
      }
    };

    return { login, loginTask };
  }
});
```

### Optional API Debug Interceptors

The core package provides optional debugging interceptors that can be enabled in development. Create a boot file to enable them:

```typescript
// src/boot/api-debug.ts
import { boot } from 'quasar/wrappers';
import { useQuvel } from '@quvel-kit/core';
import { createDebugInterceptors } from '@quvel-kit/core/utils';

export default boot(({ ssrContext }) => {
  if (import.meta.env.VITE_AXIOS_INTERCEPTORS === 'true') {
    const quvel = useQuvel();
    const interceptors = createDebugInterceptors(quvel.log, quvel.config, {
      onRequest: (config) => {
        // Custom request logging
      },
      onResponse: (response) => {
        // Custom response logging
      }
    });

    quvel.api.instance.interceptors.request.use(
      interceptors.request.onFulfilled,
      interceptors.request.onRejected
    );

    quvel.api.instance.interceptors.response.use(
      interceptors.response.onFulfilled,
      interceptors.response.onRejected
    );
  }
});
```

Enable in `.env`:
```bash
VITE_AXIOS_INTERCEPTORS=true
```

### Dynamic Asset Loading

```typescript
import { injectAssets } from '@quvel-kit/core';

// Client-side dynamic asset injection
injectAssets({
  css: [
    {
      url: 'https://cdn.example.com/styles.css',
      priority: 'critical',
      position: 'head',
    }
  ],
  js: [
    {
      url: 'https://cdn.example.com/script.js',
      loading: 'deferred',
      position: 'body-end',
    }
  ]
});
```

### Theme Management

```typescript
import { useQuvel } from '@quvel-kit/core';

export default defineComponent({
  setup() {
    const { theme } = useQuvel();

    // Get current theme
    const currentTheme = theme.theme; // 'light' | 'dark'

    // Set specific theme
    theme.setTheme('dark');

    // Toggle between light and dark
    theme.toggleTheme();

    return { currentTheme };
  }
});
```

Theme is automatically loaded from cookies or system preference during SSR boot. The ThemeService manages Quasar Dark mode and Tailwind classes.

### I18n with Modules

```typescript
import { I18nService } from '@quvel-kit/core';
import { getTranslations } from '@quvel-kit/core';
import { modules } from './modules';

// Aggregate translations from modules
const messages = {
  'en-US': getTranslations(modules, 'en-US'),
  'es-ES': getTranslations(modules, 'es-ES')
};

// Set translations before booting container
I18nService.setTranslations(messages);

// In component
const { i18n } = useQuvel();
i18n.t('common.welcome'); // Access translations
```

### WebSocket Subscriptions

```typescript
import { useWebSockets } from '@quvel-kit/core';

export default defineComponent({
  setup() {
    const { subscribe, unsubscribe } = useWebSockets();

    onMounted(async () => {
      const channel = await subscribe({
        type: 'private',
        channelName: 'user.1',
        events: {
          'notification': (data) => {
            console.log('Notification received:', data);
          }
        }
      });

      onBeforeUnmount(() => {
        unsubscribe(channel);
      });
    });
  }
});
```

## Module System

The module system organizes features into self-contained units. Each module contributes routes, services, translations, and build configuration. Modules compose into applications without tight coupling.

### Module Structure

A module is a plain object with optional properties:

```typescript
export interface Module {
  routes?: RouteRecordRaw[];          // Vue Router routes
  services?: ServiceRegistry;         // Service classes for DI
  i18n?: TranslationRegistry;         // Translations by locale
  build?: ModuleBuildConfig;          // Build-time configuration
}
```

All properties are optional. Modules implement only what they need.

### Creating a Module

Define a module by exporting an object:

```typescript
// modules/Auth/index.ts
import type { Module } from '@quvel-kit/core';
import { AuthService } from './services/AuthService';

export const AuthModule: Module = {
  routes: [
    {
      path: '/login',
      component: () => import('./pages/LoginPage.vue')
    },
    {
      path: '/register',
      component: () => import('./pages/RegisterPage.vue')
    }
  ],

  services: {
    AuthService: AuthService
  },

  i18n: {
    'en-US': {
      auth: {
        login: 'Login',
        register: 'Register',
        logout: 'Logout'
      }
    },
    'es-ES': {
      auth: {
        login: 'Iniciar sesión',
        register: 'Registrarse',
        logout: 'Cerrar sesión'
      }
    }
  },

  build: {
    boot: ['auth'],
    css: ['auth.css'],
    plugins: ['Notify']
  }
};
```

### Registering Modules

Collect modules in a registry:

```typescript
// src/modules/index.ts
import { AuthModule } from './Auth';
import { DashboardModule } from './Dashboard';
import { ProfileModule } from './Profile';

export const modules = {
  Auth: AuthModule,
  Dashboard: DashboardModule,
  Profile: ProfileModule
};
```

### Router Integration

Aggregate routes from all modules:

```typescript
// src/router/routes.ts
import { getRoutes } from '@quvel-kit/core';
import { modules } from '../modules';

const routes = [
  {
    path: '/',
    component: () => import('layouts/MainLayout.vue'),
    children: getRoutes(modules)  // Inject module routes
  }
];

export default routes;
```

### Service Container Integration

Register services from modules:

```typescript
// src/boot/quvel.ts
import { defineQuvelBoot, getServices } from '@quvel-kit/core';
import { modules } from '../modules';

export default defineQuvelBoot(getServices(modules));
```

### I18n Integration

Load translations from modules:

```typescript
// src/i18n/index.ts
import { getTranslations } from '@quvel-kit/core';
import { modules } from '../modules';

export default {
  messages: {
    'en-US': getTranslations(modules, 'en-US'),
    'es-ES': getTranslations(modules, 'es-ES')
  }
};
```

### Build Configuration

Apply module build configs in Quasar config:

```typescript
// quasar.config.ts
import { createModuleTransformer } from '@quvel-kit/core/config/moduleTransformer';
import { modules } from './src/modules';

export default configure((ctx) => {
  let config = {
    boot: [],
    css: [],
    framework: {
      plugins: []
    }
  };

  // Apply module configs
  config = createModuleTransformer(modules)(config, ctx);

  return config;
});
```

The transformer merges all module build configs into Quasar's configuration automatically.

### Module Build Config

Modules declare build-time resources:

```typescript
build: {
  boot: ['auth', 'permissions'],         // Boot files to load
  css: ['module.css', 'theme.scss'],     // CSS files to include
  animations: ['fadeIn', 'slideUp'],     // Quasar animations
  plugins: ['Notify', 'Dialog']          // Quasar plugins
}
```

Boot files support client/server targeting:

```typescript
build: {
  boot: [
    { path: 'auth', server: true, client: true },
    { path: 'analytics', server: false, client: true }
  ]
}
```

### Why Modules?

**Without modules:**
- Routes scattered across files
- Services manually wired together
- Translations duplicated
- Build config becomes unwieldy

**With modules:**
- Features are self-contained
- Adding a module is one line
- Removing a module is one line
- Zero manual wiring

## Architecture

### Service Lifecycle

Core uses a three-phase service lifecycle for dependency injection:

1. **Initialize** - Service classes are instantiated with `config` and `ssrContext`
2. **Register** - Services receive the container and store references to dependencies
3. **Boot** - Services use their dependencies for initialization (SSR-aware services only)

**Important:** Config is passed to all services in their constructor, so it's always available. Services like `LogService` and `I18nService` self-initialize in their constructors. Other services that initialize during `register()` may not be ready yet when accessed by other services during their `register()` phase.

All services complete their `register()` phase before any `boot()` phase begins. The `boot()` phase is where you can safely use ALL dependencies without ordering concerns.

#### Service Constructor Signature

Services receive config first, then ssrContext:

```typescript
import type { QSsrContext } from '@quasar/app-vite';
import type { AppConfig } from '@quvel-kit/core';
import { Service } from '@quvel-kit/core';

export class MyService extends Service {
  private readonly config: AppConfig;

  constructor(config: AppConfig, ssrContext?: QSsrContext | null) {
    super(config, ssrContext);
    this.config = config;
    // Config is always available in constructor
  }
}
```

#### Register Method Signature

Use destructuring in your `register()` method for clean dependency access:

```typescript
import type { ServiceContainer } from '@quvel-kit/core';
import type { RegisterService } from '@quvel-kit/core';

export class MyService extends Service implements RegisterService {
  private api!: ApiService;

  // Use destructuring to access dependencies
  register({ api, config, ssrContext }: ServiceContainer): void {
    this.api = api;
    // config and ssrContext are also available from container
    this.client = createClient(ssrContext, config);
  }
}
```

#### Safety Guidelines

```typescript
// ✅ ALWAYS SAFE: Config is passed in constructor
constructor(config: AppConfig, ssrContext?: QSsrContext | null) {
  super(config, ssrContext);
  this.config = config;  // ✓ Always available
  const debug = this.config.app.debug;  // ✓ Safe to use
}

// ✅ ALWAYS SAFE: Config and Log via container
register({ config, log }: ServiceContainer): void {
  const debug = config.app.debug;  // ✓ Always ready
  log.info('Service registered');  // ✓ Safe to use
}

// ⚠️ ORDERING ISSUE: Using services that initialize during register()
register({ i18n }: ServiceContainer): void {
  this.i18n = i18n;
  // This may fail if your service registers before I18nService!
  const message = this.i18n.t('key');  // Race condition based on order
}

// ✅ CORRECT: Use all dependencies in boot()
boot(): void {
  // All services are fully initialized - safe to use anything
  const locale = this.config.app.locale;
  const message = this.i18n.t('welcome');
  this.api.setHeader('locale', locale);
}
```

### SSR Support

Services can be SSR-aware by implementing the `SsrAwareService` interface:

```typescript
import type { QSsrContext } from '@quasar/app-vite';

export class MyService extends Service implements SsrAwareService {
  boot(ssrContext?: QSsrContext | null): void {
    // Access Quasar instance for SSR
    if (ssrContext?.$q) {
      ssrContext.$q.dark.set(true);
    }

    // Access request context
    const config = ssrContext?.req?.requestContext?.appConfig;
  }
}
```

## TypeScript Support

Full TypeScript support with comprehensive type definitions for:
- Service container and dependency injection
- Configuration structures
- API request/response types
- WebSocket channel types
- Task options and handlers

## License

MIT