mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
@@ -0,0 +1,16 @@
|
||||
[tool.commitizen]
|
||||
name = "cz_customize"
|
||||
tag_format = "pkg-com/v$version"
|
||||
ignored_tag_formats = ["*/v${version}", "v${version}"]
|
||||
version_scheme = "semver"
|
||||
version_provider = "npm"
|
||||
update_changelog_on_bump = true
|
||||
major_version_zero = true
|
||||
pre_bump_hooks = ["bash ../../scripts/hook-bump-pre.sh"]
|
||||
post_bump_hooks = ["pnpm --filter '@sonr.io/com' publish --no-git-checks"]
|
||||
|
||||
[tool.commitizen.customize]
|
||||
bump_pattern = "^(feat|fix|refactor|perf|BREAKING CHANGE)"
|
||||
bump_map = { "BREAKING CHANGE" = "MAJOR", "feat" = "MINOR", "fix" = "PATCH", "refactor" = "PATCH", "perf" = "PATCH" }
|
||||
default_bump = "PATCH"
|
||||
changelog_pattern = "^(feat|fix|refactor|docs|build)\\(pkg-com\\)(!)?:"
|
||||
@@ -0,0 +1,3 @@
|
||||
## pkg-com/v0.1.12 (2025-09-28)
|
||||
|
||||
## pkg-com/v0.1.11 (2025-09-27)
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "@sonr.io/com",
|
||||
"version": "0.1.12",
|
||||
"description": "Common types, utilities, and constants for Sonr ecosystem",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"import": "./dist/types/index.js",
|
||||
"require": "./dist/types/index.cjs"
|
||||
},
|
||||
"./utils": {
|
||||
"types": "./dist/utils/index.d.ts",
|
||||
"import": "./dist/utils/index.js",
|
||||
"require": "./dist/utils/index.cjs"
|
||||
},
|
||||
"./constants": {
|
||||
"types": "./dist/constants/index.d.ts",
|
||||
"import": "./dist/constants/index.js",
|
||||
"require": "./dist/constants/index.cjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "echo 'No tests defined for @sonr.io/com'",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "biome check .",
|
||||
"format": "biome format --write .",
|
||||
"release": "cz --no-raise 6,21 bump --yes --increment PATCH"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.8.0",
|
||||
"@types/node": "^22.5.5",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sonr-io/sonr.git",
|
||||
"directory": "packages/com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"sonr",
|
||||
"common",
|
||||
"types",
|
||||
"utilities",
|
||||
"constants"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* @sonr.io/com - Constants
|
||||
* Shared constants and configurations
|
||||
*/
|
||||
|
||||
/**
|
||||
* Status constants
|
||||
*/
|
||||
export const STATUS = {
|
||||
ACTIVE: 'active',
|
||||
INACTIVE: 'inactive',
|
||||
PENDING: 'pending',
|
||||
ERROR: 'error',
|
||||
WARNING: 'warning',
|
||||
SUCCESS: 'success',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* HTTP methods
|
||||
*/
|
||||
export const HTTP_METHOD = {
|
||||
GET: 'GET',
|
||||
POST: 'POST',
|
||||
PUT: 'PUT',
|
||||
DELETE: 'DELETE',
|
||||
PATCH: 'PATCH',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Time intervals in milliseconds
|
||||
*/
|
||||
export const TIME = {
|
||||
SECOND: 1000,
|
||||
MINUTE: 60 * 1000,
|
||||
HOUR: 60 * 60 * 1000,
|
||||
DAY: 24 * 60 * 60 * 1000,
|
||||
WEEK: 7 * 24 * 60 * 60 * 1000,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Default pagination settings
|
||||
*/
|
||||
export const PAGINATION = {
|
||||
DEFAULT_PAGE: 1,
|
||||
DEFAULT_PAGE_SIZE: 20,
|
||||
MAX_PAGE_SIZE: 100,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* API error codes
|
||||
*/
|
||||
export const ERROR_CODE = {
|
||||
NETWORK_ERROR: 'NETWORK_ERROR',
|
||||
TIMEOUT: 'TIMEOUT',
|
||||
UNAUTHORIZED: 'UNAUTHORIZED',
|
||||
FORBIDDEN: 'FORBIDDEN',
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
SERVER_ERROR: 'SERVER_ERROR',
|
||||
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
||||
RATE_LIMIT: 'RATE_LIMIT',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* User roles
|
||||
*/
|
||||
export const USER_ROLE = {
|
||||
OWNER: 'owner',
|
||||
ADMIN: 'admin',
|
||||
DEVELOPER: 'developer',
|
||||
VIEWER: 'viewer',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Permission effects
|
||||
*/
|
||||
export const PERMISSION_EFFECT = {
|
||||
ALLOW: 'allow',
|
||||
DENY: 'deny',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Domain verification methods
|
||||
*/
|
||||
export const VERIFICATION_METHOD = {
|
||||
DNS: 'dns',
|
||||
HTTP: 'http',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Chart types
|
||||
*/
|
||||
export const CHART_TYPE = {
|
||||
LINE: 'line',
|
||||
AREA: 'area',
|
||||
BAR: 'bar',
|
||||
PIE: 'pie',
|
||||
DONUT: 'donut',
|
||||
RADAR: 'radar',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Theme options
|
||||
*/
|
||||
export const THEME = {
|
||||
LIGHT: 'light',
|
||||
DARK: 'dark',
|
||||
SYSTEM: 'system',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Notification frequency
|
||||
*/
|
||||
export const NOTIFICATION_FREQUENCY = {
|
||||
REALTIME: 'realtime',
|
||||
DAILY: 'daily',
|
||||
WEEKLY: 'weekly',
|
||||
NEVER: 'never',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Performance status thresholds
|
||||
*/
|
||||
export const PERFORMANCE_THRESHOLD = {
|
||||
GOOD: 'good',
|
||||
WARNING: 'warning',
|
||||
CRITICAL: 'critical',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* DNS record types
|
||||
*/
|
||||
export const DNS_RECORD_TYPE = {
|
||||
TXT: 'TXT',
|
||||
CNAME: 'CNAME',
|
||||
A: 'A',
|
||||
AAAA: 'AAAA',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Service categories
|
||||
*/
|
||||
export const SERVICE_CATEGORY = {
|
||||
API: 'api',
|
||||
WEBAPP: 'webapp',
|
||||
MOBILE: 'mobile',
|
||||
IOT: 'iot',
|
||||
BLOCKCHAIN: 'blockchain',
|
||||
OTHER: 'other',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Permission categories
|
||||
*/
|
||||
export const PERMISSION_CATEGORY = {
|
||||
BASIC: 'basic',
|
||||
STANDARD: 'standard',
|
||||
ADVANCED: 'advanced',
|
||||
CUSTOM: 'custom',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Time range presets
|
||||
*/
|
||||
export const TIME_PRESET = {
|
||||
TODAY: 'today',
|
||||
YESTERDAY: 'yesterday',
|
||||
LAST_7_DAYS: 'last7days',
|
||||
LAST_30_DAYS: 'last30days',
|
||||
THIS_MONTH: 'thisMonth',
|
||||
LAST_MONTH: 'lastMonth',
|
||||
CUSTOM: 'custom',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Regular expressions for validation
|
||||
*/
|
||||
export const REGEX = {
|
||||
EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
||||
URL: /^https?:\/\/.+/,
|
||||
DOMAIN: /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i,
|
||||
API_KEY: /^sk_(test|live)_[a-zA-Z0-9]{24,}$/,
|
||||
DID: /^did:[a-z0-9]+:[a-zA-Z0-9:.-]+$/,
|
||||
USERNAME: /^[a-zA-Z0-9_-]+$/,
|
||||
PHONE: /^\+?[1-9]\d{1,14}$/,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Default chart colors
|
||||
*/
|
||||
export const CHART_COLORS = [
|
||||
'#3b82f6', // blue
|
||||
'#10b981', // emerald
|
||||
'#f59e0b', // amber
|
||||
'#ef4444', // red
|
||||
'#8b5cf6', // violet
|
||||
'#ec4899', // pink
|
||||
'#14b8a6', // teal
|
||||
'#f97316', // orange
|
||||
] as const;
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @sonr.io/com
|
||||
* Shared types, utilities, and constants for the Sonr ecosystem
|
||||
*/
|
||||
|
||||
// Export all types
|
||||
export * from './types';
|
||||
|
||||
// Export all utilities
|
||||
export * from './utils';
|
||||
|
||||
// Export all constants
|
||||
export * from './constants';
|
||||
|
||||
// Re-export commonly used items at top level for convenience
|
||||
export type {
|
||||
// Core types
|
||||
ID,
|
||||
Timestamp,
|
||||
Status,
|
||||
Pagination,
|
||||
Filter,
|
||||
// Service types
|
||||
Service,
|
||||
ServiceMetrics,
|
||||
ServiceRequest,
|
||||
// Domain types
|
||||
Domain,
|
||||
DomainStatus,
|
||||
DNSRecord,
|
||||
// Permission types
|
||||
Permission,
|
||||
UCANToken,
|
||||
UCANCapability,
|
||||
// User types
|
||||
User,
|
||||
UserRole,
|
||||
UserPreferences,
|
||||
// API types
|
||||
ApiResponse,
|
||||
ApiError,
|
||||
// Analytics types
|
||||
TimeRange,
|
||||
DataPoint,
|
||||
Metric,
|
||||
} from './types';
|
||||
|
||||
export {
|
||||
// Date utilities
|
||||
formatDate,
|
||||
formatTime,
|
||||
formatDateTime,
|
||||
getRelativeTime,
|
||||
// Format utilities
|
||||
formatNumber,
|
||||
formatCurrency,
|
||||
formatPercentage,
|
||||
formatBytes,
|
||||
abbreviateNumber,
|
||||
// Validation utilities
|
||||
isValidEmail,
|
||||
isValidUrl,
|
||||
isValidDomain,
|
||||
schemas,
|
||||
// Array utilities
|
||||
groupBy,
|
||||
sortBy,
|
||||
filterBy,
|
||||
unique,
|
||||
} from './utils';
|
||||
|
||||
export {
|
||||
// Constants
|
||||
STATUS,
|
||||
HTTP_METHOD,
|
||||
ERROR_CODE,
|
||||
USER_ROLE,
|
||||
THEME,
|
||||
} from './constants';
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Analytics & Metrics Types
|
||||
* Types for charts, metrics, and data visualization
|
||||
*/
|
||||
|
||||
import type { Timestamp } from './common';
|
||||
|
||||
/**
|
||||
* Time range for analytics
|
||||
*/
|
||||
export interface TimeRange {
|
||||
start: Date;
|
||||
end: Date;
|
||||
preset?:
|
||||
| 'today'
|
||||
| 'yesterday'
|
||||
| 'last7days'
|
||||
| 'last30days'
|
||||
| 'thisMonth'
|
||||
| 'lastMonth'
|
||||
| 'custom';
|
||||
}
|
||||
|
||||
/**
|
||||
* Analytics data point
|
||||
*/
|
||||
export interface DataPoint {
|
||||
timestamp: Timestamp;
|
||||
value: number;
|
||||
label?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chart configuration
|
||||
*/
|
||||
export interface ChartConfig {
|
||||
type: 'line' | 'area' | 'bar' | 'pie' | 'donut' | 'radar';
|
||||
colors?: string[];
|
||||
showLegend?: boolean;
|
||||
showTooltip?: boolean;
|
||||
showGrid?: boolean;
|
||||
animated?: boolean;
|
||||
stacked?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metric with trend
|
||||
*/
|
||||
export interface Metric {
|
||||
label: string;
|
||||
value: number | string;
|
||||
change?: number;
|
||||
changeType?: 'increase' | 'decrease' | 'neutral';
|
||||
unit?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance metric
|
||||
*/
|
||||
export interface PerformanceMetric {
|
||||
name: string;
|
||||
value: number;
|
||||
unit: 'ms' | 's' | 'min' | '%' | 'rpm' | 'rps';
|
||||
status: 'good' | 'warning' | 'critical';
|
||||
threshold?: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity data for charts
|
||||
*/
|
||||
export interface ActivityData {
|
||||
date: string;
|
||||
value: number;
|
||||
category?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request pattern data
|
||||
*/
|
||||
export interface RequestPatternData {
|
||||
endpoint: string;
|
||||
method: string;
|
||||
count: number;
|
||||
avgDuration: number;
|
||||
errorRate: number;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* API Types
|
||||
* Types for API requests, responses, and errors
|
||||
*/
|
||||
|
||||
import type { Timestamp } from './common';
|
||||
|
||||
/**
|
||||
* API response wrapper
|
||||
*/
|
||||
export interface ApiResponse<T> {
|
||||
data?: T;
|
||||
error?: ApiError;
|
||||
status: number;
|
||||
timestamp: Timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* API error
|
||||
*/
|
||||
export interface ApiError {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: Record<string, any>;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* API request configuration
|
||||
*/
|
||||
export interface ApiRequestConfig {
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
headers?: Record<string, string>;
|
||||
params?: Record<string, any>;
|
||||
body?: any;
|
||||
timeout?: number;
|
||||
retry?: {
|
||||
attempts: number;
|
||||
delay: number;
|
||||
backoff?: 'linear' | 'exponential';
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginated API response
|
||||
*/
|
||||
export interface PaginatedResponse<T> {
|
||||
items: T[];
|
||||
pagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* API endpoint metadata
|
||||
*/
|
||||
export interface ApiEndpoint {
|
||||
path: string;
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
description?: string;
|
||||
authenticated: boolean;
|
||||
rateLimit?: {
|
||||
requests: number;
|
||||
window: number;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Core Common Types
|
||||
* Fundamental types used across the Sonr ecosystem
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common ID type for entities
|
||||
*/
|
||||
export type ID = string;
|
||||
|
||||
/**
|
||||
* Timestamp in ISO 8601 format
|
||||
*/
|
||||
export type Timestamp = string;
|
||||
|
||||
/**
|
||||
* Status types used across components
|
||||
*/
|
||||
export type Status = 'active' | 'inactive' | 'pending' | 'error' | 'warning' | 'success';
|
||||
|
||||
/**
|
||||
* Sort direction for tables and lists
|
||||
*/
|
||||
export type SortDirection = 'asc' | 'desc';
|
||||
|
||||
/**
|
||||
* Common pagination props
|
||||
*/
|
||||
export interface Pagination {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common filter props
|
||||
*/
|
||||
export interface Filter {
|
||||
field: string;
|
||||
operator: 'equals' | 'contains' | 'startsWith' | 'endsWith' | 'gt' | 'lt' | 'gte' | 'lte';
|
||||
value: string | number | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigation item
|
||||
*/
|
||||
export interface NavigationItem {
|
||||
id: ID;
|
||||
label: string;
|
||||
href: string;
|
||||
icon?: string;
|
||||
badge?: string | number;
|
||||
disabled?: boolean;
|
||||
external?: boolean;
|
||||
children?: NavigationItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Breadcrumb item
|
||||
*/
|
||||
export interface BreadcrumbItem {
|
||||
label: string;
|
||||
href?: string;
|
||||
current?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification
|
||||
*/
|
||||
export interface Notification {
|
||||
id: ID;
|
||||
type: 'info' | 'success' | 'warning' | 'error';
|
||||
title: string;
|
||||
message?: string;
|
||||
timestamp: Timestamp;
|
||||
read?: boolean;
|
||||
action?: {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity event
|
||||
*/
|
||||
export interface ActivityEvent {
|
||||
id: ID;
|
||||
type: string;
|
||||
actor: string;
|
||||
action: string;
|
||||
target?: string;
|
||||
timestamp: Timestamp;
|
||||
details?: Record<string, any>;
|
||||
icon?: string;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Domain & DNS Types
|
||||
* Types for domain verification and DNS management
|
||||
*/
|
||||
|
||||
import type { ID, Timestamp } from './common';
|
||||
|
||||
/**
|
||||
* Domain verification status
|
||||
*/
|
||||
export type DomainStatus = 'unverified' | 'pending' | 'verified' | 'failed' | 'expired';
|
||||
|
||||
/**
|
||||
* Domain entity
|
||||
*/
|
||||
export interface Domain {
|
||||
id: ID;
|
||||
domain: string;
|
||||
status: DomainStatus;
|
||||
owner: string;
|
||||
verificationMethod: 'dns' | 'http';
|
||||
verificationRecord?: DNSRecord;
|
||||
verifiedAt?: Timestamp;
|
||||
expiresAt?: Timestamp;
|
||||
services?: ID[];
|
||||
createdAt: Timestamp;
|
||||
updatedAt: Timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* DNS record for domain verification
|
||||
*/
|
||||
export interface DNSRecord {
|
||||
type: 'TXT' | 'CNAME' | 'A' | 'AAAA';
|
||||
name: string;
|
||||
value: string;
|
||||
ttl?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain verification request
|
||||
*/
|
||||
export interface DomainVerificationRequest {
|
||||
domain: string;
|
||||
method: 'dns' | 'http';
|
||||
autoVerify?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verification step
|
||||
*/
|
||||
export interface VerificationStep {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
status: 'pending' | 'in-progress' | 'completed' | 'error';
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verification check result
|
||||
*/
|
||||
export interface VerificationCheck {
|
||||
type: 'dns' | 'http';
|
||||
success: boolean;
|
||||
message: string;
|
||||
details?: Record<string, any>;
|
||||
timestamp: Timestamp;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @sonr.io/com - Type Definitions
|
||||
* Central export for all shared types
|
||||
*/
|
||||
|
||||
// Core common types
|
||||
export * from './common';
|
||||
|
||||
// Service types
|
||||
export * from './service';
|
||||
|
||||
// Domain & DNS types
|
||||
export * from './domain';
|
||||
|
||||
// Permission & auth types
|
||||
export * from './permission';
|
||||
|
||||
// User types
|
||||
export * from './user';
|
||||
|
||||
// Analytics types
|
||||
export * from './analytics';
|
||||
|
||||
// API types
|
||||
export * from './api';
|
||||
|
||||
// Re-export commonly used types for convenience
|
||||
export type {
|
||||
ID,
|
||||
Timestamp,
|
||||
Status,
|
||||
SortDirection,
|
||||
Pagination,
|
||||
Filter,
|
||||
} from './common';
|
||||
|
||||
export type {
|
||||
Service,
|
||||
ServiceMetrics,
|
||||
ServiceEndpoint,
|
||||
ServiceRequest,
|
||||
} from './service';
|
||||
|
||||
export type {
|
||||
Domain,
|
||||
DomainStatus,
|
||||
DNSRecord,
|
||||
DomainVerificationRequest,
|
||||
} from './domain';
|
||||
|
||||
export type {
|
||||
Permission,
|
||||
UCANToken,
|
||||
UCANCapability,
|
||||
AuditLogEntry,
|
||||
} from './permission';
|
||||
|
||||
export type {
|
||||
User,
|
||||
UserRole,
|
||||
UserPreferences,
|
||||
} from './user';
|
||||
|
||||
export type {
|
||||
TimeRange,
|
||||
DataPoint,
|
||||
Metric,
|
||||
PerformanceMetric,
|
||||
} from './analytics';
|
||||
|
||||
export type {
|
||||
ApiResponse,
|
||||
ApiError,
|
||||
ApiRequestConfig,
|
||||
} from './api';
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Auth & Permission Types
|
||||
* Types for authentication, authorization, and UCAN tokens
|
||||
*/
|
||||
|
||||
import type { ID, Timestamp } from './common';
|
||||
|
||||
/**
|
||||
* Permission entity
|
||||
*/
|
||||
export interface Permission {
|
||||
id: ID;
|
||||
resource: string;
|
||||
action: string;
|
||||
effect: 'allow' | 'deny';
|
||||
conditions?: PermissionCondition[];
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission condition
|
||||
*/
|
||||
export interface PermissionCondition {
|
||||
field: string;
|
||||
operator: 'equals' | 'notEquals' | 'contains' | 'in' | 'notIn';
|
||||
value: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission template for quick setup
|
||||
*/
|
||||
export interface PermissionTemplate {
|
||||
id: ID;
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Permission[];
|
||||
category: 'basic' | 'standard' | 'advanced' | 'custom';
|
||||
}
|
||||
|
||||
/**
|
||||
* UCAN token capability
|
||||
*/
|
||||
export interface UCANCapability {
|
||||
with: string;
|
||||
can: string;
|
||||
nb?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* UCAN token structure
|
||||
*/
|
||||
export interface UCANToken {
|
||||
iss: string;
|
||||
aud: string;
|
||||
exp?: number;
|
||||
nbf?: number;
|
||||
nnc?: string;
|
||||
att: UCANCapability[];
|
||||
prf?: string[];
|
||||
fct?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit log entry
|
||||
*/
|
||||
export interface AuditLogEntry {
|
||||
id: ID;
|
||||
timestamp: Timestamp;
|
||||
actor: string;
|
||||
action: string;
|
||||
resource: string;
|
||||
result: 'success' | 'failure';
|
||||
details?: Record<string, any>;
|
||||
ip?: string;
|
||||
userAgent?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission request data
|
||||
*/
|
||||
export interface PermissionRequestData {
|
||||
requester: string;
|
||||
resource: string;
|
||||
actions: string[];
|
||||
reason?: string;
|
||||
duration?: number;
|
||||
conditions?: PermissionCondition[];
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Service-related Types
|
||||
* Types for Sonr Services (x/svc module)
|
||||
*/
|
||||
|
||||
import type { ID, Status, Timestamp } from './common';
|
||||
import type { Permission } from './permission';
|
||||
|
||||
/**
|
||||
* Service entity
|
||||
*/
|
||||
export interface Service {
|
||||
id: ID;
|
||||
name: string;
|
||||
description?: string;
|
||||
domain: string;
|
||||
status: 'active' | 'inactive' | 'pending';
|
||||
apiKey: string;
|
||||
createdAt: Timestamp;
|
||||
updatedAt: Timestamp;
|
||||
permissions?: Permission[];
|
||||
metrics?: ServiceMetrics;
|
||||
owner: string;
|
||||
tags?: string[];
|
||||
endpoints?: ServiceEndpoint[];
|
||||
lastActive?: Timestamp;
|
||||
domainVerificationStatus?: 'verified' | 'pending' | 'failed' | 'unverified';
|
||||
}
|
||||
|
||||
/**
|
||||
* Service endpoint configuration
|
||||
*/
|
||||
export interface ServiceEndpoint {
|
||||
url: string;
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
description?: string;
|
||||
authenticated: boolean;
|
||||
rateLimit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service metrics
|
||||
*/
|
||||
export interface ServiceMetrics {
|
||||
requests: number;
|
||||
errors: number;
|
||||
latencyP50: number;
|
||||
latencyP95: number;
|
||||
latencyP99: number;
|
||||
uptime: number;
|
||||
lastActivity?: Timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service creation/update request
|
||||
*/
|
||||
export interface ServiceRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
domain?: string;
|
||||
permissions?: string[];
|
||||
tags?: string[];
|
||||
endpoints?: ServiceEndpoint[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Service status
|
||||
*/
|
||||
export type ServiceStatus = 'active' | 'inactive' | 'suspended' | 'pending';
|
||||
|
||||
/**
|
||||
* Service category
|
||||
*/
|
||||
export type ServiceCategory = 'api' | 'webapp' | 'mobile' | 'iot' | 'blockchain' | 'other';
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* User Types
|
||||
* Types for user profiles, roles, and preferences
|
||||
*/
|
||||
|
||||
import type { ID, Timestamp } from './common';
|
||||
|
||||
/**
|
||||
* User profile
|
||||
*/
|
||||
export interface User {
|
||||
id: ID;
|
||||
did: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
role?: UserRole;
|
||||
createdAt: Timestamp;
|
||||
lastLogin?: Timestamp;
|
||||
preferences?: UserPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* User role
|
||||
*/
|
||||
export type UserRole = 'owner' | 'admin' | 'developer' | 'viewer';
|
||||
|
||||
/**
|
||||
* User preferences
|
||||
*/
|
||||
export interface UserPreferences {
|
||||
theme?: 'light' | 'dark' | 'system';
|
||||
language?: string;
|
||||
timezone?: string;
|
||||
notifications?: NotificationPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification preferences
|
||||
*/
|
||||
export interface NotificationPreferences {
|
||||
email?: boolean;
|
||||
push?: boolean;
|
||||
inApp?: boolean;
|
||||
frequency?: 'realtime' | 'daily' | 'weekly' | 'never';
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Array & Object Utilities
|
||||
* Functions for manipulating arrays and objects
|
||||
*/
|
||||
|
||||
import type { Filter, SortDirection } from '../types';
|
||||
|
||||
/**
|
||||
* Group an array of objects by a key
|
||||
*/
|
||||
export function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
|
||||
return array.reduce(
|
||||
(result, item) => {
|
||||
const group = String(item[key]);
|
||||
if (!result[group]) result[group] = [];
|
||||
result[group].push(item);
|
||||
return result;
|
||||
},
|
||||
{} as Record<string, T[]>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array of objects by a key
|
||||
*/
|
||||
export function sortBy<T>(array: T[], key: keyof T, direction: SortDirection = 'asc'): T[] {
|
||||
return [...array].sort((a, b) => {
|
||||
const aVal = a[key];
|
||||
const bVal = b[key];
|
||||
|
||||
if (aVal < bVal) return direction === 'asc' ? -1 : 1;
|
||||
if (aVal > bVal) return direction === 'asc' ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter an array of objects by multiple filters
|
||||
*/
|
||||
export function filterBy<T>(array: T[], filters: Filter[]): T[] {
|
||||
return array.filter((item) => {
|
||||
return filters.every((filter) => {
|
||||
const value = (item as any)[filter.field];
|
||||
const filterValue = filter.value;
|
||||
|
||||
switch (filter.operator) {
|
||||
case 'equals':
|
||||
return value === filterValue;
|
||||
case 'contains':
|
||||
return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
|
||||
case 'startsWith':
|
||||
return String(value).toLowerCase().startsWith(String(filterValue).toLowerCase());
|
||||
case 'endsWith':
|
||||
return String(value).toLowerCase().endsWith(String(filterValue).toLowerCase());
|
||||
case 'gt':
|
||||
return value > filterValue;
|
||||
case 'lt':
|
||||
return value < filterValue;
|
||||
case 'gte':
|
||||
return value >= filterValue;
|
||||
case 'lte':
|
||||
return value <= filterValue;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep clone an object
|
||||
*/
|
||||
export function deepClone<T>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge objects deeply
|
||||
*/
|
||||
export function deepMerge<T extends Record<string, any>>(...objects: Partial<T>[]): T {
|
||||
const result = {} as any;
|
||||
|
||||
for (const obj of objects) {
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
|
||||
result[key] = deepMerge(result[key] || {}, val);
|
||||
} else {
|
||||
result[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove duplicates from array
|
||||
*/
|
||||
export function unique<T>(array: T[]): T[] {
|
||||
return [...new Set(array)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove duplicates from array of objects by key
|
||||
*/
|
||||
export function uniqueBy<T>(array: T[], key: keyof T): T[] {
|
||||
const seen = new Set();
|
||||
return array.filter((item) => {
|
||||
const value = item[key];
|
||||
if (seen.has(value)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(value);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk array into smaller arrays
|
||||
*/
|
||||
export function chunk<T>(array: T[], size: number): T[][] {
|
||||
const chunks: T[][] = [];
|
||||
for (let i = 0; i < array.length; i += size) {
|
||||
chunks.push(array.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten nested array
|
||||
*/
|
||||
export function flatten<T>(array: (T | T[])[]): T[] {
|
||||
return array.reduce<T[]>((flat, item) => {
|
||||
return flat.concat(Array.isArray(item) ? flatten(item) : item);
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick specific keys from object
|
||||
*/
|
||||
export function pick<T extends Record<string, any>, K extends keyof T>(
|
||||
obj: T,
|
||||
keys: K[]
|
||||
): Pick<T, K> {
|
||||
const result = {} as Pick<T, K>;
|
||||
keys.forEach((key) => {
|
||||
if (key in obj) {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Omit specific keys from object
|
||||
*/
|
||||
export function omit<T extends Record<string, any>, K extends keyof T>(
|
||||
obj: T,
|
||||
keys: K[]
|
||||
): Omit<T, K> {
|
||||
const result = { ...obj };
|
||||
keys.forEach((key) => {
|
||||
delete result[key];
|
||||
});
|
||||
return result as Omit<T, K>;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Chart & Analytics Utilities
|
||||
* Functions for data visualization and analytics
|
||||
*/
|
||||
|
||||
import type { DataPoint } from '../types';
|
||||
|
||||
/**
|
||||
* Generate chart colors
|
||||
*/
|
||||
export function generateChartColors(count: number): string[] {
|
||||
const baseColors = [
|
||||
'#3b82f6', // blue
|
||||
'#10b981', // emerald
|
||||
'#f59e0b', // amber
|
||||
'#ef4444', // red
|
||||
'#8b5cf6', // violet
|
||||
'#ec4899', // pink
|
||||
'#14b8a6', // teal
|
||||
'#f97316', // orange
|
||||
];
|
||||
|
||||
const colors: string[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
colors.push(baseColors[i % baseColors.length]);
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate percentage change
|
||||
*/
|
||||
export function calculatePercentageChange(oldValue: number, newValue: number): number {
|
||||
if (oldValue === 0) return newValue > 0 ? 100 : 0;
|
||||
return ((newValue - oldValue) / Math.abs(oldValue)) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate data points by interval
|
||||
*/
|
||||
export function aggregateDataPoints(
|
||||
points: DataPoint[],
|
||||
interval: 'hour' | 'day' | 'week' | 'month'
|
||||
): DataPoint[] {
|
||||
const grouped = new Map<string, DataPoint[]>();
|
||||
|
||||
points.forEach((point) => {
|
||||
const date = new Date(point.timestamp);
|
||||
let key: string;
|
||||
|
||||
switch (interval) {
|
||||
case 'hour':
|
||||
key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${date.getHours()}`;
|
||||
break;
|
||||
case 'day':
|
||||
key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
||||
break;
|
||||
case 'week': {
|
||||
const weekNumber = Math.floor(date.getDate() / 7);
|
||||
key = `${date.getFullYear()}-${date.getMonth()}-W${weekNumber}`;
|
||||
break;
|
||||
}
|
||||
case 'month':
|
||||
key = `${date.getFullYear()}-${date.getMonth()}`;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!grouped.has(key)) {
|
||||
grouped.set(key, []);
|
||||
}
|
||||
grouped.get(key)?.push(point);
|
||||
});
|
||||
|
||||
const aggregated: DataPoint[] = [];
|
||||
grouped.forEach((group, key) => {
|
||||
const sum = group.reduce((acc, point) => acc + point.value, 0);
|
||||
const avg = sum / group.length;
|
||||
|
||||
aggregated.push({
|
||||
timestamp: group[0].timestamp,
|
||||
value: avg,
|
||||
label: key,
|
||||
metadata: {
|
||||
count: group.length,
|
||||
sum,
|
||||
avg,
|
||||
min: Math.min(...group.map((p) => p.value)),
|
||||
max: Math.max(...group.map((p) => p.value)),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return aggregated.sort(
|
||||
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate statistics for data points
|
||||
*/
|
||||
export function calculateStatistics(values: number[]): {
|
||||
min: number;
|
||||
max: number;
|
||||
mean: number;
|
||||
median: number;
|
||||
sum: number;
|
||||
count: number;
|
||||
stdDev: number;
|
||||
} {
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const sum = values.reduce((acc, val) => acc + val, 0);
|
||||
const mean = sum / values.length;
|
||||
|
||||
const median =
|
||||
values.length % 2 === 0
|
||||
? (sorted[values.length / 2 - 1] + sorted[values.length / 2]) / 2
|
||||
: sorted[Math.floor(values.length / 2)];
|
||||
|
||||
const variance = values.reduce((acc, val) => acc + (val - mean) ** 2, 0) / values.length;
|
||||
const stdDev = Math.sqrt(variance);
|
||||
|
||||
return {
|
||||
min: Math.min(...values),
|
||||
max: Math.max(...values),
|
||||
mean,
|
||||
median,
|
||||
sum,
|
||||
count: values.length,
|
||||
stdDev,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate trend line data
|
||||
*/
|
||||
export function generateTrendLine(points: DataPoint[]): DataPoint[] {
|
||||
if (points.length < 2) return points;
|
||||
|
||||
// Simple linear regression
|
||||
const n = points.length;
|
||||
const sumX = points.reduce((acc, _, i) => acc + i, 0);
|
||||
const sumY = points.reduce((acc, p) => acc + p.value, 0);
|
||||
const sumXY = points.reduce((acc, p, i) => acc + i * p.value, 0);
|
||||
const sumX2 = points.reduce((acc, _, i) => acc + i * i, 0);
|
||||
|
||||
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
|
||||
const intercept = (sumY - slope * sumX) / n;
|
||||
|
||||
return points.map((point, i) => ({
|
||||
...point,
|
||||
value: slope * i + intercept,
|
||||
metadata: {
|
||||
...point.metadata,
|
||||
isTrendLine: true,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate moving average
|
||||
*/
|
||||
export function calculateMovingAverage(values: number[], window: number): number[] {
|
||||
if (window > values.length) return values;
|
||||
|
||||
const result: number[] = [];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const start = Math.max(0, i - window + 1);
|
||||
const windowValues = values.slice(start, i + 1);
|
||||
const avg = windowValues.reduce((acc, val) => acc + val, 0) / windowValues.length;
|
||||
result.push(avg);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Date & Time Utilities
|
||||
* Functions for date/time formatting and manipulation
|
||||
*/
|
||||
|
||||
import type { TimeRange, Timestamp } from '../types';
|
||||
|
||||
/**
|
||||
* Format a timestamp to a human-readable date string
|
||||
*/
|
||||
export function formatDate(timestamp: Timestamp, options?: Intl.DateTimeFormatOptions): string {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleDateString(
|
||||
undefined,
|
||||
options || {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp to a human-readable time string
|
||||
*/
|
||||
export function formatTime(timestamp: Timestamp, options?: Intl.DateTimeFormatOptions): string {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString(
|
||||
undefined,
|
||||
options || {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp to a human-readable date and time string
|
||||
*/
|
||||
export function formatDateTime(timestamp: Timestamp): string {
|
||||
return `${formatDate(timestamp)} ${formatTime(timestamp)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relative time string (e.g., "2 hours ago")
|
||||
*/
|
||||
export function getRelativeTime(timestamp: Timestamp): string {
|
||||
const now = Date.now();
|
||||
const then = new Date(timestamp).getTime();
|
||||
const diff = now - then;
|
||||
|
||||
const seconds = Math.floor(diff / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
const weeks = Math.floor(days / 7);
|
||||
const months = Math.floor(days / 30);
|
||||
const years = Math.floor(days / 365);
|
||||
|
||||
if (seconds < 60) return `${seconds} second${seconds !== 1 ? 's' : ''} ago`;
|
||||
if (minutes < 60) return `${minutes} minute${minutes !== 1 ? 's' : ''} ago`;
|
||||
if (hours < 24) return `${hours} hour${hours !== 1 ? 's' : ''} ago`;
|
||||
if (days < 7) return `${days} day${days !== 1 ? 's' : ''} ago`;
|
||||
if (weeks < 4) return `${weeks} week${weeks !== 1 ? 's' : ''} ago`;
|
||||
if (months < 12) return `${months} month${months !== 1 ? 's' : ''} ago`;
|
||||
return `${years} year${years !== 1 ? 's' : ''} ago`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date range from preset
|
||||
*/
|
||||
export function getDateRangeFromPreset(preset: TimeRange['preset']): TimeRange {
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
|
||||
switch (preset) {
|
||||
case 'today':
|
||||
return {
|
||||
start: today,
|
||||
end: now,
|
||||
preset,
|
||||
};
|
||||
case 'yesterday': {
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return {
|
||||
start: yesterday,
|
||||
end: today,
|
||||
preset,
|
||||
};
|
||||
}
|
||||
case 'last7days': {
|
||||
const weekAgo = new Date(today);
|
||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||
return {
|
||||
start: weekAgo,
|
||||
end: now,
|
||||
preset,
|
||||
};
|
||||
}
|
||||
case 'last30days': {
|
||||
const monthAgo = new Date(today);
|
||||
monthAgo.setDate(monthAgo.getDate() - 30);
|
||||
return {
|
||||
start: monthAgo,
|
||||
end: now,
|
||||
preset,
|
||||
};
|
||||
}
|
||||
case 'thisMonth': {
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
return {
|
||||
start: startOfMonth,
|
||||
end: now,
|
||||
preset,
|
||||
};
|
||||
}
|
||||
case 'lastMonth': {
|
||||
const startOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const endOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0);
|
||||
return {
|
||||
start: startOfLastMonth,
|
||||
end: endOfLastMonth,
|
||||
preset,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return {
|
||||
start: today,
|
||||
end: now,
|
||||
preset: 'today',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration in milliseconds to human-readable string
|
||||
*/
|
||||
export function formatDuration(ms: number): string {
|
||||
if (ms < 1000) return `${ms}ms`;
|
||||
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
if (seconds < 60) return `${seconds}s`;
|
||||
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
|
||||
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours}h ${minutes % 60}m`;
|
||||
|
||||
const days = Math.floor(hours / 24);
|
||||
return `${days}d ${hours % 24}h`;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Formatting Utilities
|
||||
* Functions for number and string formatting
|
||||
*/
|
||||
|
||||
import type { Metric, Status } from '../types';
|
||||
|
||||
/**
|
||||
* Format a number with commas
|
||||
*/
|
||||
export function formatNumber(value: number, decimals = 0): string {
|
||||
return value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number as currency
|
||||
*/
|
||||
export function formatCurrency(value: number, currency = 'USD'): string {
|
||||
return value.toLocaleString(undefined, {
|
||||
style: 'currency',
|
||||
currency,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number as a percentage
|
||||
*/
|
||||
export function formatPercentage(value: number, decimals = 1): string {
|
||||
return `${(value * 100).toFixed(decimals)}%`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format bytes to human-readable size
|
||||
*/
|
||||
export function formatBytes(bytes: number, decimals = 2): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${Number.parseFloat((bytes / k ** i).toFixed(decimals))} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abbreviate large numbers (e.g., 1.2K, 3.4M)
|
||||
*/
|
||||
export function abbreviateNumber(value: number): string {
|
||||
if (value < 1000) return value.toString();
|
||||
|
||||
const suffixes = ['', 'K', 'M', 'B', 'T'];
|
||||
const suffixNum = Math.floor(`${value}`.length / 3);
|
||||
const shortValue = Number.parseFloat(
|
||||
(suffixNum !== 0 ? value / 1000 ** suffixNum : value).toPrecision(2)
|
||||
);
|
||||
|
||||
if (shortValue % 1 !== 0) {
|
||||
return shortValue.toFixed(1) + suffixes[suffixNum];
|
||||
}
|
||||
|
||||
return shortValue + suffixes[suffixNum];
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a string to a maximum length
|
||||
*/
|
||||
export function truncate(str: string, maxLength: number, suffix = '...'): string {
|
||||
if (str.length <= maxLength) return str;
|
||||
return str.slice(0, maxLength - suffix.length) + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to title case
|
||||
*/
|
||||
export function toTitleCase(str: string): string {
|
||||
return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to slug format
|
||||
*/
|
||||
export function toSlug(str: string): string {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^\w\s-]/g, '')
|
||||
.replace(/[\s_-]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random ID
|
||||
*/
|
||||
export function generateId(prefix = ''): string {
|
||||
const random = Math.random().toString(36).substr(2, 9);
|
||||
const timestamp = Date.now().toString(36);
|
||||
return prefix ? `${prefix}_${timestamp}_${random}` : `${timestamp}_${random}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract initials from a name
|
||||
*/
|
||||
export function getInitials(name: string): string {
|
||||
return name
|
||||
.split(' ')
|
||||
.map((word) => word[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status color/variant
|
||||
*/
|
||||
export function getStatusColor(status: Status): string {
|
||||
const colors: Record<Status, string> = {
|
||||
active: 'green',
|
||||
inactive: 'gray',
|
||||
pending: 'yellow',
|
||||
error: 'red',
|
||||
warning: 'orange',
|
||||
success: 'green',
|
||||
};
|
||||
return colors[status] || 'gray';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status icon
|
||||
*/
|
||||
export function getStatusIcon(status: Status): string {
|
||||
const icons: Record<Status, string> = {
|
||||
active: 'check-circle',
|
||||
inactive: 'x-circle',
|
||||
pending: 'clock',
|
||||
error: 'alert-circle',
|
||||
warning: 'alert-triangle',
|
||||
success: 'check-circle',
|
||||
};
|
||||
return icons[status] || 'circle';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format metric with trend
|
||||
*/
|
||||
export function formatMetricWithTrend(metric: Metric): string {
|
||||
let result = String(metric.value);
|
||||
|
||||
if (metric.unit) {
|
||||
result += ` ${metric.unit}`;
|
||||
}
|
||||
|
||||
if (metric.change !== undefined) {
|
||||
const symbol = metric.change > 0 ? '↑' : metric.change < 0 ? '↓' : '→';
|
||||
const changeStr = Math.abs(metric.change).toFixed(1);
|
||||
result += ` ${symbol} ${changeStr}%`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @sonr.io/com - Utility Functions
|
||||
* Central export for all utility functions
|
||||
*/
|
||||
|
||||
// Date & time utilities
|
||||
export * from './date';
|
||||
|
||||
// Formatting utilities
|
||||
export * from './format';
|
||||
|
||||
// Validation utilities
|
||||
export * from './validation';
|
||||
|
||||
// Array & object utilities
|
||||
export * from './array';
|
||||
|
||||
// Chart & analytics utilities
|
||||
export * from './chart';
|
||||
|
||||
// Export utility collections for convenience
|
||||
export {
|
||||
formatDate,
|
||||
formatTime,
|
||||
formatDateTime,
|
||||
getRelativeTime,
|
||||
getDateRangeFromPreset,
|
||||
formatDuration,
|
||||
} from './date';
|
||||
|
||||
export {
|
||||
formatNumber,
|
||||
formatCurrency,
|
||||
formatPercentage,
|
||||
formatBytes,
|
||||
abbreviateNumber,
|
||||
truncate,
|
||||
toTitleCase,
|
||||
toSlug,
|
||||
generateId,
|
||||
getInitials,
|
||||
getStatusColor,
|
||||
getStatusIcon,
|
||||
formatMetricWithTrend,
|
||||
} from './format';
|
||||
|
||||
export {
|
||||
isValidEmail,
|
||||
isValidUrl,
|
||||
isValidDomain,
|
||||
isValidApiKey,
|
||||
isValidDID,
|
||||
isValidPhoneNumber,
|
||||
schemas,
|
||||
createValidator,
|
||||
} from './validation';
|
||||
|
||||
export {
|
||||
groupBy,
|
||||
sortBy,
|
||||
filterBy,
|
||||
deepClone,
|
||||
deepMerge,
|
||||
unique,
|
||||
uniqueBy,
|
||||
chunk,
|
||||
flatten,
|
||||
pick,
|
||||
omit,
|
||||
} from './array';
|
||||
|
||||
export {
|
||||
generateChartColors,
|
||||
calculatePercentageChange,
|
||||
aggregateDataPoints,
|
||||
calculateStatistics,
|
||||
generateTrendLine,
|
||||
calculateMovingAverage,
|
||||
} from './chart';
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Validation Utilities
|
||||
* Functions for validating common formats and patterns
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Validate email address
|
||||
*/
|
||||
export function isValidEmail(email: string): boolean {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate URL
|
||||
*/
|
||||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate domain
|
||||
*/
|
||||
export function isValidDomain(domain: string): boolean {
|
||||
const domainRegex =
|
||||
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
||||
return domainRegex.test(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate API key format
|
||||
*/
|
||||
export function isValidApiKey(key: string): boolean {
|
||||
const apiKeyRegex = /^sk_(test|live)_[a-zA-Z0-9]{24,}$/;
|
||||
return apiKeyRegex.test(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate DID format
|
||||
*/
|
||||
export function isValidDID(did: string): boolean {
|
||||
// W3C DID format: did:method:method-specific-id
|
||||
const didRegex = /^did:[a-z0-9]+:[a-zA-Z0-9:.-]+$/;
|
||||
return didRegex.test(did);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate phone number
|
||||
*/
|
||||
export function isValidPhoneNumber(phone: string): boolean {
|
||||
// Basic international format
|
||||
const phoneRegex = /^\+?[1-9]\d{1,14}$/;
|
||||
return phoneRegex.test(phone.replace(/[\s-()]/g, ''));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Zod Validation Schemas
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Common validation schemas
|
||||
*/
|
||||
export const schemas = {
|
||||
/**
|
||||
* Email schema
|
||||
*/
|
||||
email: z.string().email('Invalid email address'),
|
||||
|
||||
/**
|
||||
* URL schema
|
||||
*/
|
||||
url: z.string().url('Invalid URL'),
|
||||
|
||||
/**
|
||||
* Domain schema
|
||||
*/
|
||||
domain: z.string().refine(isValidDomain, 'Invalid domain'),
|
||||
|
||||
/**
|
||||
* API key schema
|
||||
*/
|
||||
apiKey: z.string().refine(isValidApiKey, 'Invalid API key format'),
|
||||
|
||||
/**
|
||||
* DID schema
|
||||
*/
|
||||
did: z.string().refine(isValidDID, 'Invalid DID format'),
|
||||
|
||||
/**
|
||||
* Phone number schema
|
||||
*/
|
||||
phoneNumber: z.string().refine(isValidPhoneNumber, 'Invalid phone number'),
|
||||
|
||||
/**
|
||||
* Service registration schema
|
||||
*/
|
||||
serviceRegistration: z.object({
|
||||
name: z.string().min(3, 'Name must be at least 3 characters').max(50),
|
||||
description: z.string().optional(),
|
||||
domain: z.string().refine(isValidDomain, 'Invalid domain').optional(),
|
||||
permissions: z.array(z.string()).optional(),
|
||||
tags: z.array(z.string()).max(10, 'Maximum 10 tags allowed').optional(),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Domain verification schema
|
||||
*/
|
||||
domainVerification: z.object({
|
||||
domain: z.string().refine(isValidDomain, 'Invalid domain'),
|
||||
method: z.enum(['dns', 'http']),
|
||||
autoVerify: z.boolean().optional(),
|
||||
}),
|
||||
|
||||
/**
|
||||
* User profile schema
|
||||
*/
|
||||
userProfile: z.object({
|
||||
username: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(30)
|
||||
.regex(/^[a-zA-Z0-9_-]+$/, 'Username can only contain letters, numbers, - and _'),
|
||||
email: z.string().email().optional(),
|
||||
name: z.string().max(100).optional(),
|
||||
avatar: z.string().url().optional(),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Pagination schema
|
||||
*/
|
||||
pagination: z.object({
|
||||
page: z.number().int().positive(),
|
||||
pageSize: z.number().int().positive().max(100),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Time range schema
|
||||
*/
|
||||
timeRange: z.object({
|
||||
start: z.date(),
|
||||
end: z.date(),
|
||||
preset: z
|
||||
.enum(['today', 'yesterday', 'last7days', 'last30days', 'thisMonth', 'lastMonth', 'custom'])
|
||||
.optional(),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Permission request schema
|
||||
*/
|
||||
permissionRequest: z.object({
|
||||
resource: z.string().min(1),
|
||||
action: z.string().min(1),
|
||||
effect: z.enum(['allow', 'deny']),
|
||||
conditions: z
|
||||
.array(
|
||||
z.object({
|
||||
field: z.string(),
|
||||
operator: z.enum(['equals', 'notEquals', 'contains', 'in', 'notIn']),
|
||||
value: z.any(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a validator function from a Zod schema
|
||||
*/
|
||||
export function createValidator<T>(schema: z.ZodSchema<T>) {
|
||||
return (data: unknown): { success: boolean; data?: T; errors?: z.ZodError } => {
|
||||
const result = schema.safeParse(data);
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data };
|
||||
}
|
||||
return { success: false, errors: result.error };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["dist", "node_modules", "**/*.test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
'types/index': 'src/types/index.ts',
|
||||
'utils/index': 'src/utils/index.ts',
|
||||
'constants/index': 'src/constants/index.ts',
|
||||
},
|
||||
format: ['esm', 'cjs'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
treeshake: true,
|
||||
external: ['zod'],
|
||||
});
|
||||
Reference in New Issue
Block a user