npm package: @paulovila/shared
Description: Foundation plugin providing shared types, utilities, and localization for all Vetra plugins
Author: Vetra Team
Latest version: 1.0.1

# @vetra/shared

Foundation plugin providing shared types, utilities, and localization for all Vetra plugins.

## Overview

`@vetra/shared` is the foundational plugin that all other Vetra plugins depend on. It provides:

- **Shared Types** - Common interfaces and types for plugins
- **Localization Utilities** - Multi-locale support (English/Spanish)
- **Validation Helpers** - Common validation functions
- **Utility Functions** - Deep merge, type guards, string/array/object utilities
- **Configuration Management** - Environment and feature flag handling
- **Constants** - Shared constants for all plugins

## Installation

```bash
npm install @vetra/shared
# or
pnpm add @vetra/shared
```

## Quick Start

### Basic Usage

```typescript
import { sharedPlugin } from '@vetra/shared'
import { buildConfig } from 'payload'

export default buildConfig({
  plugins: [
    sharedPlugin({
      enabled: true,
      debug: process.env.NODE_ENV === 'development',
    }),
  ],
})
```

### Using Localization Utilities

```typescript
import {
  getLocalizedValue,
  setLocalizedValue,
  getDocumentLocales,
} from '@vetra/shared'

// Get localized value with fallback
const title = getLocalizedValue(document, 'title', 'es', 'en')

// Set localized value
const updated = setLocalizedValue(document, 'title', 'Hola', 'es')

// Get all locales in document
const locales = getDocumentLocales(document)
```

### Using Validation Helpers

```typescript
import {
  isValidEmail,
  isValidUrl,
  isValidPhone,
  minLength,
  maxLength,
} from '@vetra/shared'

// Validate email
if (isValidEmail(email)) {
  // Valid email
}

// Validate length
if (minLength(password, 8) && maxLength(password, 128)) {
  // Valid password length
}
```

### Using Utility Functions

```typescript
import {
  deepMerge,
  toKebabCase,
  removeDuplicates,
  getNestedValue,
} from '@vetra/shared'

// Deep merge objects
const merged = deepMerge(target, source)

// Convert to kebab-case
const slug = toKebabCase('HelloWorld') // 'hello-world'

// Remove duplicates
const unique = removeDuplicates([1, 2, 2, 3, 3, 3]) // [1, 2, 3]

// Get nested value
const value = getNestedValue(obj, 'user.profile.name')
```

## API Reference

### Localization

#### `getLocalizedValue(data, field, locale, fallbackLocale)`

Get localized value with fallback support.

```typescript
const title = getLocalizedValue(
  { title_en: 'Hello', title_es: 'Hola' },
  'title',
  'es',
  'en'
) // 'Hola'
```

#### `setLocalizedValue(data, field, value, locale)`

Set localized value for a specific locale.

```typescript
const updated = setLocalizedValue(
  { title_en: 'Hello' },
  'title',
  'Hola',
  'es'
) // { title_en: 'Hello', title_es: 'Hola' }
```

#### `getDocumentLocales(data)`

Get all locales present in a document.

```typescript
const locales = getDocumentLocales({
  title_en: 'Hello',
  title_es: 'Hola',
}) // ['en', 'es']
```

#### `mergeLocalizedDocuments(target, source, preserveFields)`

Merge localized documents with custom field preservation.

```typescript
const merged = mergeLocalizedDocuments(
  { id: '1', title_en: 'Hello' },
  { title_es: 'Hola' },
  ['id']
)
```

#### `translate(translations, key, locale, defaultValue)`

Translate a key using a translation object.

```typescript
const translations = {
  en: { greeting: 'Hello' },
  es: { greeting: 'Hola' },
}

const greeting = translate(translations, 'greeting', 'es')
// 'Hola'
```

#### `formatLocalizedDate(date, locale, options)`

Format a date for a specific locale.

```typescript
const formatted = formatLocalizedDate(
  new Date(),
  'es',
  { year: 'numeric', month: 'long', day: 'numeric' }
)
```

