mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
feat: add monorepo configuration and shared packages structure
Root-level setup: - package.json with Turborepo scripts - pnpm-workspace.yaml for workspace management - turbo.json for build pipeline - Common config files (.nvmrc, .prettierrc, .editorconfig) Shared packages (packages/): - @manacore/shared-types - Common TypeScript types - @manacore/shared-supabase - Unified Supabase client - @manacore/shared-utils - Date, string, async utilities - @manacore/shared-ui - React Native UI components (placeholder) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e7f5f942f3
commit
2c58d03f63
23 changed files with 738 additions and 0 deletions
18
.editorconfig
Normal file
18
.editorconfig
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
20
|
||||||
32
.prettierignore
Normal file
32
.prettierignore
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.next/
|
||||||
|
.nuxt/
|
||||||
|
.output/
|
||||||
|
.svelte-kit/
|
||||||
|
.astro/
|
||||||
|
.expo/
|
||||||
|
android/
|
||||||
|
ios/
|
||||||
|
|
||||||
|
# Turbo
|
||||||
|
.turbo/
|
||||||
|
|
||||||
|
# Package manager
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
*.min.js
|
||||||
|
*.min.css
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
26
.prettierrc
Normal file
26
.prettierrc
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-astro"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "*.astro",
|
||||||
|
"options": {
|
||||||
|
"parser": "astro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
124
README.md
Normal file
124
README.md
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
# Manacore Monorepo
|
||||||
|
|
||||||
|
Monorepo containing all Manacore projects with shared packages and unified tooling.
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
|
||||||
|
| Project | Description | Tech Stack |
|
||||||
|
|---------|-------------|------------|
|
||||||
|
| **maerchenzauber** | AI-powered story generation app | NestJS, Expo, SvelteKit, Astro |
|
||||||
|
| **manacore** | Multi-app ecosystem platform | Expo, SvelteKit, Astro |
|
||||||
|
| **manadeck** | Card/deck management app | NestJS, Expo, SvelteKit |
|
||||||
|
| **memoro** | Voice memo & AI analysis app | Expo, SvelteKit, Astro |
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 20+
|
||||||
|
- pnpm 9.15.0+
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install pnpm globally (if not installed)
|
||||||
|
npm install -g pnpm
|
||||||
|
|
||||||
|
# Install all dependencies
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start all projects in dev mode
|
||||||
|
pnpm run dev
|
||||||
|
|
||||||
|
# Start a specific project
|
||||||
|
pnpm run maerchenzauber:dev
|
||||||
|
pnpm run manacore:dev
|
||||||
|
pnpm run manadeck:dev
|
||||||
|
pnpm run memoro:dev
|
||||||
|
|
||||||
|
# Build all projects
|
||||||
|
pnpm run build
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
pnpm run test
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
pnpm run type-check
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
pnpm run format
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shared Packages
|
||||||
|
|
||||||
|
Located in `packages/`:
|
||||||
|
|
||||||
|
| Package | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `@manacore/shared-types` | Common TypeScript types |
|
||||||
|
| `@manacore/shared-supabase` | Unified Supabase client |
|
||||||
|
| `@manacore/shared-utils` | Utility functions (date, string, async) |
|
||||||
|
| `@manacore/shared-ui` | React Native UI components |
|
||||||
|
|
||||||
|
### Using Shared Packages
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In any project
|
||||||
|
import { User, ApiResponse } from '@manacore/shared-types';
|
||||||
|
import { createSupabaseClient } from '@manacore/shared-supabase';
|
||||||
|
import { formatDate, truncate, retry } from '@manacore/shared-utils';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
manacore-monorepo/
|
||||||
|
├── packages/ # Shared packages
|
||||||
|
│ ├── shared-types/ # TypeScript types
|
||||||
|
│ ├── shared-supabase/ # Supabase utilities
|
||||||
|
│ ├── shared-utils/ # Common utilities
|
||||||
|
│ └── shared-ui/ # React Native components
|
||||||
|
├── maerchenzauber/ # Storyteller project
|
||||||
|
├── manacore/ # Manacore apps project
|
||||||
|
├── manadeck/ # ManaDeck project
|
||||||
|
├── memoro/ # Memoro project
|
||||||
|
├── turbo.json # Turborepo configuration
|
||||||
|
├── pnpm-workspace.yaml # Workspace configuration
|
||||||
|
└── package.json # Root package
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tooling
|
||||||
|
|
||||||
|
- **Package Manager:** pnpm 9.15.0
|
||||||
|
- **Build System:** Turborepo
|
||||||
|
- **Formatting:** Prettier
|
||||||
|
- **Node Version:** 20 (see .nvmrc)
|
||||||
|
|
||||||
|
## Adding Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add to root (dev tools)
|
||||||
|
pnpm add -D <package> -w
|
||||||
|
|
||||||
|
# Add to specific project
|
||||||
|
pnpm add <package> --filter maerchenzauber
|
||||||
|
|
||||||
|
# Add to shared package
|
||||||
|
pnpm add <package> --filter @manacore/shared-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Create a feature branch
|
||||||
|
2. Make changes
|
||||||
|
3. Run `pnpm run format` and `pnpm run type-check`
|
||||||
|
4. Commit with conventional commit messages
|
||||||
|
5. Create pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Private - All rights reserved
|
||||||
29
package.json
Normal file
29
package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "manacore-monorepo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Manacore Monorepo containing maerchenzauber, manacore, manadeck, and memoro",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "turbo run dev",
|
||||||
|
"build": "turbo run build",
|
||||||
|
"test": "turbo run test",
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"type-check": "turbo run type-check",
|
||||||
|
"clean": "turbo run clean",
|
||||||
|
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
|
||||||
|
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
|
||||||
|
"maerchenzauber:dev": "turbo run dev --filter=maerchenzauber...",
|
||||||
|
"manacore:dev": "turbo run dev --filter=manacore...",
|
||||||
|
"manadeck:dev": "turbo run dev --filter=manadeck...",
|
||||||
|
"memoro:dev": "turbo run dev --filter=memoro..."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"turbo": "^2.3.0",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@9.15.0"
|
||||||
|
}
|
||||||
24
packages/shared-supabase/package.json
Normal file
24
packages/shared-supabase/package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "@manacore/shared-supabase",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Shared Supabase client and utilities for Manacore monorepo",
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/supabase-js": "^2.81.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@manacore/shared-types": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
62
packages/shared-supabase/src/index.ts
Normal file
62
packages/shared-supabase/src/index.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* Shared Supabase utilities for Manacore monorepo
|
||||||
|
*
|
||||||
|
* This package provides a unified Supabase client and common database utilities.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
import type { SupabaseConfig } from '@manacore/shared-types';
|
||||||
|
|
||||||
|
export { SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Supabase client with the given configuration
|
||||||
|
*/
|
||||||
|
export function createSupabaseClient(config: SupabaseConfig): SupabaseClient {
|
||||||
|
return createClient(config.url, config.anonKey, {
|
||||||
|
auth: {
|
||||||
|
persistSession: true,
|
||||||
|
autoRefreshToken: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Supabase admin client with service role key
|
||||||
|
*/
|
||||||
|
export function createSupabaseAdminClient(config: SupabaseConfig): SupabaseClient {
|
||||||
|
if (!config.serviceRoleKey) {
|
||||||
|
throw new Error('Service role key is required for admin client');
|
||||||
|
}
|
||||||
|
|
||||||
|
return createClient(config.url, config.serviceRoleKey, {
|
||||||
|
auth: {
|
||||||
|
persistSession: false,
|
||||||
|
autoRefreshToken: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common database query helpers
|
||||||
|
*/
|
||||||
|
export const dbHelpers = {
|
||||||
|
/**
|
||||||
|
* Handle Supabase query result and return standardized response
|
||||||
|
*/
|
||||||
|
handleQueryResult<T>(result: { data: T | null; error: any }): {
|
||||||
|
data: T | null;
|
||||||
|
error: { message: string; code?: string } | null;
|
||||||
|
} {
|
||||||
|
if (result.error) {
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
error: {
|
||||||
|
message: result.error.message,
|
||||||
|
code: result.error.code,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { data: result.data, error: null };
|
||||||
|
},
|
||||||
|
};
|
||||||
17
packages/shared-supabase/tsconfig.json
Normal file
17
packages/shared-supabase/tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2020"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
18
packages/shared-types/package.json
Normal file
18
packages/shared-types/package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "@manacore/shared-types",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Shared TypeScript types for Manacore monorepo",
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
47
packages/shared-types/src/index.ts
Normal file
47
packages/shared-types/src/index.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Shared types for Manacore monorepo
|
||||||
|
*
|
||||||
|
* This package contains common TypeScript types used across all projects.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Common user types
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common API response types
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
data: T | null;
|
||||||
|
error: ApiError | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiError {
|
||||||
|
message: string;
|
||||||
|
code?: string;
|
||||||
|
status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination types
|
||||||
|
export interface PaginationParams {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
|
data: T[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
hasMore: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supabase common types
|
||||||
|
export interface SupabaseConfig {
|
||||||
|
url: string;
|
||||||
|
anonKey: string;
|
||||||
|
serviceRoleKey?: string;
|
||||||
|
}
|
||||||
17
packages/shared-types/tsconfig.json
Normal file
17
packages/shared-types/tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2020"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
23
packages/shared-ui/package.json
Normal file
23
packages/shared-ui/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "@manacore/shared-ui",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Shared React Native UI components for Manacore monorepo",
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18.0.0",
|
||||||
|
"react-native": ">=0.70.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.0",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
24
packages/shared-ui/src/index.ts
Normal file
24
packages/shared-ui/src/index.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Shared React Native UI components for Manacore monorepo
|
||||||
|
*
|
||||||
|
* This package will contain common UI components used across all mobile apps.
|
||||||
|
*
|
||||||
|
* Planned components:
|
||||||
|
* - Button
|
||||||
|
* - Text
|
||||||
|
* - Input
|
||||||
|
* - Card
|
||||||
|
* - Modal
|
||||||
|
* - Loading indicators
|
||||||
|
* - Icons
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Placeholder export until components are migrated
|
||||||
|
export const SHARED_UI_VERSION = '0.1.0';
|
||||||
|
|
||||||
|
// Future exports will include:
|
||||||
|
// export { Button } from './components/Button';
|
||||||
|
// export { Text } from './components/Text';
|
||||||
|
// export { Input } from './components/Input';
|
||||||
|
// export { Card } from './components/Card';
|
||||||
|
// export { Modal } from './components/Modal';
|
||||||
18
packages/shared-ui/tsconfig.json
Normal file
18
packages/shared-ui/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2020"],
|
||||||
|
"jsx": "react-native",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
21
packages/shared-utils/package.json
Normal file
21
packages/shared-utils/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "@manacore/shared-utils",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Shared utility functions for Manacore monorepo",
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"date-fns": "^4.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
71
packages/shared-utils/src/async.ts
Normal file
71
packages/shared-utils/src/async.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* Async utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleep for a specified number of milliseconds
|
||||||
|
*/
|
||||||
|
export function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry a function with exponential backoff
|
||||||
|
*/
|
||||||
|
export async function retry<T>(
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
options: {
|
||||||
|
maxAttempts?: number;
|
||||||
|
initialDelay?: number;
|
||||||
|
maxDelay?: number;
|
||||||
|
backoffMultiplier?: number;
|
||||||
|
} = {}
|
||||||
|
): Promise<T> {
|
||||||
|
const {
|
||||||
|
maxAttempts = 3,
|
||||||
|
initialDelay = 1000,
|
||||||
|
maxDelay = 10000,
|
||||||
|
backoffMultiplier = 2,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let lastError: Error | undefined;
|
||||||
|
let delay = initialDelay;
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error;
|
||||||
|
|
||||||
|
if (attempt === maxAttempts) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(delay);
|
||||||
|
delay = Math.min(delay * backoffMultiplier, maxDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce a function
|
||||||
|
*/
|
||||||
|
export function debounce<T extends (...args: any[]) => any>(
|
||||||
|
fn: T,
|
||||||
|
delay: number
|
||||||
|
): (...args: Parameters<T>) => void {
|
||||||
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
return (...args: Parameters<T>) => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
fn(...args);
|
||||||
|
timeoutId = null;
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
43
packages/shared-utils/src/date.ts
Normal file
43
packages/shared-utils/src/date.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* Date utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { format, formatDistanceToNow, parseISO } from 'date-fns';
|
||||||
|
import { de, enUS } from 'date-fns/locale';
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
de,
|
||||||
|
en: enUS,
|
||||||
|
};
|
||||||
|
|
||||||
|
type LocaleKey = keyof typeof locales;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date string to a readable format
|
||||||
|
*/
|
||||||
|
export function formatDate(
|
||||||
|
date: string | Date,
|
||||||
|
formatStr: string = 'PPP',
|
||||||
|
locale: LocaleKey = 'de'
|
||||||
|
): string {
|
||||||
|
const dateObj = typeof date === 'string' ? parseISO(date) : date;
|
||||||
|
return format(dateObj, formatStr, { locale: locales[locale] });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get relative time from now (e.g., "2 hours ago")
|
||||||
|
*/
|
||||||
|
export function formatRelativeTime(date: string | Date, locale: LocaleKey = 'de'): string {
|
||||||
|
const dateObj = typeof date === 'string' ? parseISO(date) : date;
|
||||||
|
return formatDistanceToNow(dateObj, {
|
||||||
|
addSuffix: true,
|
||||||
|
locale: locales[locale],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date for API requests (ISO string)
|
||||||
|
*/
|
||||||
|
export function toISOString(date: Date): string {
|
||||||
|
return date.toISOString();
|
||||||
|
}
|
||||||
12
packages/shared-utils/src/index.ts
Normal file
12
packages/shared-utils/src/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Shared utility functions for Manacore monorepo
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Date utilities
|
||||||
|
export * from './date';
|
||||||
|
|
||||||
|
// String utilities
|
||||||
|
export * from './string';
|
||||||
|
|
||||||
|
// Async utilities
|
||||||
|
export * from './async';
|
||||||
43
packages/shared-utils/src/string.ts
Normal file
43
packages/shared-utils/src/string.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* String utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate a string to a maximum length with ellipsis
|
||||||
|
*/
|
||||||
|
export function truncate(str: string, maxLength: number): string {
|
||||||
|
if (str.length <= maxLength) return str;
|
||||||
|
return str.slice(0, maxLength - 3) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalize the first letter of a string
|
||||||
|
*/
|
||||||
|
export function capitalize(str: string): string {
|
||||||
|
if (!str) return str;
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random string ID
|
||||||
|
*/
|
||||||
|
export function generateId(length: number = 8): string {
|
||||||
|
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slugify a string for URLs
|
||||||
|
*/
|
||||||
|
export function slugify(str: string): string {
|
||||||
|
return str
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^\w\s-]/g, '')
|
||||||
|
.replace(/[\s_-]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '');
|
||||||
|
}
|
||||||
17
packages/shared-utils/tsconfig.json
Normal file
17
packages/shared-utils/tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2020"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
17
pnpm-workspace.yaml
Normal file
17
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
packages:
|
||||||
|
# Main projects
|
||||||
|
- 'maerchenzauber'
|
||||||
|
- 'manacore'
|
||||||
|
- 'manadeck'
|
||||||
|
- 'memoro'
|
||||||
|
|
||||||
|
# Sub-apps within projects
|
||||||
|
- 'maerchenzauber/apps/*'
|
||||||
|
- 'maerchenzauber/packages/*'
|
||||||
|
- 'manacore/apps/*'
|
||||||
|
- 'manadeck/apps/*'
|
||||||
|
- 'manadeck/backend'
|
||||||
|
- 'memoro/apps/*'
|
||||||
|
|
||||||
|
# Future shared packages
|
||||||
|
- 'packages/*'
|
||||||
34
turbo.json
Normal file
34
turbo.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": [
|
||||||
|
"dist/**",
|
||||||
|
"build/**",
|
||||||
|
".next/**",
|
||||||
|
".svelte-kit/**",
|
||||||
|
".astro/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"dependsOn": ["^lint"],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"type-check": {
|
||||||
|
"dependsOn": ["^type-check"],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["coverage/**"]
|
||||||
|
},
|
||||||
|
"clean": {
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue