Initial commit: add .gitignore and README
This commit is contained in:
12
.changeset/config.json
Normal file
12
.changeset/config.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
|
||||
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
vendor/
|
||||
|
||||
# Package manager lock files (optional: uncomment to ignore)
|
||||
# package-lock.json
|
||||
# yarn.lock
|
||||
|
||||
# Environment and secrets
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
*.env.backup
|
||||
.env.backup.*
|
||||
|
||||
# Logs and temp
|
||||
*.log
|
||||
logs/
|
||||
*.tmp
|
||||
*.temp
|
||||
*.tmp.*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Build / output
|
||||
dist/
|
||||
build/
|
||||
.next/
|
||||
out/
|
||||
*.pyc
|
||||
__pycache__/
|
||||
.eggs/
|
||||
*.egg-info/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Optional
|
||||
.reports/
|
||||
reports/
|
||||
10
.npmrc
Normal file
10
.npmrc
Normal file
@@ -0,0 +1,10 @@
|
||||
# Private npm registry configuration
|
||||
# Uncomment and configure when registry is set up
|
||||
|
||||
# @workspace:registry=http://verdaccio:4873/
|
||||
# //verdaccio:4873/:_authToken=${NPM_TOKEN}
|
||||
|
||||
# Or for GitHub Packages:
|
||||
# @workspace:registry=https://npm.pkg.github.com
|
||||
# //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
|
||||
|
||||
142
README.md
Normal file
142
README.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Workspace Shared Packages
|
||||
|
||||
**Status**: 🚧 **In Development**
|
||||
**Purpose**: Shared packages and libraries for workspace projects
|
||||
**Last Updated**: 2025-01-27
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This monorepo contains shared packages and libraries used across multiple projects in the workspace. It enables code reuse, consistent patterns, and simplified dependency management.
|
||||
|
||||
---
|
||||
|
||||
## Packages
|
||||
|
||||
### @workspace/shared-types
|
||||
**Status**: 🚧 Planned
|
||||
**Purpose**: Common TypeScript types and interfaces
|
||||
**Usage**: Used across dbis_core, the_order, Sankofa, and others
|
||||
|
||||
### @workspace/shared-utils
|
||||
**Status**: 🚧 Planned
|
||||
**Purpose**: Common utility functions
|
||||
**Usage**: Used in 20+ projects
|
||||
|
||||
### @workspace/shared-config
|
||||
**Status**: 🚧 Planned
|
||||
**Purpose**: Shared configuration schemas and validation
|
||||
**Usage**: All projects with configuration
|
||||
|
||||
### @workspace/shared-constants
|
||||
**Status**: 🚧 Planned
|
||||
**Purpose**: Shared constants and enums
|
||||
**Usage**: DBIS projects, DeFi projects
|
||||
|
||||
### @workspace/api-client
|
||||
**Status**: 🚧 Planned
|
||||
**Purpose**: Common API client utilities
|
||||
**Usage**: Frontend projects, API consumers
|
||||
|
||||
### @workspace/validation
|
||||
**Status**: 🚧 Planned
|
||||
**Purpose**: Zod schemas and validators
|
||||
**Usage**: Multiple backend services
|
||||
|
||||
### @workspace/blockchain
|
||||
**Status**: 🚧 Planned
|
||||
**Purpose**: Blockchain utilities and helpers
|
||||
**Usage**: Blockchain projects
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
- Node.js >= 18.0.0
|
||||
- pnpm >= 8.0.0
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Build all packages
|
||||
pnpm build
|
||||
|
||||
# Run tests
|
||||
pnpm test
|
||||
|
||||
# Lint all packages
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
### Using Packages
|
||||
|
||||
In your project's `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@workspace/shared-types": "workspace:*",
|
||||
"@workspace/shared-utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Creating a New Package
|
||||
|
||||
1. Create package directory: `packages/package-name/`
|
||||
2. Add `package.json` with proper name (`@workspace/package-name`)
|
||||
3. Implement package code
|
||||
4. Add to workspace configuration
|
||||
5. Build and test
|
||||
|
||||
### Publishing
|
||||
|
||||
Packages are published to private npm registry (Verdaccio or GitHub Packages).
|
||||
|
||||
```bash
|
||||
# Build package
|
||||
cd packages/package-name
|
||||
pnpm build
|
||||
|
||||
# Publish
|
||||
pnpm publish --registry=<registry-url>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
workspace-shared/
|
||||
├── packages/ # Shared packages
|
||||
│ ├── shared-types/
|
||||
│ ├── shared-utils/
|
||||
│ ├── shared-config/
|
||||
│ └── ...
|
||||
├── apps/ # Shared applications (if any)
|
||||
├── tools/ # Development tools
|
||||
├── package.json # Root package.json
|
||||
├── pnpm-workspace.yaml
|
||||
└── turbo.json # Turborepo configuration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [Dependency Consolidation Plan](../docs/DEPENDENCY_CONSOLIDATION_PLAN.md)
|
||||
- [Integration & Streamlining Plan](../INTEGRATION_STREAMLINING_PLAN.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-01-27
|
||||
|
||||
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "workspace-shared",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Shared packages and libraries for workspace projects",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"test": "turbo run test",
|
||||
"lint": "turbo run lint",
|
||||
"type-check": "turbo run type-check",
|
||||
"clean": "turbo run clean"
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^2.0.0",
|
||||
"typescript": "^5.5.4",
|
||||
"@types/node": "^20.11.0",
|
||||
"prettier": "^3.3.3",
|
||||
"eslint": "^9.17.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"pnpm": ">=8.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.0"
|
||||
}
|
||||
|
||||
29
packages/api-client/package.json
Normal file
29
packages/api-client/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@workspace/api-client",
|
||||
"version": "1.0.0",
|
||||
"description": "Common API client utilities",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.5",
|
||||
"@workspace/shared-types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
|
||||
32
packages/api-client/src/client.ts
Normal file
32
packages/api-client/src/client.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* API client factory
|
||||
*/
|
||||
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import { setupInterceptors } from './interceptors';
|
||||
|
||||
export interface ApiClientConfig extends AxiosRequestConfig {
|
||||
baseURL: string;
|
||||
timeout?: number;
|
||||
retries?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create configured API client
|
||||
*/
|
||||
export function createApiClient(config: ApiClientConfig): AxiosInstance {
|
||||
const client = axios.create({
|
||||
baseURL: config.baseURL,
|
||||
timeout: config.timeout || 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...config.headers,
|
||||
},
|
||||
});
|
||||
|
||||
// Setup interceptors
|
||||
setupInterceptors(client, config.retries || 3);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
9
packages/api-client/src/index.ts
Normal file
9
packages/api-client/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @workspace/api-client
|
||||
* Common API client utilities
|
||||
*/
|
||||
|
||||
export * from './client';
|
||||
export * from './interceptors';
|
||||
export * from './types';
|
||||
|
||||
56
packages/api-client/src/interceptors.ts
Normal file
56
packages/api-client/src/interceptors.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Axios interceptors
|
||||
*/
|
||||
|
||||
import { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
/**
|
||||
* Setup request and response interceptors
|
||||
*/
|
||||
export function setupInterceptors(client: AxiosInstance, maxRetries: number = 3): void {
|
||||
// Request interceptor
|
||||
client.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
// Add auth token if available
|
||||
const token = process.env.API_TOKEN;
|
||||
if (token && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Response interceptor with retry logic
|
||||
client.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
const config = error.config as InternalAxiosRequestConfig & { __retryCount?: number };
|
||||
|
||||
if (!config) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
config.__retryCount = config.__retryCount || 0;
|
||||
|
||||
// Retry on network errors or 5xx errors
|
||||
if (
|
||||
(error.code === 'ECONNABORTED' || (error.response?.status && error.response.status >= 500)) &&
|
||||
config.__retryCount < maxRetries
|
||||
) {
|
||||
config.__retryCount += 1;
|
||||
|
||||
// Exponential backoff
|
||||
const delay = Math.pow(2, config.__retryCount) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
return client(config);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
21
packages/api-client/src/types.ts
Normal file
21
packages/api-client/src/types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* API client types
|
||||
*/
|
||||
|
||||
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
|
||||
export interface ApiRequestConfig extends AxiosRequestConfig {
|
||||
retries?: number;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T = unknown> extends AxiosResponse<T> {
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
message: string;
|
||||
code?: string;
|
||||
status?: number;
|
||||
details?: unknown;
|
||||
}
|
||||
|
||||
20
packages/api-client/tsconfig.json
Normal file
20
packages/api-client/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
29
packages/blockchain/package.json
Normal file
29
packages/blockchain/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@workspace/blockchain",
|
||||
"version": "1.0.0",
|
||||
"description": "Blockchain utilities and helpers",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"ethers": "^6.9.0",
|
||||
"@workspace/shared-types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
|
||||
30
packages/blockchain/src/contracts.ts
Normal file
30
packages/blockchain/src/contracts.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Contract utilities
|
||||
*/
|
||||
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
/**
|
||||
* Get contract instance
|
||||
*/
|
||||
export function getContract(
|
||||
address: string,
|
||||
abi: ethers.InterfaceAbi,
|
||||
signerOrProvider: ethers.Signer | ethers.Provider
|
||||
): ethers.Contract {
|
||||
return new ethers.Contract(address, abi, signerOrProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy contract
|
||||
*/
|
||||
export async function deployContract(
|
||||
signer: ethers.Signer,
|
||||
contractFactory: ethers.ContractFactory,
|
||||
...args: unknown[]
|
||||
): Promise<ethers.Contract> {
|
||||
const contract = await contractFactory.deploy(...args);
|
||||
await contract.waitForDeployment();
|
||||
return contract;
|
||||
}
|
||||
|
||||
51
packages/blockchain/src/ethers.ts
Normal file
51
packages/blockchain/src/ethers.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Ethers.js utilities
|
||||
*/
|
||||
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
/**
|
||||
* Create provider from RPC URL
|
||||
*/
|
||||
export function createProvider(rpcUrl: string): ethers.JsonRpcProvider {
|
||||
return new ethers.JsonRpcProvider(rpcUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create wallet from private key
|
||||
*/
|
||||
export function createWallet(
|
||||
privateKey: string,
|
||||
provider?: ethers.Provider
|
||||
): ethers.Wallet {
|
||||
return new ethers.Wallet(privateKey, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format ether value
|
||||
*/
|
||||
export function formatEther(value: bigint): string {
|
||||
return ethers.formatEther(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse ether value
|
||||
*/
|
||||
export function parseEther(value: string): bigint {
|
||||
return ethers.parseEther(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format units
|
||||
*/
|
||||
export function formatUnits(value: bigint, decimals: number = 18): string {
|
||||
return ethers.formatUnits(value, decimals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse units
|
||||
*/
|
||||
export function parseUnits(value: string, decimals: number = 18): bigint {
|
||||
return ethers.parseUnits(value, decimals);
|
||||
}
|
||||
|
||||
10
packages/blockchain/src/index.ts
Normal file
10
packages/blockchain/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @workspace/blockchain
|
||||
* Blockchain utilities and helpers
|
||||
*/
|
||||
|
||||
export * from './ethers';
|
||||
export * from './transactions';
|
||||
export * from './contracts';
|
||||
export * from './types';
|
||||
|
||||
45
packages/blockchain/src/transactions.ts
Normal file
45
packages/blockchain/src/transactions.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Transaction utilities
|
||||
*/
|
||||
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
export interface TransactionOptions {
|
||||
gasLimit?: bigint;
|
||||
gasPrice?: bigint;
|
||||
maxFeePerGas?: bigint;
|
||||
maxPriorityFeePerGas?: bigint;
|
||||
value?: bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for transaction confirmation
|
||||
*/
|
||||
export async function waitForTransaction(
|
||||
provider: ethers.Provider,
|
||||
txHash: string,
|
||||
confirmations: number = 1
|
||||
): Promise<ethers.TransactionReceipt | null> {
|
||||
return provider.waitForTransaction(txHash, confirmations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction receipt
|
||||
*/
|
||||
export async function getTransactionReceipt(
|
||||
provider: ethers.Provider,
|
||||
txHash: string
|
||||
): Promise<ethers.TransactionReceipt | null> {
|
||||
return provider.getTransactionReceipt(txHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate gas for transaction
|
||||
*/
|
||||
export async function estimateGas(
|
||||
provider: ethers.Provider,
|
||||
transaction: ethers.TransactionRequest
|
||||
): Promise<bigint> {
|
||||
return provider.estimateGas(transaction);
|
||||
}
|
||||
|
||||
25
packages/blockchain/src/types.ts
Normal file
25
packages/blockchain/src/types.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Blockchain types
|
||||
*/
|
||||
|
||||
export interface NetworkConfig {
|
||||
name: string;
|
||||
chainId: number;
|
||||
rpcUrl: string;
|
||||
explorerUrl?: string;
|
||||
}
|
||||
|
||||
export interface TokenInfo {
|
||||
address: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export interface TransactionStatus {
|
||||
hash: string;
|
||||
status: 'pending' | 'confirmed' | 'failed';
|
||||
blockNumber?: number;
|
||||
confirmations: number;
|
||||
}
|
||||
|
||||
20
packages/blockchain/tsconfig.json
Normal file
20
packages/blockchain/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
31
packages/shared-auth/package.json
Normal file
31
packages/shared-auth/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@workspace/shared-auth",
|
||||
"version": "1.0.0",
|
||||
"description": "Shared authentication and authorization utilities",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"bcryptjs": "^2.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"@types/jsonwebtoken": "^9.0.4",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
|
||||
10
packages/shared-auth/src/index.ts
Normal file
10
packages/shared-auth/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @workspace/shared-auth
|
||||
* Shared authentication and authorization utilities
|
||||
*/
|
||||
|
||||
export * from './jwt';
|
||||
export * from './password';
|
||||
export * from './permissions';
|
||||
export * from './types';
|
||||
|
||||
55
packages/shared-auth/src/jwt.ts
Normal file
55
packages/shared-auth/src/jwt.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* JWT token utilities
|
||||
*/
|
||||
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export interface TokenPayload {
|
||||
userId: string;
|
||||
email: string;
|
||||
roles?: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface TokenOptions {
|
||||
expiresIn?: string | number;
|
||||
issuer?: string;
|
||||
audience?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JWT token
|
||||
*/
|
||||
export function generateToken(
|
||||
payload: TokenPayload,
|
||||
secret: string,
|
||||
options?: TokenOptions
|
||||
): string {
|
||||
return jwt.sign(payload, secret, {
|
||||
expiresIn: options?.expiresIn || '24h',
|
||||
issuer: options?.issuer,
|
||||
audience: options?.audience,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify JWT token
|
||||
*/
|
||||
export function verifyToken<T extends TokenPayload>(
|
||||
token: string,
|
||||
secret: string
|
||||
): T {
|
||||
return jwt.verify(token, secret) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode JWT token (without verification)
|
||||
*/
|
||||
export function decodeToken<T extends TokenPayload>(token: string): T | null {
|
||||
try {
|
||||
return jwt.decode(token) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
25
packages/shared-auth/src/password.ts
Normal file
25
packages/shared-auth/src/password.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Password hashing utilities
|
||||
*/
|
||||
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
/**
|
||||
* Hash password
|
||||
*/
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify password
|
||||
*/
|
||||
export async function verifyPassword(
|
||||
password: string,
|
||||
hash: string
|
||||
): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
63
packages/shared-auth/src/permissions.ts
Normal file
63
packages/shared-auth/src/permissions.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Permission checking utilities
|
||||
*/
|
||||
|
||||
export type Permission = string;
|
||||
export type Role = string;
|
||||
|
||||
export interface UserPermissions {
|
||||
roles: Role[];
|
||||
permissions: Permission[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has role
|
||||
*/
|
||||
export function hasRole(user: UserPermissions, role: Role): boolean {
|
||||
return user.roles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has permission
|
||||
*/
|
||||
export function hasPermission(
|
||||
user: UserPermissions,
|
||||
permission: Permission
|
||||
): boolean {
|
||||
return user.permissions.includes(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has any of the required roles
|
||||
*/
|
||||
export function hasAnyRole(user: UserPermissions, roles: Role[]): boolean {
|
||||
return roles.some((role) => hasRole(user, role));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has all required roles
|
||||
*/
|
||||
export function hasAllRoles(user: UserPermissions, roles: Role[]): boolean {
|
||||
return roles.every((role) => hasRole(user, role));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has any of the required permissions
|
||||
*/
|
||||
export function hasAnyPermission(
|
||||
user: UserPermissions,
|
||||
permissions: Permission[]
|
||||
): boolean {
|
||||
return permissions.some((permission) => hasPermission(user, permission));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has all required permissions
|
||||
*/
|
||||
export function hasAllPermissions(
|
||||
user: UserPermissions,
|
||||
permissions: Permission[]
|
||||
): boolean {
|
||||
return permissions.every((permission) => hasPermission(user, permission));
|
||||
}
|
||||
|
||||
23
packages/shared-auth/src/types.ts
Normal file
23
packages/shared-auth/src/types.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Authentication and authorization types
|
||||
*/
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
roles: string[];
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export interface AuthContext {
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
userId: string;
|
||||
token: string;
|
||||
expiresAt: Date;
|
||||
refreshToken?: string;
|
||||
}
|
||||
|
||||
20
packages/shared-auth/tsconfig.json
Normal file
20
packages/shared-auth/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
29
packages/shared-config/package.json
Normal file
29
packages/shared-config/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@workspace/shared-config",
|
||||
"version": "1.0.0",
|
||||
"description": "Shared configuration schemas and validation",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.23.8",
|
||||
"dotenv": "^16.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
|
||||
55
packages/shared-config/src/env.ts
Normal file
55
packages/shared-config/src/env.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Environment variable utilities
|
||||
*/
|
||||
|
||||
import { config } from 'dotenv';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Load environment variables
|
||||
*/
|
||||
export function loadEnv(envFile?: string): void {
|
||||
config({ path: envFile });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment variable with default
|
||||
*/
|
||||
export function getEnv(key: string, defaultValue?: string): string {
|
||||
const value = process.env[key];
|
||||
if (value === undefined) {
|
||||
if (defaultValue !== undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
throw new Error(`Environment variable ${key} is not set`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment variable as number
|
||||
*/
|
||||
export function getEnvNumber(key: string, defaultValue?: number): number {
|
||||
const value = getEnv(key, defaultValue?.toString());
|
||||
const num = Number(value);
|
||||
if (isNaN(num)) {
|
||||
throw new Error(`Environment variable ${key} is not a valid number`);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment variable as boolean
|
||||
*/
|
||||
export function getEnvBoolean(key: string, defaultValue?: boolean): boolean {
|
||||
const value = getEnv(key, defaultValue?.toString());
|
||||
return value.toLowerCase() === 'true' || value === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate environment variables against schema
|
||||
*/
|
||||
export function validateEnv<T>(schema: z.ZodSchema<T>): T {
|
||||
return schema.parse(process.env);
|
||||
}
|
||||
|
||||
9
packages/shared-config/src/index.ts
Normal file
9
packages/shared-config/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @workspace/shared-config
|
||||
* Shared configuration schemas and validation
|
||||
*/
|
||||
|
||||
export * from './env';
|
||||
export * from './schemas';
|
||||
export * from './validation';
|
||||
|
||||
37
packages/shared-config/src/schemas.ts
Normal file
37
packages/shared-config/src/schemas.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Configuration schemas
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Database configuration schema
|
||||
*/
|
||||
export const databaseConfigSchema = z.object({
|
||||
DB_HOST: z.string(),
|
||||
DB_PORT: z.string().transform(Number),
|
||||
DB_NAME: z.string(),
|
||||
DB_USER: z.string(),
|
||||
DB_PASSWORD: z.string(),
|
||||
DB_SSL: z.string().optional().transform((val) => val === 'true'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Server configuration schema
|
||||
*/
|
||||
export const serverConfigSchema = z.object({
|
||||
PORT: z.string().transform(Number).default('3000'),
|
||||
NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),
|
||||
HOST: z.string().default('localhost'),
|
||||
});
|
||||
|
||||
/**
|
||||
* JWT configuration schema
|
||||
*/
|
||||
export const jwtConfigSchema = z.object({
|
||||
JWT_SECRET: z.string(),
|
||||
JWT_EXPIRES_IN: z.string().default('24h'),
|
||||
JWT_ISSUER: z.string().optional(),
|
||||
JWT_AUDIENCE: z.string().optional(),
|
||||
});
|
||||
|
||||
27
packages/shared-config/src/validation.ts
Normal file
27
packages/shared-config/src/validation.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Configuration validation utilities
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Validate configuration object
|
||||
*/
|
||||
export function validateConfig<T>(schema: z.ZodSchema<T>, config: unknown): T {
|
||||
return schema.parse(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration with error handling
|
||||
*/
|
||||
export function validateConfigSafe<T>(
|
||||
schema: z.ZodSchema<T>,
|
||||
config: unknown
|
||||
): { success: true; data: T } | { success: false; error: z.ZodError } {
|
||||
const result = schema.safeParse(config);
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data };
|
||||
}
|
||||
return { success: false, error: result.error };
|
||||
}
|
||||
|
||||
20
packages/shared-config/tsconfig.json
Normal file
20
packages/shared-config/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
25
packages/shared-types/package.json
Normal file
25
packages/shared-types/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@workspace/shared-types",
|
||||
"version": "1.0.0",
|
||||
"description": "Common TypeScript types and interfaces",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
|
||||
29
packages/shared-types/src/api.ts
Normal file
29
packages/shared-types/src/api.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* API-related types
|
||||
*/
|
||||
|
||||
export interface PaginationParams {
|
||||
page: number;
|
||||
limit: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SortParams {
|
||||
field: string;
|
||||
order: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface FilterParams {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
33
packages/shared-types/src/config.ts
Normal file
33
packages/shared-types/src/config.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Configuration-related types
|
||||
*/
|
||||
|
||||
export interface DatabaseConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
username: string;
|
||||
password: string;
|
||||
ssl?: boolean;
|
||||
pool?: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ServerConfig {
|
||||
port: number;
|
||||
host: string;
|
||||
env: 'development' | 'staging' | 'production';
|
||||
cors?: {
|
||||
origin: string | string[];
|
||||
credentials?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LoggingConfig {
|
||||
level: 'debug' | 'info' | 'warn' | 'error';
|
||||
format: 'json' | 'text';
|
||||
destination?: string;
|
||||
}
|
||||
|
||||
20
packages/shared-types/src/database.ts
Normal file
20
packages/shared-types/src/database.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Database-related types
|
||||
*/
|
||||
|
||||
export interface BaseEntity {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
deletedAt?: Date | null;
|
||||
}
|
||||
|
||||
export interface SoftDeletable {
|
||||
deletedAt: Date | null;
|
||||
}
|
||||
|
||||
export interface Timestamped {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
54
packages/shared-types/src/index.ts
Normal file
54
packages/shared-types/src/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @workspace/shared-types
|
||||
* Common TypeScript types and interfaces
|
||||
*/
|
||||
|
||||
// API Response Types
|
||||
export interface ApiResponse<T> {
|
||||
data: T;
|
||||
status: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: unknown;
|
||||
}
|
||||
|
||||
// Database Types
|
||||
export interface BaseEntity {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// Configuration Types
|
||||
export interface DatabaseConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface ServerConfig {
|
||||
port: number;
|
||||
host: string;
|
||||
env: 'development' | 'staging' | 'production';
|
||||
}
|
||||
|
||||
// Common Utility Types
|
||||
export type Nullable<T> = T | null;
|
||||
export type Optional<T> = T | undefined;
|
||||
export type Maybe<T> = T | null | undefined;
|
||||
|
||||
// Status Types
|
||||
export type Status = 'active' | 'inactive' | 'pending' | 'archived';
|
||||
export type Priority = 'low' | 'medium' | 'high' | 'critical';
|
||||
|
||||
// Export all types
|
||||
export * from './api';
|
||||
export * from './database';
|
||||
export * from './config';
|
||||
|
||||
20
packages/shared-types/tsconfig.json
Normal file
20
packages/shared-types/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
30
packages/shared-utils/package.json
Normal file
30
packages/shared-utils/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@workspace/shared-utils",
|
||||
"version": "1.0.0",
|
||||
"description": "Shared utility functions",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": "^3.3.1",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
|
||||
35
packages/shared-utils/src/date.ts
Normal file
35
packages/shared-utils/src/date.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Date utility functions
|
||||
*/
|
||||
|
||||
import { format, parseISO, isValid } from 'date-fns';
|
||||
|
||||
/**
|
||||
* Format date to ISO string
|
||||
*/
|
||||
export function formatDate(date: Date | string, formatStr: string = 'yyyy-MM-dd'): string {
|
||||
const dateObj = typeof date === 'string' ? parseISO(date) : date;
|
||||
if (!isValid(dateObj)) {
|
||||
throw new Error('Invalid date');
|
||||
}
|
||||
return format(dateObj, formatStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date to ISO 8601
|
||||
*/
|
||||
export function formatISO(date: Date | string): string {
|
||||
const dateObj = typeof date === 'string' ? parseISO(date) : date;
|
||||
if (!isValid(dateObj)) {
|
||||
throw new Error('Invalid date');
|
||||
}
|
||||
return dateObj.toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is valid
|
||||
*/
|
||||
export function isValidDate(date: unknown): date is Date {
|
||||
return date instanceof Date && isValid(date);
|
||||
}
|
||||
|
||||
10
packages/shared-utils/src/index.ts
Normal file
10
packages/shared-utils/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @workspace/shared-utils
|
||||
* Shared utility functions
|
||||
*/
|
||||
|
||||
export * from './date';
|
||||
export * from './string';
|
||||
export * from './validation';
|
||||
export * from './uuid';
|
||||
|
||||
58
packages/shared-utils/src/string.ts
Normal file
58
packages/shared-utils/src/string.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* String utility functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Capitalize first letter
|
||||
*/
|
||||
export function capitalize(str: string): string {
|
||||
if (!str) return str;
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to camelCase
|
||||
*/
|
||||
export function toCamelCase(str: string): string {
|
||||
return str
|
||||
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
|
||||
return index === 0 ? word.toLowerCase() : word.toUpperCase();
|
||||
})
|
||||
.replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to kebab-case
|
||||
*/
|
||||
export function toKebabCase(str: string): string {
|
||||
return str
|
||||
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
||||
.replace(/[\s_]+/g, '-')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to snake_case
|
||||
*/
|
||||
export function toSnakeCase(str: string): string {
|
||||
return str
|
||||
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
||||
.replace(/[\s-]+/g, '_')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate string with ellipsis
|
||||
*/
|
||||
export function truncate(str: string, length: number, suffix: string = '...'): string {
|
||||
if (str.length <= length) return str;
|
||||
return str.slice(0, length - suffix.length) + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace
|
||||
*/
|
||||
export function removeWhitespace(str: string): string {
|
||||
return str.replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
20
packages/shared-utils/src/uuid.ts
Normal file
20
packages/shared-utils/src/uuid.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* UUID utility functions
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4, validate as validateUUID } from 'uuid';
|
||||
|
||||
/**
|
||||
* Generate UUID v4
|
||||
*/
|
||||
export function generateUUID(): string {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate UUID
|
||||
*/
|
||||
export function isValidUUID(uuid: string): boolean {
|
||||
return validateUUID(uuid);
|
||||
}
|
||||
|
||||
49
packages/shared-utils/src/validation.ts
Normal file
49
packages/shared-utils/src/validation.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Validation utility functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if value is empty
|
||||
*/
|
||||
export function isEmpty(value: unknown): boolean {
|
||||
if (value === null || value === undefined) return true;
|
||||
if (typeof value === 'string') return value.trim().length === 0;
|
||||
if (Array.isArray(value)) return value.length === 0;
|
||||
if (typeof value === 'object') return Object.keys(value).length === 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if value is not empty
|
||||
*/
|
||||
export function isNotEmpty(value: unknown): boolean {
|
||||
return !isEmpty(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if email is valid
|
||||
*/
|
||||
export function isValidEmail(email: string): boolean {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if URL is valid
|
||||
*/
|
||||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if string is numeric
|
||||
*/
|
||||
export function isNumeric(str: string): boolean {
|
||||
return !isNaN(Number(str)) && !isNaN(parseFloat(str));
|
||||
}
|
||||
|
||||
20
packages/shared-utils/tsconfig.json
Normal file
20
packages/shared-utils/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
28
packages/validation/package.json
Normal file
28
packages/validation/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@workspace/validation",
|
||||
"version": "1.0.0",
|
||||
"description": "Zod schemas and validators",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
}
|
||||
}
|
||||
|
||||
8
packages/validation/src/index.ts
Normal file
8
packages/validation/src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @workspace/validation
|
||||
* Zod schemas and validators
|
||||
*/
|
||||
|
||||
export * from './schemas';
|
||||
export * from './validators';
|
||||
|
||||
37
packages/validation/src/schemas.ts
Normal file
37
packages/validation/src/schemas.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Common validation schemas
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Email validation schema
|
||||
*/
|
||||
export const emailSchema = z.string().email('Invalid email address');
|
||||
|
||||
/**
|
||||
* UUID validation schema
|
||||
*/
|
||||
export const uuidSchema = z.string().uuid('Invalid UUID');
|
||||
|
||||
/**
|
||||
* URL validation schema
|
||||
*/
|
||||
export const urlSchema = z.string().url('Invalid URL');
|
||||
|
||||
/**
|
||||
* Pagination schema
|
||||
*/
|
||||
export const paginationSchema = z.object({
|
||||
page: z.number().int().positive().default(1),
|
||||
limit: z.number().int().positive().max(100).default(10),
|
||||
});
|
||||
|
||||
/**
|
||||
* Sort schema
|
||||
*/
|
||||
export const sortSchema = z.object({
|
||||
field: z.string(),
|
||||
order: z.enum(['asc', 'desc']).default('asc'),
|
||||
});
|
||||
|
||||
27
packages/validation/src/validators.ts
Normal file
27
packages/validation/src/validators.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Validation utilities
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Validate data against schema
|
||||
*/
|
||||
export function validate<T>(schema: z.ZodSchema<T>, data: unknown): T {
|
||||
return schema.parse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe validate (returns result instead of throwing)
|
||||
*/
|
||||
export function validateSafe<T>(
|
||||
schema: z.ZodSchema<T>,
|
||||
data: unknown
|
||||
): { success: true; data: T } | { success: false; error: z.ZodError } {
|
||||
const result = schema.safeParse(data);
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data };
|
||||
}
|
||||
return { success: false, error: result.error };
|
||||
}
|
||||
|
||||
20
packages/validation/tsconfig.json
Normal file
20
packages/validation/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
5
pnpm-workspace.yaml
Normal file
5
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
packages:
|
||||
- 'packages/*'
|
||||
- 'apps/*'
|
||||
- 'tools/*'
|
||||
|
||||
39
scripts/publish.sh
Executable file
39
scripts/publish.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
# Publish shared packages to private npm registry
|
||||
|
||||
set -e
|
||||
|
||||
REGISTRY="${NPM_REGISTRY:-http://localhost:4873}"
|
||||
SCOPE="@workspace"
|
||||
|
||||
echo "📦 Publishing shared packages to registry: $REGISTRY"
|
||||
|
||||
# Check if registry is accessible
|
||||
if ! curl -s "$REGISTRY" > /dev/null; then
|
||||
echo "❌ Registry not accessible at $REGISTRY"
|
||||
echo " Please ensure registry is running or set NPM_REGISTRY environment variable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Publish each package
|
||||
for package in packages/*/; do
|
||||
if [ -f "$package/package.json" ]; then
|
||||
package_name=$(basename "$package")
|
||||
echo "📤 Publishing $package_name..."
|
||||
|
||||
cd "$package"
|
||||
|
||||
# Build package
|
||||
pnpm build
|
||||
|
||||
# Publish
|
||||
npm publish --registry="$REGISTRY" --access restricted || {
|
||||
echo "⚠️ Failed to publish $package_name (may already be published)"
|
||||
}
|
||||
|
||||
cd - > /dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✅ Publishing complete!"
|
||||
|
||||
24
turbo.json
Normal file
24
turbo.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".next/**", "build/**"]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["build"],
|
||||
"outputs": ["coverage/**"]
|
||||
},
|
||||
"lint": {
|
||||
"outputs": []
|
||||
},
|
||||
"type-check": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": []
|
||||
},
|
||||
"clean": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user