#### `formatLocalizedCurrency(amount, currency, locale)`

Format currency for a specific locale.

```typescript
const formatted = formatLocalizedCurrency(1234.56, 'USD', 'en')
// '$1,234.56'
```

### Validation

#### `isValidEmail(email)`

Validate email address.

```typescript
isValidEmail('test@example.com') // true
isValidEmail('invalid.email') // false
```

#### `isValidUrl(url)`

Validate URL.

```typescript
isValidUrl('https://example.com') // true
isValidUrl('not a url') // false
```

#### `isValidPhone(phone)`

Validate phone number.

```typescript
isValidPhone('+1-234-567-8900') // true
isValidPhone('123') // false
```

#### `isValidUUID(uuid)`

Validate UUID.

```typescript
isValidUUID('550e8400-e29b-41d4-a716-446655440000') // true
```

#### `isValidSlug(slug)`

Validate slug format.

```typescript
isValidSlug('valid-slug') // true
isValidSlug('Invalid Slug') // false
```

#### `minLength(value, min)` / `maxLength(value, max)`

Validate string or array length.

```typescript
minLength('hello', 3) // true
maxLength('hello', 10) // true
```

#### `minValue(value, min)` / `maxValue(value, max)`

Validate numeric value.

```typescript
minValue(10, 5) // true
maxValue(5, 10) // true
```

### Utilities

#### `deepMerge(target, source)`

Deep merge two objects.

```typescript
const merged = deepMerge(
  { a: 1, b: { c: 2 } },
  { b: { d: 3 }, e: 4 }
)
// { a: 1, b: { c: 2, d: 3 }, e: 4 }
```

#### `toKebabCase(str)` / `toCamelCase(str)` / `toPascalCase(str)` / `toSnakeCase(str)`

Convert string case.

```typescript
toKebabCase('helloWorld') // 'hello-world'
toCamelCase('hello-world') // 'helloWorld'
toPascalCase('hello-world') // 'HelloWorld'
toSnakeCase('helloWorld') // 'hello_world'
```

#### `removeDuplicates(array, key)`

Remove duplicates from array.

```typescript
removeDuplicates([1, 2, 2, 3, 3, 3]) // [1, 2, 3]

// With key function
removeDuplicates(
  [{ id: 1 }, { id: 2 }, { id: 1 }],
  (item) => item.id
) // [{ id: 1 }, { id: 2 }]
```

#### `flatten(array, depth)`

Flatten nested array.

```typescript
flatten([1, [2, 3], [4, [5, 6]]]) // [1, 2, 3, 4, 5, 6]
flatten([1, [2, [3, 4]]], 1) // [1, 2, [3, 4]]
```

#### `groupBy(array, key)`

Group array items by key.

```typescript
groupBy(
  [
    { type: 'a', value: 1 },
    { type: 'b', value: 2 },
    { type: 'a', value: 3 },
  ],
  (item) => item.type
)
// { a: [{ type: 'a', value: 1 }, { type: 'a', value: 3 }], b: [...] }
```

#### `sortBy(array, key, order)`

Sort array by key.

```typescript
sortBy(
  [{ value: 3 }, { value: 1 }, { value: 2 }],
  (item) => item.value,
  'asc'
) // [{ value: 1 }, { value: 2 }, { value: 3 }]
```

#### `getNestedValue(obj, path, defaultValue)`

Get nested value from object.

```typescript
getNestedValue(
  { user: { profile: { name: 'John' } } },
  'user.profile.name'
) // 'John'
```

#### `setNestedValue(obj, path, value)`

Set nested value in object.

```typescript
const obj = {}
setNestedValue(obj, 'user.profile.name', 'John')
// { user: { profile: { name: 'John' } } }
```

#### `pick(obj, keys)` / `omit(obj, keys)`

Pick or omit specific keys from object.

```typescript
pick({ a: 1, b: 2, c: 3 }, ['a', 'c']) // { a: 1, c: 3 }
omit({ a: 1, b: 2, c: 3 }, ['b']) // { a: 1, c: 3 }
```

### Type Guards

```typescript
import {
  isPlainObject,
  isArray,
  isString,
  isNumber,
  isBoolean,
  isNullOrUndefined,
  isEmpty,
  isDate,
  isFunction,
} from '@vetra/shared'

isPlainObject({}) // true
isArray([]) // true
isString('hello') // true
isNumber(123) // true
isBoolean(true) // true
isNullOrUndefined(null) // true
isEmpty('') // true
isDate(new Date()) // true
isFunction(() => {}) // true
```

### Configuration

#### `getEnvironmentConfig()`

Get environment configuration.

```typescript
const config = getEnvironmentConfig()
// { nodeEnv: 'development', isDevelopment: true, ... }
```

#### `getFeatureFlags(prefix)`

Get feature flags from environment variables.

```typescript
const flags = getFeatureFlags('FEATURE_')
// { kyc_enabled: true, kyb_enabled: false, ... }
```

#### `isFeatureEnabled(flags, name, defaultValue)`

Check if feature flag is enabled.

```typescript
isFeatureEnabled(flags, 'kyc_enabled', false) // true
```

#### `getApiConfig()` / `getDatabaseConfig()` / `getCacheConfig()`

Get configuration from environment variables.

```typescript
const apiConfig = getApiConfig()
// { baseUrl: 'http://localhost:3000', timeout: 30000, ... }
```

## Constants

### Locales

```typescript
import { SUPPORTED_LOCALES, DEFAULT_LOCALE } from '@vetra/shared'

SUPPORTED_LOCALES // ['en', 'es']
DEFAULT_LOCALE // 'en'
```

### Workflow

```typescript
import {
  WORKFLOW_STATUSES,
  BLOCK_TYPES,
  ENGINE_TYPES,
  PRIORITY_LEVELS,
} from '@vetra/shared'

WORKFLOW_STATUSES // { PENDING: 'pending', PROCESSING: 'processing', ... }
BLOCK_TYPES // { KYC: 'kyc', DOCUMENT_UPLOAD: 'document_upload', ... }
ENGINE_TYPES // { OCR: 'ocr', FACE_RECOGNITION: 'face_recognition', ... }
PRIORITY_LEVELS // { LOW: 'low', MEDIUM: 'medium', HIGH: 'high', CRITICAL: 'critical' }
```

### Error Codes

```typescript
import { ERROR_CODES } from '@vetra/shared'

ERROR_CODES // { LOCALE_NOT_SUPPORTED: 'LOCALE_NOT_SUPPORTED', ... }
```

## Types

### Plugin Types

```typescript
import type {
  BasePluginOptions,
  PluginConfig,
  ResolvedPluginConfig,
} from '@vetra/shared'
```

### Localization Types

```typescript
import type {
  LocalizedValue,
  LocalizedDocument,
  LocalizationOptions,
  LocaleContext,
} from '@vetra/shared'
```

### Workflow Types

```typescript
import type {
  WorkflowConfig,
  WorkflowTemplate,
  WorkflowInstance,
  BlockConfig,
  BlockInstance,
  BlockResult,
} from '@vetra/shared'
```

### Error Types

```typescript
import { VetraError } from '@vetra/shared'
import type { ErrorDetails } from '@vetra/shared'

throw new VetraError({
  code: 'VALIDATION_FAILED',
  message: 'Validation failed',
  statusCode: 400,
})
```

## Testing

Run tests with coverage:

```bash
npm run test:coverage
```

Tests are located in `tests/unit/` and `tests/integration/`.

## Development

### Building

```bash
npm run build
```

### Type Checking

```bash
npm run type-check
```

### Linting

```bash
npm run lint
```

## Contributing

Contributions are welcome! Please ensure:

- All tests pass
- Code coverage remains >80%
- TypeScript strict mode is satisfied
- Code follows the project style guide

## License

MIT

## Support

For issues, questions, or contributions, please refer to the main Vetra3 repository.