From 12c631c79ae19d2ab3dfba45d27ab6ce694d7c8c Mon Sep 17 00:00:00 2001 From: Andres J Date: Sat, 7 Feb 2026 17:33:07 -0600 Subject: [PATCH] docs: add Spanish docs structure and fix language-prefixed links on home index --- apps/docs/astro.config.ts | 12 + .../docs/es/backend/getting-started.md | 252 ++++++++++++++++++ .../src/content/docs/es/backend/index.mdx | 220 +++++++++++++++ .../content/docs/es/backend/installation.md | 145 ++++++++++ .../docs/es/backend/packages/node-auth-app.md | 85 ++++++ .../es/backend/packages/node-better-auth.md | 104 ++++++++ apps/docs/src/content/docs/es/index.mdx | 145 ++++++++++ .../content/docs/es/react/getting-started.md | 151 +++++++++++ apps/docs/src/content/docs/es/react/index.mdx | 55 ++++ .../src/content/docs/es/react/installation.md | 228 ++++++++++++++++ .../docs/es/react/packages/chat-react.md | 177 ++++++++++++ .../docs/es/react/packages/react-query.md | 168 ++++++++++++ .../docs/es/react/packages/react-ui.md | 198 ++++++++++++++ .../content/docs/es/react/reference/api.md | 218 +++++++++++++++ .../content/docs/es/solid/getting-started.md | 207 ++++++++++++++ apps/docs/src/content/docs/es/solid/index.mdx | 93 +++++++ .../src/content/docs/es/solid/installation.md | 202 ++++++++++++++ .../docs/es/solid/packages/chat-solid.md | 177 ++++++++++++ .../docs/es/solid/packages/solid-query.md | 197 ++++++++++++++ .../docs/es/solid/packages/solid-ui.md | 201 ++++++++++++++ .../content/docs/es/solid/reference/api.md | 234 ++++++++++++++++ .../docs/es/universal/getting-started.md | 123 +++++++++ .../src/content/docs/es/universal/index.mdx | 119 +++++++++ .../docs/es/universal/packages/chat-domain.md | 142 ++++++++++ .../es/universal/packages/shared-types.md | 111 ++++++++ packages/prisma/project.json | 2 +- 26 files changed, 3965 insertions(+), 1 deletion(-) create mode 100644 apps/docs/src/content/docs/es/backend/getting-started.md create mode 100644 apps/docs/src/content/docs/es/backend/index.mdx create mode 100644 apps/docs/src/content/docs/es/backend/installation.md create mode 100644 apps/docs/src/content/docs/es/backend/packages/node-auth-app.md create mode 100644 apps/docs/src/content/docs/es/backend/packages/node-better-auth.md create mode 100644 apps/docs/src/content/docs/es/index.mdx create mode 100644 apps/docs/src/content/docs/es/react/getting-started.md create mode 100644 apps/docs/src/content/docs/es/react/index.mdx create mode 100644 apps/docs/src/content/docs/es/react/installation.md create mode 100644 apps/docs/src/content/docs/es/react/packages/chat-react.md create mode 100644 apps/docs/src/content/docs/es/react/packages/react-query.md create mode 100644 apps/docs/src/content/docs/es/react/packages/react-ui.md create mode 100644 apps/docs/src/content/docs/es/react/reference/api.md create mode 100644 apps/docs/src/content/docs/es/solid/getting-started.md create mode 100644 apps/docs/src/content/docs/es/solid/index.mdx create mode 100644 apps/docs/src/content/docs/es/solid/installation.md create mode 100644 apps/docs/src/content/docs/es/solid/packages/chat-solid.md create mode 100644 apps/docs/src/content/docs/es/solid/packages/solid-query.md create mode 100644 apps/docs/src/content/docs/es/solid/packages/solid-ui.md create mode 100644 apps/docs/src/content/docs/es/solid/reference/api.md create mode 100644 apps/docs/src/content/docs/es/universal/getting-started.md create mode 100644 apps/docs/src/content/docs/es/universal/index.mdx create mode 100644 apps/docs/src/content/docs/es/universal/packages/chat-domain.md create mode 100644 apps/docs/src/content/docs/es/universal/packages/shared-types.md diff --git a/apps/docs/astro.config.ts b/apps/docs/astro.config.ts index dadbffaf..57f5ae13 100644 --- a/apps/docs/astro.config.ts +++ b/apps/docs/astro.config.ts @@ -124,6 +124,18 @@ export default defineConfig({ }, ], */ customCss: ["./src/styles/global.css"], + defaultLocale: "root", + locales: { + // English docs in `src/content/docs/en/` + root: { + label: "English", + lang: "en", // lang is required for root locales + }, + es: { + label: "Español", + lang: "es-ES", + }, + }, }), ], vite: { diff --git a/apps/docs/src/content/docs/es/backend/getting-started.md b/apps/docs/src/content/docs/es/backend/getting-started.md new file mode 100644 index 00000000..8b38614b --- /dev/null +++ b/apps/docs/src/content/docs/es/backend/getting-started.md @@ -0,0 +1,252 @@ +--- +title: Primeros pasos con Backend +description: Aprende a configurar Effectify en tu aplicación backend de Node.js +--- + +# Primeros pasos con Backend + +Esta guía te ayudará a empezar con Effectify en tu aplicación backend de Node.js. Recorreremos la configuración de dependencias básicas y la creación de tu primer servicio backend impulsado por Effect. + +## Requisitos previos + +Antes de comenzar, asegúrate de tener: + +- Node.js 18 o superior +- Conocimientos básicos de Node.js y TypeScript +- Familiaridad con conceptos de desarrollo backend + +## Instalación + +Elige los paquetes que necesitas para tu proyecto: + +### Paquete de autenticación principal + +Para autenticación con better-auth y Effect: + +```bash +npm install @effectify/node-better-auth better-auth effect +``` + +### Aplicación de autenticación completa + +Para un servicio de autenticación listo para desplegar: + +```bash +npm install @effectify/node-auth-app +``` + +### Dependencias adicionales + +Probablemente necesitarás estas dependencias comunes: + +```bash +npm install express cors helmet dotenv +npm install -D @types/express @types/cors typescript ts-node nodemon +``` + +## Configuración básica + +### 1. Estructura de proyecto + +Crea una estructura de proyecto bien organizada: + +``` +mi-backend/ +├── src/ +│ ├── config/ +│ │ └── index.ts +│ ├── services/ +│ │ ├── auth.ts +│ │ └── user.ts +│ ├── repositories/ +│ │ └── user.ts +│ ├── routes/ +│ │ └── auth.ts +│ ├── middleware/ +│ │ └── auth.ts +│ ├── types/ +│ │ └── index.ts +│ └── index.ts +├── package.json +├── tsconfig.json +└── .env +``` + +### 2. Configuración de TypeScript + +Crea un `tsconfig.json`: + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} +``` + +### 3. Configuración de entorno + +Crea un archivo `.env`: + +```env +PORT=3000 +NODE_ENV=development +DATABASE_URL=postgresql://user:password@localhost:5432/myapp +JWT_SECRET=tu-super-secreto-jwt +BETTER_AUTH_SECRET=tu-secreto-better-auth +BETTER_AUTH_URL=http://localhost:3000 +``` + +### 4. Configuración básica del servidor + +Crea `src/index.ts`: + +```typescript +import express from "express" +import cors from "cors" +import helmet from "helmet" +import { Effect, Layer } from "effect" +import { config } from "./config" +import { authRoutes } from "./routes/auth" + +const app = express() + +// Middleware +app.use(helmet()) +app.use(cors()) +app.use(express.json()) + +// Rutas +app.use("/auth", authRoutes) + +// Health check +app.get("/health", (req, res) => { + res.json({ status: "ok", timestamp: new Date().toISOString() }) +}) + +// Iniciar servidor +const startServer = Effect.gen(function*() { + const { port } = yield* config + + return yield* Effect.async((resume) => { + const server = app.listen(port, () => { + console.log(`Servidor corriendo en el puerto ${port}`) + resume(Effect.succeed(void 0)) + }) + + server.on("error", (error) => { + resume(Effect.fail(error)) + }) + }) +}) + +// Ejecutar el servidor +Effect.runPromise(startServer).catch(console.error) +``` + +## Gestión de configuración + +Crea `src/config/index.ts`: + +```typescript +import { Context, Effect, Layer } from "effect" + +export interface AppConfig { + readonly port: number + readonly nodeEnv: string + readonly database: { + readonly url: string + } + readonly auth: { + readonly jwtSecret: string + readonly betterAuthSecret: string + readonly betterAuthUrl: string + } +} + +export class AppConfigService extends Context.Tag("AppConfigService")< + AppConfigService, + AppConfig +>() {} + +export const loadConfig = Effect.gen(function*() { + const port = Number(process.env.PORT) || 3000 + const nodeEnv = process.env.NODE_ENV || "development" + + const databaseUrl = yield* Effect.fromNullable(process.env.DATABASE_URL).pipe( + Effect.orElseFail(() => new Error("DATABASE_URL is required")), + ) + + const jwtSecret = yield* Effect.fromNullable(process.env.JWT_SECRET).pipe( + Effect.orElseFail(() => new Error("JWT_SECRET is required")), + ) + + const betterAuthSecret = yield* Effect.fromNullable(process.env.BETTER_AUTH_SECRET).pipe( + Effect.orElseFail(() => new Error("BETTER_AUTH_SECRET is required")), + ) + + const betterAuthUrl = process.env.BETTER_AUTH_URL || "http://localhost:3000" + + return { + port, + nodeEnv, + database: { url: databaseUrl }, + auth: { jwtSecret, betterAuthSecret, betterAuthUrl }, + } satisfies AppConfig +}) + +export const AppConfigLive = Layer.effect(AppConfigService, loadConfig) +export const config = AppConfigService +``` + +## Manejo de errores + +Crea `src/types/errors.ts`: + +```typescript +import { Data } from "effect" + +export class AppError extends Data.TaggedError("AppError")<{ + readonly message: string + readonly cause?: unknown +}> {} + +export class AuthenticationError extends Data.TaggedError("AuthenticationError")<{ + readonly message: string +}> {} + +export class AuthorizationError extends Data.TaggedError("AuthorizationError")<{ + readonly message: string +}> {} + +export class ValidationError extends Data.TaggedError("ValidationError")<{ + readonly errors: Record +}> {} + +export class DatabaseError extends Data.TaggedError("DatabaseError")<{ + readonly message: string + readonly cause?: unknown +}> {} + +export class UserNotFoundError extends Data.TaggedError("UserNotFoundError")<{ + readonly userId: string +}> {} + +export class UserAlreadyExistsError extends Data.TaggedError("UserAlreadyExistsError")<{ + readonly email: string +}> {} +``` diff --git a/apps/docs/src/content/docs/es/backend/index.mdx b/apps/docs/src/content/docs/es/backend/index.mdx new file mode 100644 index 00000000..daa3cf3f --- /dev/null +++ b/apps/docs/src/content/docs/es/backend/index.mdx @@ -0,0 +1,220 @@ +--- +title: Integración de Backend +description: Paquetes de integración de Effect para aplicaciones backend en Node.js +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +# Integración de Backend + +Effectify ofrece integraciones potentes para backend en Node.js que combinan la robustez y componibilidad de Effect con frameworks y librerías populares. Nuestros paquetes de backend te permiten construir servicios escalables y mantenibles con excelente manejo de errores y tipado seguro. + +## Paquetes disponibles + + + + Integración de Effect con better-auth para aplicaciones Node.js. Proporciona autenticación con tipos seguros y manejo de errores con Effect. + [Más información →](packages/node-better-auth/) + + + + Aplicación de servidor de autenticación completa construida con Effect, better-auth y patrones modernos de Node.js. Lista para desplegar. + [Más información →](packages/node-auth-app/) + + + +## Primeros pasos + +¿Listo para construir servicios backend con Effectify? Consulta nuestra guía de inicio para configurar tu primer proyecto Node.js con Effect. + + + Ponte en marcha en minutos con Effectify en tu aplicación backend de Node.js. [Empezar →](getting-started/) + + +## Características clave + +- **Tipos seguros**: Soporte completo de TypeScript con el potente sistema de tipos de Effect +- **Manejo de errores**: Gestión robusta de errores con las utilidades de Effect +- **Componibilidad**: Construye servicios complejos a partir de piezas simples y componibles +- **Rendimiento**: Optimizado para el rendimiento y la escalabilidad en Node.js +- **Observabilidad**: Soporte integrado de logs, métricas y trazas +- **Testing**: Excelente capacidad de pruebas con utilidades de Effect + +## Visión de arquitectura + +Los paquetes de backend de Effectify siguen un patrón de arquitectura por capas: + +```typescript +// Capa de Dominio - Lógica de negocio pura +const authenticateUser = (credentials: LoginCredentials) => + Effect.gen(function* () { + const user = yield* UserRepository.findByEmail(credentials.email) + const isValid = yield* PasswordService.verify( + credentials.password, + user.hashedPassword, + ) + + if (!isValid) { + yield* Effect.fail(new AuthenticationError("Invalid credentials")) + } + + return user + }) + +// Capa de Servicio - Servicios de aplicación +const AuthService = { + login: (credentials: LoginCredentials) => + Effect.gen(function* () { + const user = yield* authenticateUser(credentials) + const token = yield* TokenService.generate(user.id) + const session = yield* SessionService.create(user.id, token) + + return { user, token, session } + }), +} + +// Capa de Infraestructura - Integraciones externas +const UserRepository = { + findByEmail: (email: string) => + Effect.tryPromise({ + try: () => db.user.findUnique({ where: { email } }), + catch: (error) => + new DatabaseError("Failed to find user", { cause: error }), + }), +} + +// Capa HTTP - Endpoints de API +app.post("/auth/login", (req, res) => { + Effect.runPromise( + AuthService.login(req.body).pipe( + Effect.map((result) => res.json(result)), + Effect.catchAll((error) => + Effect.sync(() => res.status(400).json({ error: error.message })), + ), + ), + ) +}) +``` + +## Principios de diseño + +### 1. Arquitectura Effect‑First + +Toda la lógica de negocio se construye usando Effect, proporcionando: + +- Manejo de errores componible +- Operaciones asíncronas con tipos seguros +- Mecanismos integrados de reintento y timeout +- Excelente capacidad de pruebas + +### 2. Arquitectura por capas + +- **Dominio**: Lógica de negocio pura con Effect +- **Servicios**: Casos de uso y servicios de aplicación +- **Infraestructura**: Integraciones con sistemas externos +- **HTTP**: Endpoints y middleware + +### 3. Inversión de dependencias + +Uso del sistema de contextos de Effect para una gestión limpia de dependencias: + +```typescript +// Servicios como contextos de Effect +class DatabaseService extends Context.Tag("DatabaseService")< + DatabaseService, + { + readonly query: (sql: string) => Effect.Effect + } +>() {} + +// Uso en lógica de negocio +const getUser = (id: string) => + Effect.gen(function* () { + const db = yield* DatabaseService + const result = yield* db.query(`SELECT * FROM users WHERE id = ?`, [id]) + return result[0] + }) +``` + +## Patrones comunes + +### Manejo de errores + +```typescript +// Errores de dominio +class UserNotFoundError extends Data.TaggedError("UserNotFoundError")<{ + readonly userId: string +}> {} + +class ValidationError extends Data.TaggedError("ValidationError")<{ + readonly errors: Record +}> {} + +// Uso en servicios +const updateUser = (id: string, data: UserUpdateData) => + Effect.gen(function* () { + const validation = yield* validateUserData(data) + const user = yield* UserRepository.findById(id) + + if (!user) { + yield* Effect.fail(new UserNotFoundError({ userId: id })) + } + + return yield* UserRepository.update(id, validation) + }) +``` + +### Gestión de configuración + +```typescript +// Esquema de configuración +class AppConfig extends Context.Tag("AppConfig")< + AppConfig, + { + readonly port: number + readonly database: { + readonly url: string + readonly maxConnections: number + } + readonly auth: { + readonly jwtSecret: string + readonly sessionTimeout: number + } + } +>() {} + +// Carga con validación +const loadConfig = Effect.gen(function* () { + const port = yield* Effect.fromNullable(process.env.PORT).pipe( + Effect.map(Number), + Effect.orElse(() => Effect.succeed(3000)), + ) + + const databaseUrl = yield* Effect.fromNullable(process.env.DATABASE_URL).pipe( + Effect.orElseFail(() => new ConfigError("DATABASE_URL is required")), + ) + + return { + port, + database: { url: databaseUrl, maxConnections: 10 }, + auth: { jwtSecret: process.env.JWT_SECRET!, sessionTimeout: 3600 }, + } +}) +``` + +### Middleware HTTP + +```typescript +// Middleware basado en Effect +const authMiddleware = (req: Request) => + Effect.gen(function* () { + const token = yield* Effect.fromNullable(req.headers.authorization).pipe( + Effect.orElseFail( + () => new UnauthorizedError("Missing authorization header"), + ), + ) + + const user = yield* TokenService.verify(token.replace("Bearer ", "")) + return { ...req, user } + }) +``` diff --git a/apps/docs/src/content/docs/es/backend/installation.md b/apps/docs/src/content/docs/es/backend/installation.md new file mode 100644 index 00000000..de849572 --- /dev/null +++ b/apps/docs/src/content/docs/es/backend/installation.md @@ -0,0 +1,145 @@ +--- +title: Instalación +description: Cómo instalar los paquetes de backend de Effectify en Node.js +--- + +# Instalación + +Esta guía cubre cómo instalar y configurar los paquetes de backend de Effectify en tu proyecto Node.js. + +## Gestor de paquetes + +Usa tu gestor favorito. Los ejemplos usan npm, pero puedes usar yarn, pnpm o bun. + +## Paquetes principales + +### @effectify/node-better-auth + +Integración de Effect con better-auth para Node.js: + +```bash +npm install @effectify/node-better-auth +``` + +**Peer Dependencies:** + +```bash +npm install better-auth effect @effect/platform @effect/platform-node +``` + +### @effectify/node-auth-app + +Aplicación completa de autenticación: + +```bash +npm install @effectify/node-auth-app +``` + +**Dependencias:** + +```bash +npm install express cors helmet dotenv better-sqlite3 +``` + +## Configuración por framework + +### Express.js + +```bash +mkdir mi-backend && cd mi-backend +npm init -y +npm install express cors helmet dotenv +npm install -D @types/express @types/cors @types/node typescript ts-node nodemon +``` + +Inicializa TypeScript: + +```bash +npx tsc --init +``` + +`tsconfig.json`: + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} +``` + +Scripts en `package.json`: + +```json +{ + "scripts": { + "dev": "nodemon --exec ts-node src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + } +} +``` + +### Fastify + +```bash +mkdir mi-fastify-backend && cd mi-fastify-backend +npm init -y +npm install fastify @fastify/cors @fastify/helmet +npm install -D @types/node typescript ts-node nodemon +``` + +Configuración básica con Effect: + +```typescript +import Fastify from "fastify" +import { Effect } from "effect" + +const fastify = Fastify({ logger: true }) +fastify.register(require("@fastify/cors")) +fastify.register(require("@fastify/helmet")) + +fastify.get("/health", async () => { + const healthCheck = Effect.succeed({ + status: "ok", + timestamp: new Date().toISOString(), + }) + return await Effect.runPromise(healthCheck) +}) + +await fastify.listen({ port: 3000 }) +``` + +### NestJS + +```bash +npm i -g @nestjs/cli +nest new mi-nest-backend +cd mi-nest-backend +npm install @effectify/node-better-auth effect +``` + +Servicio Effect: + +```typescript +import { Injectable } from "@nestjs/common" +import { Effect } from "effect" + +@Injectable() +export class EffectService { + runEffect(effect: Effect.Effect): Promise { + return Effect.runPromise(effect) + } +} +``` diff --git a/apps/docs/src/content/docs/es/backend/packages/node-auth-app.md b/apps/docs/src/content/docs/es/backend/packages/node-auth-app.md new file mode 100644 index 00000000..88e36f14 --- /dev/null +++ b/apps/docs/src/content/docs/es/backend/packages/node-auth-app.md @@ -0,0 +1,85 @@ +--- +title: "@effectify/node-auth-app" +description: Aplicación completa de servidor de autenticación con Effect y better-auth +--- + +# @effectify/node-auth-app + +El paquete `@effectify/node-auth-app` proporciona una aplicación de servidor de autenticación lista para producción construida con Effect, better-auth y patrones modernos de Node.js. Puede desplegarse como servicio standalone o integrarse en aplicaciones existentes. + +## Instalación + +```bash +npm install @effectify/node-auth-app +``` + +**Dependencias:** + +```bash +npm install express cors helmet dotenv better-auth better-sqlite3 +``` + +## Inicio rápido + +### 1. Variables de entorno + +```env +PORT=3001 +NODE_ENV=development +CORS_ORIGIN=http://localhost:3000 +DATABASE_URL=file:./auth.db +BETTER_AUTH_SECRET=tu-super-secreto +BETTER_AUTH_URL=http://localhost:3001 +JWT_SECRET=tu-jwt-secreto +``` + +### 2. Uso básico + +```typescript +import { createAuthApp } from "@effectify/node-auth-app" +import { Effect } from "effect" + +const startAuthServer = Effect.gen(function*() { + const app = yield* createAuthApp({ + port: 3001, + corsOrigin: "http://localhost:3000", + database: { provider: "sqlite", url: "file:./auth.db" }, + }) + console.log("Auth server running on port 3001") + return app +}) + +Effect.runPromise(startAuthServer) +``` + +### 3. Configuración personalizada + +```typescript +import { AuthAppConfig, createAuthApp } from "@effectify/node-auth-app" + +const config: AuthAppConfig = { + port: 3001, + corsOrigin: ["http://localhost:3000", "https://myapp.com"], + database: { provider: "postgresql", url: process.env.DATABASE_URL! }, + auth: { + session: { expiresIn: 60 * 60 * 24 * 7, updateAge: 60 * 60 * 24 }, + emailVerification: { enabled: true, expiresIn: 60 * 60 * 24 }, + passwordReset: { enabled: true, expiresIn: 60 * 60 }, + }, +} + +const app = await Effect.runPromise(createAuthApp(config)) +``` + +## Endpoints de API + +### Autenticación + +- POST `/api/auth/sign-up` - Registrar nuevo usuario +- POST `/api/auth/sign-in` - Iniciar sesión +- POST `/api/auth/sign-out` - Cerrar sesión +- GET `/api/auth/session` - Obtener sesión actual + +### Gestión de usuario + +- GET `/api/auth/user` - Perfil actual diff --git a/apps/docs/src/content/docs/es/backend/packages/node-better-auth.md b/apps/docs/src/content/docs/es/backend/packages/node-better-auth.md new file mode 100644 index 00000000..056ff591 --- /dev/null +++ b/apps/docs/src/content/docs/es/backend/packages/node-better-auth.md @@ -0,0 +1,104 @@ +--- +title: "@effectify/node-better-auth" +description: Integración de Effect con better-auth para aplicaciones Node.js +--- + +# @effectify/node-better-auth + +El paquete `@effectify/node-better-auth` integra Effect con better-auth para aplicaciones Node.js. Permite usar las capacidades de autenticación de better-auth aprovechando el manejo de errores, componibilidad y seguridad de tipos de Effect. + +## Instalación + +```bash +npm install @effectify/node-better-auth better-auth effect +``` + +## Uso básico + +### Configurar Better Auth con Effect + +```typescript +import { betterAuth } from "better-auth" +import { Effect } from "effect" + +export const auth = betterAuth({ + database: { provider: "sqlite", url: process.env.DATABASE_URL || "auth.db" }, + emailAndPassword: { enabled: true, requireEmailVerification: false }, + session: { expiresIn: 60 * 60 * 24 * 7, updateAge: 60 * 60 * 24 }, + secret: process.env.BETTER_AUTH_SECRET!, + baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000", +}) + +export const AuthService = { + signUp: (email: string, password: string, name?: string) => + Effect.tryPromise({ + try: () => auth.api.signUpEmail({ body: { email, password, name } }), + catch: (error) => new AuthenticationError("Sign up failed", { cause: error }), + }), + signIn: (email: string, password: string) => + Effect.tryPromise({ + try: () => auth.api.signInEmail({ body: { email, password } }), + catch: (error) => new AuthenticationError("Sign in failed", { cause: error }), + }), + signOut: (sessionToken: string) => + Effect.tryPromise({ + try: () => + auth.api.signOut({ + headers: { authorization: `Bearer ${sessionToken}` }, + }), + catch: (error) => new AuthenticationError("Sign out failed", { cause: error }), + }), +} +``` + +### Integración con Express.js + +```typescript +import express from "express" +import { Effect } from "effect" +import { auth, AuthService } from "./auth" + +const app = express() +app.use(express.json()) +app.use("/api/auth", auth.handler) + +app.post("/api/register", (req, res) => { + const { email, password, name } = req.body + Effect.runPromise( + AuthService.signUp(email, password, name).pipe( + Effect.map((result) => + res + .status(201) + .json({ success: true, user: result.user, session: result.session }) + ), + Effect.catchTag( + "AuthenticationError", + (error) => Effect.sync(() => res.status(400).json({ error: error.message })), + ), + Effect.catchAll(() => Effect.sync(() => res.status(500).json({ error: "Internal server error" }))), + ), + ) +}) +``` + +## Características avanzadas + +### Errores personalizados + +```typescript +import { Data } from "effect" + +export class AuthenticationError extends Data.TaggedError( + "AuthenticationError", +)<{ readonly message: string; readonly cause?: unknown }> {} +export class AuthorizationError extends Data.TaggedError("AuthorizationError")<{ + readonly message: string + readonly requiredRole?: string +}> {} +export class SessionExpiredError extends Data.TaggedError( + "SessionExpiredError", +)<{ readonly sessionId: string }> {} +export class InvalidTokenError extends Data.TaggedError("InvalidTokenError")<{ + readonly token: string +}> {} +``` diff --git a/apps/docs/src/content/docs/es/index.mdx b/apps/docs/src/content/docs/es/index.mdx new file mode 100644 index 00000000..bfd2d35b --- /dev/null +++ b/apps/docs/src/content/docs/es/index.mdx @@ -0,0 +1,145 @@ +--- +title: Bienvenido a Effectify +description: Aplicaciones componibles y con tipos seguros usando Effect en React, SolidJS y Node.js +template: splash +hero: + title: | + Bienvenido a + + Effectify + + tagline: Crea aplicaciones robustas y con tipos seguros usando Effect en React, SolidJS y Node.js + image: + file: ../../../assets/houston.webp + actions: + - text: Empezar + link: es/react/getting-started/ + icon: right-arrow + - text: Ver en GitHub + link: https://github.com/devx-op/effectify + icon: external + variant: minimal +banner: + content: | + Effectify está en desarrollo activo. Danos una estrella en + GitHub + para seguir el avance. +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## ¿Qué es Effectify? + +Effectify es un conjunto integral de paquetes que lleva el poder de [Effect](https://effect.website/) al desarrollo web moderno. Proporciona soluciones componibles y con tipos seguros para aplicaciones en React, SolidJS y Node.js, permitiéndote construir software robusto con excelente manejo de errores y experiencia de desarrollo. + +## Plataformas + + + + Integración con TanStack Query, biblioteca de UI y funcionalidades de chat en tiempo real para aplicaciones React. + + [Explorar paquetes de React →](es/react/) + + + Integración reactiva de Effect optimizada para el sistema de reactividad de SolidJS, con componentes de UI y chat. + + [Explorar paquetes de SolidJS →](es/solid/) + + + Servicios de autenticación, integración con better-auth y soluciones completas de backend construidas con Effect. + + [Explorar paquetes de Backend →](es/backend/) + + + Lógica de dominio, tipos y reglas de negocio compartidas que funcionan en todas las plataformas y frameworks. + + [Explorar paquetes Universales →](es/universal/) + + + + +## Características clave + +### Tipos seguros primero + +Cada paquete está construido con TypeScript y el potente sistema de tipos de Effect, ofreciendo garantías en tiempo de compilación y excelente soporte en el IDE. + +### Arquitectura componible + +Construye aplicaciones complejas a partir de piezas simples y componibles usando los patrones funcionales de Effect. + +### Manejo de errores + +Manejo robusto de errores con el sistema de gestión de errores de Effect, haciendo tus aplicaciones más confiables y fáciles de depurar. + +### Consistencia multiplataforma + +Comparte lógica de negocio y tipos entre aplicaciones de React, SolidJS y Node.js para un comportamiento consistente. + +## Inicio rápido + +Elige tu plataforma para comenzar: + + + + ```bash + npm install @effectify/react-query @tanstack/react-query effect + ``` + + [Inicio rápido React →](es/react/getting-started/) + + + ```bash + npm install @effectify/solid-query @tanstack/solid-query effect + ``` + + [Inicio rápido SolidJS →](es/solid/getting-started/) + + + ```bash + npm install @effectify/node-better-auth better-auth effect + ``` + + [Inicio rápido Backend →](es/backend/getting-started/) + + + + +## Aplicaciones de ejemplo + +Explora aplicaciones completas construidas con Effectify: + +- **[React SPA](https://github.com/devx-op/effectify/tree/main/apps/react-app-spa)** - Aplicación de una sola página con autenticación y chat +- **[SolidJS SPA](https://github.com/devx-op/effectify/tree/main/apps/solid-app-spa)** - Aplicación SolidJS con chat reactivo +- **[SolidJS Start](https://github.com/devx-op/effectify/tree/main/apps/solid-app-start)** - Aplicación full‑stack con SolidStart +- **[Servicio de Auth en Node.js](https://github.com/devx-op/effectify/tree/main/apps/node-auth-app)** - Microservicio de autenticación completo + +## Comunidad y soporte + + + + Consulta el código fuente, reporta issues y contribuye al proyecto. + + [Visitar GitHub →](https://github.com/devx-op/effectify) + + + Únete a las discusiones de la comunidad, haz preguntas y comparte tus proyectos. + + [Unirse a Discussions →](https://github.com/devx-op/effectify/discussions) + + + Conéctate con la comunidad de Effect para soporte y colaboración. + + [Unirse a Discord →](https://discord.gg/effect-ts) + + + + +## Verificación de despliegue + +Esta actualización verifica el despliegue en GitHub Pages desencadenado por cambios afectados en el proyecto de documentación con Nx. diff --git a/apps/docs/src/content/docs/es/react/getting-started.md b/apps/docs/src/content/docs/es/react/getting-started.md new file mode 100644 index 00000000..d8bd96c1 --- /dev/null +++ b/apps/docs/src/content/docs/es/react/getting-started.md @@ -0,0 +1,151 @@ +--- +title: Primeros pasos con React +description: Aprende a configurar Effectify en tu aplicación React +--- + +# Primeros pasos con React + +Esta guía te ayudará a empezar con Effectify en tu aplicación React. Veremos cómo configurar las dependencias básicas y crear tu primer componente con Effect. + +## Requisitos previos + +- Node.js 18 o superior +- Una aplicación React (CRA, Vite, Next.js, etc.) +- Conocimientos básicos de React y TypeScript + +## Instalación + +Elige los paquetes que necesitas: + +### Paquete principal de queries + +```bash +npm install @effectify/react-query @tanstack/react-query effect +``` + +### Componentes de UI + +```bash +npm install @effectify/react-ui +``` + +### Componentes de Chat + +```bash +npm install @effectify/chat-react +``` + +## Configuración básica + +### 1. Configurar TanStack Query + +```tsx +// App.tsx +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { ReactQueryDevtools } from "@tanstack/react-query-devtools" + +const queryClient = new QueryClient() + +function App() { + return ( + + + + + ) +} +``` + +### 2. Crear tu primer Effect + +```tsx +// effects/user.ts +import { Effect } from "effect" + +export interface User { + id: number + name: string + email: string +} + +export const fetchUser = (id: number) => + Effect.tryPromise({ + try: () => fetch(`/api/users/${id}`).then((res) => res.json()), + catch: (error) => new Error(`Failed to fetch user: ${error}`), + }) +``` + +### 3. Usar Effect con React Query + +```tsx +// components/UserProfile.tsx +import { useQuery } from "@tanstack/react-query" +import { Effect } from "effect" +import { fetchUser } from "../effects/user" + +interface UserProfileProps { + userId: number +} + +export function UserProfile({ userId }: UserProfileProps) { + const { + data: user, + isLoading, + error, + } = useQuery({ + queryKey: ["user", userId], + queryFn: () => Effect.runPromise(fetchUser(userId)), + }) + + if (isLoading) return
Cargando...
+ if (error) return
Error: {error.message}
+ if (!user) return
Usuario no encontrado
+ + return ( +
+

{user.name}

+

{user.email}

+
+ ) +} +``` + +## Siguientes pasos + +- [Integración React Query](packages/react-query/) - Patrones avanzados de obtención de datos +- [Componentes UI](packages/react-ui/) - Explora la biblioteca +- [Componentes de Chat](packages/chat-react/) - Añade tiempo real + +## Patrones comunes + +### Manejo de errores + +```tsx +const fetchUserWithRetry = (id: number) => + fetchUser(id).pipe( + Effect.retry({ times: 3 }), + Effect.catchAll(() => Effect.succeed({ id, name: "Desconocido", email: "unknown@example.com" })), + ) +``` + +### Combinación de Effects + +```tsx +const fetchUserWithPosts = (id: number) => + Effect.all([fetchUser(id), fetchUserPosts(id)]).pipe( + Effect.map(([user, posts]) => ({ user, posts })), + ) +``` + +## Resolución de problemas + +### Problemas comunes + +1. Effect no corre: Asegúrate de llamar a `Effect.runPromise()` en la query +2. Errores de tipos: Usa versiones actuales de Effect y TypeScript +3. Errores de build: Verifica peer dependencies instaladas + +### Ayuda + +- [GitHub Issues](https://github.com/devx-op/effectify/issues) +- [Discusiones](https://github.com/devx-op/effectify/discussions) diff --git a/apps/docs/src/content/docs/es/react/index.mdx b/apps/docs/src/content/docs/es/react/index.mdx new file mode 100644 index 00000000..f0a95338 --- /dev/null +++ b/apps/docs/src/content/docs/es/react/index.mdx @@ -0,0 +1,55 @@ +--- +title: Integración con React +description: Paquetes de integración de Effect para aplicaciones React +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +# Integración con React + +Effectify ofrece integraciones potentes para React que combinan la seguridad de tipos y la componibilidad de Effect con el ecosistema de React. Nuestros paquetes te permiten construir aplicaciones robustas y mantenibles con excelente experiencia de desarrollo. + +## Paquetes disponibles + + + + Integración de Effect con TanStack Query para aplicaciones React. + Proporciona obtención de datos con tipos seguros y manejo de errores con + Effect. [Más información →](packages/react-query/) + + + + Biblioteca de componentes de UI construida con React, Radix UI y Tailwind + CSS. Incluye formularios, layouts y componentes interactivos. [Más + información →](packages/react-ui/) + + + + Componentes y servicios de chat en tiempo real para React. Construidos con + Effect para un manejo de estado y errores robusto. [Más información + →](packages/chat-react/) + + + +## Primeros pasos + +¿Listo para construir con Effectify y React? Revisa nuestra guía de inicio para configurar tu primer proyecto. + + + Pon en marcha Effectify en tu aplicación React en minutos. [Empezar + →](getting-started/) + + +## Características clave + +- Seguridad de tipos: Soporte completo de TypeScript con Effect +- Manejo de errores: Gestión robusta de errores con Effect +- Componibilidad: Construye aplicaciones complejas a partir de piezas simples +- Experiencia de desarrollo: Herramientas y depuración de primer nivel +- Rendimiento: Optimizado para aplicaciones React modernas + +## Comunidad y soporte + +- [Repositorio en GitHub](https://github.com/devx-op/effectify) +- [Issues y bugs](https://github.com/devx-op/effectify/issues) +- [Discusiones](https://github.com/devx-op/effectify/discussions) diff --git a/apps/docs/src/content/docs/es/react/installation.md b/apps/docs/src/content/docs/es/react/installation.md new file mode 100644 index 00000000..7917509b --- /dev/null +++ b/apps/docs/src/content/docs/es/react/installation.md @@ -0,0 +1,228 @@ +--- +title: Instalación +description: Cómo instalar los paquetes de React de Effectify en tu proyecto +--- + +# Instalación + +Esta guía cubre cómo instalar y configurar los paquetes de React de Effectify en tu proyecto. + +## Gestor de paquetes + +Usa tu gestor favorito. Los ejemplos usan npm, pero puedes usar yarn, pnpm o bun. + +## Paquetes principales + +### @effectify/react-query + +Integración de Effect con TanStack Query para React: + +```bash +npm install @effectify/react-query +``` + +**Peer Dependencies:** + +```bash +npm install @tanstack/react-query effect react +``` + +### @effectify/react-ui + +Biblioteca de UI con Radix UI y Tailwind CSS: + +```bash +npm install @effectify/react-ui +``` + +**Peer Dependencies:** + +```bash +npm install react react-dom tailwindcss +``` + +### @effectify/chat-react + +Componentes de chat en tiempo real: + +```bash +npm install @effectify/chat-react +``` + +**Peer Dependencies:** + +```bash +npm install @effectify/react-query @effectify/chat-domain react +``` + +## Configuración por framework + +### Create React App + +```bash +npx create-react-app my-app --template typescript +cd my-app +npm install @effectify/react-query @tanstack/react-query effect +``` + +Configura TanStack Query en `src/index.tsx`: + +```tsx +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" + +const queryClient = new QueryClient() + +root.render( + + + , +) +``` + +### Vite + +```bash +npm create vite@latest my-app -- --template react-ts +cd my-app +npm install @effectify/react-query @tanstack/react-query effect +``` + +Configura en `src/main.tsx`: + +```tsx +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" + +const queryClient = new QueryClient() + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + , +) +``` + +### Next.js + +```bash +npx create-next-app@latest my-app --typescript +cd my-app +npm install @effectify/react-query @tanstack/react-query effect +``` + +Crea `app/providers.tsx`: + +```tsx +"use client" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { useState } from "react" + +export function Providers({ children }: { children: React.ReactNode }) { + const [queryClient] = useState(() => new QueryClient()) + return {children} +} +``` + +Actualiza `app/layout.tsx`: + +```tsx +import { Providers } from "./providers" + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {children} + + + ) +} +``` + +## Configuración de Tailwind CSS + +Si usas `@effectify/react-ui`, configura Tailwind: + +```bash +npm install -D tailwindcss postcss autoprefixer +npx tailwindcss init -p +``` + +`tailwind.config.js`: + +```js +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{js,ts,jsx,tsx}", + "./node_modules/@effectify/react-ui/**/*.{js,ts,jsx,tsx}", + ], + theme: { extend: {} }, + plugins: [], +} +``` + +Directivas en CSS: + +```css +/* src/index.css */ +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +## Configuración de TypeScript + +Asegura que tu `tsconfig.json` incluya: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["DOM", "DOM.Iterable", "ES6"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} +``` + +## Verificación + +Crea un componente simple: + +```tsx +// src/components/TestComponent.tsx +import { useQuery } from "@tanstack/react-query" +import { Effect } from "effect" + +const testEffect = Effect.succeed("Effectify funciona!") + +export function TestComponent() { + const { data } = useQuery({ + queryKey: ["test"], + queryFn: () => Effect.runPromise(testEffect), + }) + return
{data}
+} +``` + +Si ves "Effectify funciona!" renderizado, la instalación fue exitosa. + +## Siguientes pasos + +- [Guía de inicio](getting-started/) +- [React Query](packages/react-query/) +- [Componentes UI](packages/react-ui/) diff --git a/apps/docs/src/content/docs/es/react/packages/chat-react.md b/apps/docs/src/content/docs/es/react/packages/chat-react.md new file mode 100644 index 00000000..605c8adb --- /dev/null +++ b/apps/docs/src/content/docs/es/react/packages/chat-react.md @@ -0,0 +1,177 @@ +--- +title: "@effectify/chat-react" +description: Componentes y servicios de chat en tiempo real para aplicaciones React +--- + +# @effectify/chat-react + +El paquete `@effectify/chat-react` ofrece componentes y servicios de chat en tiempo real para aplicaciones React. Construido con Effect para un manejo de estado y errores robusto, incluye soporte WebSocket, persistencia de mensajes y gestión de usuarios. + +## Instalación + +```bash +npm install @effectify/chat-react +``` + +**Peer Dependencies:** + +```bash +npm install @effectify/react-query @effectify/chat-domain react +``` + +## Uso básico + +### Componente simple de chat + +```tsx +import { ChatRoom } from "@effectify/chat-react/components/chat-room" +import { ChatProvider } from "@effectify/chat-react/components/chat-provider" + +function App() { + return ( + + + + ) +} +``` + +### Interfaz de chat personalizada + +```tsx +import { ChatInput, ChatMessages, ChatUserList } from "@effectify/chat-react/components" +import { useChatRoom } from "@effectify/chat-react/hooks/use-chat-room" + +function CustomChatRoom() { + const { messages, users, sendMessage, isConnected } = useChatRoom() + + return ( +
+
+
+ +
+ +
+
+ +
+
+ ) +} +``` + +## Componentes + +### ChatProvider + +Proveedor raíz que gestiona el estado del chat y las conexiones WebSocket: + +```tsx +import { ChatProvider } from "@effectify/chat-react/components/chat-provider" + +function App() { + return ( + + + + ) +} +``` + +### ChatRoom + +Componente de sala de chat completa: + +```tsx +import { ChatRoom } from "@effectify/chat-react/components/chat-room" + +function MyChatApp() { + return ( +
+ +
+ ) +} +``` + +### ChatMessages + +Lista de mensajes con formato: + +```tsx +import { ChatMessages } from "@effectify/chat-react/components/chat-messages" +import { useChatRoom } from "@effectify/chat-react/hooks/use-chat-room" + +function MessageArea() { + const { messages } = useChatRoom() + return ( + ( +
+ {message.user.name}: {message.content} +
+ )} + /> + ) +} +``` + +### ChatInput + +Componente de entrada para enviar mensajes: + +```tsx +import { ChatInput } from "@effectify/chat-react/components/chat-input" +import { useChatRoom } from "@effectify/chat-react/hooks/use-chat-room" + +function MessageInput() { + const { sendMessage, isConnected } = useChatRoom() + return ( + { + // Indicador de escritura + }} + /> + ) +} +``` + +### ChatUserList + +Lista de usuarios en línea: + +```tsx +import { ChatUserList } from "@effectify/chat-react/components/chat-user-list" +import { useChatRoom } from "@effectify/chat-react/hooks/use-chat-room" + +function UserSidebar() { + const { users } = useChatRoom() + return ( +
+

Usuarios en línea

+ ( +
+
+ {user.name} +
+ )} + /> +
+ ) +} +``` diff --git a/apps/docs/src/content/docs/es/react/packages/react-query.md b/apps/docs/src/content/docs/es/react/packages/react-query.md new file mode 100644 index 00000000..7a9cc2d0 --- /dev/null +++ b/apps/docs/src/content/docs/es/react/packages/react-query.md @@ -0,0 +1,168 @@ +--- +title: "@effectify/react-query" +description: Integración de Effect con TanStack Query para aplicaciones React +--- + +# @effectify/react-query + +El paquete `@effectify/react-query` integra Effect con TanStack Query para aplicaciones React. Aprovecha el manejo de errores y la componibilidad de Effect junto con el caché y sincronización de TanStack Query. + +## Instalación + +```bash +npm install @effectify/react-query @tanstack/react-query effect react +``` + +## Uso básico + +### Obtención simple de datos + +```tsx +import { useQuery } from "@tanstack/react-query" +import { Effect } from "effect" + +const fetchUser = (id: number) => + Effect.tryPromise({ + try: () => fetch(`/api/users/${id}`).then((res) => res.json()), + catch: (error) => new Error(`Failed to fetch user: ${error}`), + }) + +function UserProfile({ userId }: { userId: number }) { + const { data, isLoading, error } = useQuery({ + queryKey: ["user", userId], + queryFn: () => Effect.runPromise(fetchUser(userId)), + }) + + if (isLoading) return
Cargando...
+ if (error) return
Error: {error.message}
+ return
Hola, {data?.name}!
+} +``` + +### Con manejo de errores + +```tsx +import { Effect, pipe } from "effect" + +const fetchUserWithRetry = (id: number) => + pipe( + fetchUser(id), + Effect.retry({ times: 3, delay: "1 second" }), + Effect.catchAll(() => Effect.succeed({ id, name: "Usuario desconocido", email: "unknown@example.com" })), + ) + +function UserProfile({ userId }: { userId: number }) { + const { data } = useQuery({ + queryKey: ["user", userId], + queryFn: () => Effect.runPromise(fetchUserWithRetry(userId)), + }) + return
Hola, {data?.name}!
+} +``` + +## Patrones avanzados + +### Combinando múltiples Effects + +```tsx +const fetchUserWithPosts = (userId: number) => + Effect.all([fetchUser(userId), fetchUserPosts(userId)]).pipe( + Effect.map(([user, posts]) => ({ user, posts })), + ) + +function UserDashboard({ userId }: { userId: number }) { + const { data, isLoading } = useQuery({ + queryKey: ["userDashboard", userId], + queryFn: () => Effect.runPromise(fetchUserWithPosts(userId)), + }) + + if (isLoading) return
Cargando panel...
+ return ( +
+

{data?.user.name}

+
Posts: {data?.posts.length}
+
+ ) +} +``` + +### Tipos de errores personalizados + +```tsx +class UserNotFoundError { + readonly _tag = "UserNotFoundError" + constructor(readonly userId: number) {} +} + +class NetworkError { + readonly _tag = "NetworkError" + constructor(readonly message: string) {} +} + +const fetchUserTyped = (id: number) => + Effect.tryPromise({ + try: async () => { + const response = await fetch(`/api/users/${id}`) + if (response.status === 404) throw new UserNotFoundError(id) + if (!response.ok) throw new NetworkError(`HTTP ${response.status}`) + return response.json() + }, + catch: (error) => { + if (error instanceof UserNotFoundError) return error + if (error instanceof NetworkError) return error + return new NetworkError(String(error)) + }, + }) + +function UserProfile({ userId }: { userId: number }) { + const { data, error } = useQuery({ + queryKey: ["user", userId], + queryFn: () => Effect.runPromise(fetchUserTyped(userId)), + }) + + if (error) { + if (error instanceof UserNotFoundError) return
Usuario {error.userId} no encontrado
+ if (error instanceof NetworkError) return
Error de red: {error.message}
+ } + return
Hola, {data?.name}!
+} +``` + +### Mutations con Effect + +```tsx +import { useMutation, useQueryClient } from "@tanstack/react-query" + +const updateUser = (id: number, data: Partial) => + Effect.tryPromise({ + try: () => + fetch(`/api/users/${id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }).then((res) => res.json()), + catch: (error) => new Error(`Failed to update user: ${error}`), + }) + +function EditUserForm({ userId }: { userId: number }) { + const queryClient = useQueryClient() + const mutation = useMutation({ + mutationFn: (data: Partial) => Effect.runPromise(updateUser(userId, data)), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["user", userId] }) + }, + }) + + const handleSubmit = (formData: Partial) => { + mutation.mutate(formData) + } + + return ( +
+ +
+ ) +} +``` diff --git a/apps/docs/src/content/docs/es/react/packages/react-ui.md b/apps/docs/src/content/docs/es/react/packages/react-ui.md new file mode 100644 index 00000000..6808d17b --- /dev/null +++ b/apps/docs/src/content/docs/es/react/packages/react-ui.md @@ -0,0 +1,198 @@ +--- +title: "@effectify/react-ui" +description: Biblioteca de componentes de UI para aplicaciones React +--- + +# @effectify/react-ui + +El paquete `@effectify/react-ui` proporciona un conjunto completo de componentes de UI construidos con React, Radix UI y Tailwind CSS. Incluye formularios, layouts, componentes interactivos y utilidades diseñadas para funcionar con aplicaciones basadas en Effect. + +## Instalación + +```bash +npm install @effectify/react-ui +``` + +**Peer Dependencies:** + +```bash +npm install react react-dom tailwindcss +``` + +## Configuración + +### 1. Configura Tailwind CSS + +Añade el paquete a tu configuración de Tailwind: + +```js +// tailwind.config.js +module.exports = { + content: [ + "./src/**/*.{js,ts,jsx,tsx}", + "./node_modules/@effectify/react-ui/**/*.{js,ts,jsx,tsx}", + ], + theme: { extend: {} }, + plugins: [], +} +``` + +### 2. Importa estilos globales + +```tsx +// src/index.tsx o src/main.tsx +import "@effectify/react-ui/globals.css" +``` + +## Componentes + +### Componentes de formulario + +#### Button + +```tsx +import { Button } from "@effectify/react-ui/components/button" + +function MyComponent() { + return ( +
+ + + + + + +
+ ) +} +``` + +#### Input + +```tsx +import { Input } from "@effectify/react-ui/components/input" +import { Label } from "@effectify/react-ui/components/label" + +function LoginForm() { + return ( +
+
+ + +
+
+ + +
+
+ ) +} +``` + +#### Form con TanStack Form + +```tsx +import { useForm } from "@tanstack/react-form" +import { Button } from "@effectify/react-ui/components/button" +import { Input } from "@effectify/react-ui/components/input" +import { Label } from "@effectify/react-ui/components/label" + +interface LoginData { + email: string + password: string +} + +function LoginForm() { + const form = useForm({ + defaultValues: { email: "", password: "" }, + onSubmit: async ({ value }) => { + console.log("Form data:", value) + }, + }) + + return ( +
{ + e.preventDefault() + e.stopPropagation() + form.handleSubmit() + }} + className="space-y-4" + > + ( +
+ + field.handleChange(e.target.value)} + type="email" + placeholder="Introduce tu email" + /> + {field.state.meta.errors &&

{field.state.meta.errors.join(", ")}

} +
+ )} + /> + + ( +
+ + field.handleChange(e.target.value)} + type="password" + placeholder="Introduce tu contraseña" + /> + {field.state.meta.errors &&

{field.state.meta.errors.join(", ")}

} +
+ )} + /> + + + + ) +} +``` + +### Componentes de layout + +#### Card + +```tsx +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@effectify/react-ui/components/card" +import { Button } from "@effectify/react-ui/components/button" + +function UserCard({ user }) { + return ( + + + {user.name} + {user.email} + + +

Miembro desde {user.joinDate}

+
+ + + + +
+ ) +} +``` diff --git a/apps/docs/src/content/docs/es/react/reference/api.md b/apps/docs/src/content/docs/es/react/reference/api.md new file mode 100644 index 00000000..8a4d41a9 --- /dev/null +++ b/apps/docs/src/content/docs/es/react/reference/api.md @@ -0,0 +1,218 @@ +--- +title: Referencia de API +description: Referencia completa de API para los paquetes React de Effectify +--- + +# Referencia de API + +Esta página proporciona una referencia completa de API para todos los paquetes React de Effectify. + +## @effectify/react-query + +### Funciones + +#### `createEffectQuery` + +Crea una función de query que integra Effect con TanStack Query. + +```tsx +function createEffectQuery(effect: Effect): () => Promise +``` + +Parámetros: + +- `effect`: El Effect a ejecutar + +Retorna: + +- Función utilizable como `queryFn` de TanStack Query + +Ejemplo: + +```tsx +const fetchUser = (id: number) => Effect.succeed({ id, name: "John" }) +const queryFn = createEffectQuery(fetchUser(1)) +const { data } = useQuery({ queryKey: ["user", 1], queryFn }) +``` + +#### `createEffectMutation` + +Crea una función de mutation que integra Effect con TanStack Query. + +```tsx +function createEffectMutation(effect: (args: A) => Effect): (args: A) => Promise +``` + +### Hooks + +#### `useEffectQuery` + +Hook que combina useQuery con ejecución de Effect. + +```tsx +function useEffectQuery( + queryKey: QueryKey, + effect: Effect, + options?: UseQueryOptions, +): UseQueryResult +``` + +#### `useEffectMutation` + +Hook que combina useMutation con ejecución de Effect. + +```tsx +function useEffectMutation( + effect: (args: A) => Effect, + options?: UseMutationOptions, +): UseMutationResult +``` + +## @effectify/react-ui + +### Componentes + +#### Button + +```tsx +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" + size?: "default" | "sm" | "lg" | "icon" + asChild?: boolean +} +function Button(props: ButtonProps): JSX.Element +``` + +#### Input + +```tsx +interface InputProps extends React.InputHTMLAttributes { + error?: boolean +} +function Input(props: InputProps): JSX.Element +``` + +#### Card + +```tsx +interface CardProps extends React.HTMLAttributes {} +function Card(props: CardProps): JSX.Element +interface CardHeaderProps extends React.HTMLAttributes {} +function CardHeader(props: CardHeaderProps): JSX.Element +interface CardTitleProps extends React.HTMLAttributes {} +function CardTitle(props: CardTitleProps): JSX.Element +interface CardDescriptionProps extends React.HTMLAttributes {} +function CardDescription(props: CardDescriptionProps): JSX.Element +interface CardContentProps extends React.HTMLAttributes {} +function CardContent(props: CardContentProps): JSX.Element +interface CardFooterProps extends React.HTMLAttributes {} +function CardFooter(props: CardFooterProps): JSX.Element +``` + +#### Dialog + +```tsx +interface DialogProps { + children: React.ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void +} +function Dialog(props: DialogProps): JSX.Element +function DialogTrigger(props: { children: React.ReactNode; asChild?: boolean }): JSX.Element +function DialogContent(props: React.HTMLAttributes): JSX.Element +function DialogHeader(props: React.HTMLAttributes): JSX.Element +function DialogTitle(props: React.HTMLAttributes): JSX.Element +function DialogDescription(props: React.HTMLAttributes): JSX.Element +function DialogFooter(props: React.HTMLAttributes): JSX.Element +``` + +### Utilidades + +#### `cn` + +Utilidad para combinar classNames con precedencia adecuada. + +```tsx +function cn(...inputs: ClassValue[]): string +``` + +#### Funciones de validación + +```tsx +function validateEmail(email: string): string | undefined +function validateRequired(value: any): string | undefined +function validateMinLength(value: string, min: number): string | undefined +function validateMaxLength(value: string, max: number): string | undefined +``` + +## @effectify/chat-react + +### Componentes + +#### ChatProvider + +```tsx +interface ChatProviderProps { + userId: string + roomId: string + websocketUrl: string + options?: ChatOptions + children: React.ReactNode +} +interface ChatOptions { + reconnectAttempts?: number + reconnectDelay?: number + messageHistory?: number + heartbeatInterval?: number +} +function ChatProvider(props: ChatProviderProps): JSX.Element +``` + +#### ChatRoom + +```tsx +interface ChatRoomProps extends React.HTMLAttributes { + showUserList?: boolean + showTypingIndicator?: boolean + messageLimit?: number +} +function ChatRoom(props: ChatRoomProps): JSX.Element +``` + +#### ChatMessages + +```tsx +interface ChatMessagesProps extends React.HTMLAttributes { + messages: Message[] + renderMessage?: (message: Message) => React.ReactNode + onLoadMore?: () => void + hasMore?: boolean +} +function ChatMessages(props: ChatMessagesProps): JSX.Element +``` + +#### ChatInput + +```tsx +interface ChatInputProps extends React.HTMLAttributes { + onSendMessage: (message: { content: string; type: MessageType }) => void + disabled?: boolean + placeholder?: string + maxLength?: number + showEmojiPicker?: boolean + onTyping?: () => void + onStopTyping?: () => void +} +function ChatInput(props: ChatInputProps): JSX.Element +``` + +#### ChatUserList + +```tsx +interface ChatUserListProps extends React.HTMLAttributes { + users: User[] + renderUser?: (user: User) => React.ReactNode + onUserClick?: (user: User) => void +} +function ChatUserList(props: ChatUserListProps): JSX.Element +``` diff --git a/apps/docs/src/content/docs/es/solid/getting-started.md b/apps/docs/src/content/docs/es/solid/getting-started.md new file mode 100644 index 00000000..2c2d077b --- /dev/null +++ b/apps/docs/src/content/docs/es/solid/getting-started.md @@ -0,0 +1,207 @@ +--- +title: Primeros pasos con SolidJS +description: Aprende a configurar Effectify en tu aplicación SolidJS +--- + +# Primeros pasos con SolidJS + +Esta guía te ayudará a empezar con Effectify en tu aplicación SolidJS. Veremos cómo configurar las dependencias básicas y crear tu primer componente con Effect. + +## Requisitos previos + +- Node.js 18 o superior +- Una aplicación SolidJS (plantilla Vite, SolidStart, etc.) +- Conocimientos básicos de SolidJS y TypeScript + +## Instalación + +Elige los paquetes que necesitas: + +### Paquete principal de queries + +```bash +npm install @effectify/solid-query @tanstack/solid-query effect solid-js +``` + +### Componentes de UI + +```bash +npm install @effectify/solid-ui +``` + +### Componentes de Chat + +```bash +npm install @effectify/chat-solid +``` + +## Configuración básica + +### 1. Configurar TanStack Query + +```tsx +// src/App.tsx +import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" +import { SolidQueryDevtools } from "@tanstack/solid-query-devtools" + +const queryClient = new QueryClient() + +function App() { + return ( + + + + + ) +} + +export default App +``` + +### 2. Crear tu primer Effect + +```tsx +// src/effects/user.ts +import { Effect } from "effect" + +export interface User { + id: number + name: string + email: string +} + +export const fetchUser = (id: number) => + Effect.tryPromise({ + try: () => fetch(`/api/users/${id}`).then((res) => res.json()), + catch: (error) => new Error(`Failed to fetch user: ${error}`), + }) +``` + +### 3. Usar Effect con recursos de SolidJS + +```tsx +// src/components/UserProfile.tsx +import { createResource, Show } from "solid-js" +import { Effect } from "effect" +import { fetchUser } from "../effects/user" + +interface UserProfileProps { + userId: number +} + +export function UserProfile(props: UserProfileProps) { + const [user] = createResource(() => props.userId, (id) => Effect.runPromise(fetchUser(id))) + + return ( + Cargando...
}> + {(userData) => ( +
+

{userData().name}

+

{userData().email}

+
+ )} + + ) +} +``` + +### 4. Usar Effect con TanStack Query + +```tsx +// src/components/UserProfileQuery.tsx +import { createQuery } from "@tanstack/solid-query" +import { Show } from "solid-js" +import { Effect } from "effect" +import { fetchUser } from "../effects/user" + +interface UserProfileProps { + userId: number +} + +export function UserProfileQuery(props: UserProfileProps) { + const userQuery = createQuery(() => ({ + queryKey: ["user", props.userId], + queryFn: () => Effect.runPromise(fetchUser(props.userId)), + })) + + return ( + Cargando...}> + {(user) => ( +
+

{user().name}

+

{user().email}

+ +

Error: {userQuery.error?.message}

+
+
+ )} +
+ ) +} +``` + +## Patrones avanzados + +### Effects reactivos con señales + +```tsx +import { createEffect, createSignal, For, Show } from "solid-js" +import { Effect } from "effect" + +function UserSearch() { + const [query, setQuery] = createSignal("") + const [results, setResults] = createSignal([]) + const [loading, setLoading] = createSignal(false) + + const searchUsers = (searchQuery: string) => + Effect.tryPromise({ + try: () => fetch(`/api/users/search?q=${searchQuery}`).then((res) => res.json()), + catch: (error) => new Error(`Search failed: ${error}`), + }) + + createEffect(() => { + const currentQuery = query() + if (currentQuery.length > 2) { + setLoading(true) + Effect.runPromise( + searchUsers(currentQuery).pipe( + Effect.tap((results) => Effect.sync(() => setResults(results))), + Effect.catchAll((error) => Effect.sync(() => console.error(error))), + Effect.finalize(() => Effect.sync(() => setLoading(false))), + ), + ) + } + }) + + return ( +
+ setQuery(e.currentTarget.value)} + placeholder="Buscar usuarios..." + /> + +
Buscando...
+
+
    + {(user) =>
  • {user.name}
  • }
    +
+
+ ) +} +``` + +### Manejo de errores con Effect + +```tsx +const fetchUserWithRetry = (id: number) => + fetchUser(id).pipe( + Effect.retry({ times: 3, delay: "1 second" }), + Effect.catchAll(() => Effect.succeed({ id, name: "Desconocido", email: "unknown@example.com" })), + ) + +function UserProfile(props: { userId: number }) { + const [user] = createResource(() => props.userId, (id) => Effect.runPromise(fetchUserWithRetry(id))) +} +``` diff --git a/apps/docs/src/content/docs/es/solid/index.mdx b/apps/docs/src/content/docs/es/solid/index.mdx new file mode 100644 index 00000000..9e2f27e6 --- /dev/null +++ b/apps/docs/src/content/docs/es/solid/index.mdx @@ -0,0 +1,93 @@ +--- +title: Integración con SolidJS +description: Paquetes de integración de Effect para aplicaciones SolidJS +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +# Integración con SolidJS + +Effectify ofrece integraciones potentes para SolidJS que combinan la naturaleza reactiva de SolidJS con el tipado seguro y la componibilidad de Effect. Nuestros paquetes permiten construir aplicaciones de alto rendimiento y mantenibles con excelente experiencia de desarrollo. + +## Paquetes disponibles + + + + Integración de Effect con TanStack Query para SolidJS. Proporciona obtención de datos con tipos seguros y manejo de errores con Effect y reactividad de SolidJS. + + [Más información →](packages/solid-query/) + + + + Biblioteca de componentes de UI construida con SolidJS, Kobalte y Tailwind CSS. Incluye formularios, layouts y componentes interactivos optimizados para SolidJS. + + [Más información →](packages/solid-ui/) + + + + Componentes y servicios de chat en tiempo real para SolidJS. Construidos con Effect para estado robusto y SolidJS para actualizaciones reactivas. + + [Más información →](packages/chat-solid/) + + + +## Primeros pasos + + + Ponte en marcha con Effectify en tu aplicación SolidJS en minutos. + + [Empezar →](getting-started/) + + +## Características clave + +- Reactivo por diseño: Aprovecha la reactividad granular de SolidJS +- Tipos seguros: Soporte completo de TypeScript con Effect +- Manejo de errores: Gestión robusta de errores con Effect +- Rendimiento: Optimizado para compilación y runtime de SolidJS +- Componibilidad: Construye aplicaciones complejas desde piezas componibles +- Experiencia de desarrollo: Herramientas y depuración de primer nivel + +## ¿Por qué SolidJS + Effect? + +- Reactividad granular: Primitivas de SolidJS funcionan con el flujo de datos de Effect +- Optimizaciones de compilación: Ambas optimizan en tiempo de compilación +- Programación funcional: Comparten principios funcionales +- Seguridad de tipos: Fuerte soporte de TypeScript +- Bundle pequeño: Prioridad a bajo overhead de runtime + +## Arquitectura + +```tsx +const fetchUser = (id: number) => + Effect.tryPromise({ + try: () => fetch(`/api/users/${id}`).then(res => res.json()), + catch: (error) => new Error(`Failed to fetch user: ${error}`) + }) + +function UserProfile(props: { userId: number }) { + const [user] = createResource( + () => props.userId, + (id) => Effect.runPromise(fetchUser(id)) + ) + + return ( + Cargando...}> +

{user()?.name}

+
+ ) +} +``` + +## Comunidad y soporte + +- [Repositorio en GitHub](https://github.com/devx-op/effectify) +- [Issues y bugs](https://github.com/devx-op/effectify/issues) +- [Discusiones](https://github.com/devx-op/effectify/discussions) +- [SolidJS Discord](https://discord.com/invite/solidjs) +- [Effect Discord](https://discord.gg/effect-ts) + +## Ejemplos + +- SolidJS SPA: apps/solid-app-spa +- SolidJS Start: apps/solid-app-start diff --git a/apps/docs/src/content/docs/es/solid/installation.md b/apps/docs/src/content/docs/es/solid/installation.md new file mode 100644 index 00000000..ccc850fa --- /dev/null +++ b/apps/docs/src/content/docs/es/solid/installation.md @@ -0,0 +1,202 @@ +--- +title: Instalación +description: Cómo instalar los paquetes de SolidJS de Effectify en tu proyecto +--- + +# Instalación + +Esta guía cubre cómo instalar y configurar los paquetes de SolidJS de Effectify en tu proyecto. + +## Gestor de paquetes + +Usa tu gestor favorito. Los ejemplos usan npm, pero puedes usar yarn, pnpm o bun. + +## Paquetes principales + +### @effectify/solid-query + +Integración de Effect con TanStack Query para SolidJS: + +```bash +npm install @effectify/solid-query +``` + +**Peer Dependencies:** + +```bash +npm install @tanstack/solid-query effect solid-js +``` + +### @effectify/solid-ui + +Biblioteca de componentes de UI con Kobalte y Tailwind CSS: + +```bash +npm install @effectify/solid-ui +``` + +**Peer Dependencies:** + +```bash +npm install solid-js tailwindcss @kobalte/core +``` + +### @effectify/chat-solid + +Componentes de chat en tiempo real: + +```bash +npm install @effectify/chat-solid +``` + +**Peer Dependencies:** + +```bash +npm install @effectify/solid-query @effectify/chat-domain solid-js +``` + +## Configuración específica por framework + +### Vite + SolidJS + +```bash +npm create solid@latest mi-app +cd mi-app +npm install @effectify/solid-query @tanstack/solid-query effect +``` + +Configura TanStack Query en `src/index.tsx`: + +```tsx +import { render } from "solid-js/web" +import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" +import App from "./App" + +const queryClient = new QueryClient() + +render( + () => ( + + + + ), + document.getElementById("root")!, +) +``` + +### SolidStart + +```bash +npm create solid@latest mi-app -- --template solid-start +cd mi-app +npm install @effectify/solid-query @tanstack/solid-query effect +``` + +Crea `src/root.tsx`: + +```tsx +// @refresh reload +import { Suspense } from "solid-js" +import { Body, ErrorBoundary, FileRoutes, Head, Html, Meta, Routes, Scripts, Title } from "solid-start" +import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" + +const queryClient = new QueryClient() + +export default function Root() { + return ( + + + SolidStart - Con Effectify + + + + + + + + + + + + + + + + + ) +} +``` + +### Astro + SolidJS + +```bash +npx astro add solid +npm install @effectify/solid-query @tanstack/solid-query effect +``` + +Crea un proveedor: + +```tsx +// src/components/QueryProvider.tsx +import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" +import type { ParentComponent } from "solid-js" + +const queryClient = new QueryClient() + +export const QueryProvider: ParentComponent = (props) => { + return {props.children} +} +``` + +Úsalo en Astro: + +```astro +--- +// src/pages/index.astro +--- + + + + + + Astro + SolidJS + Effectify + + + + + + + +``` + +## Configuración de Tailwind CSS + +Si usas `@effectify/solid-ui`, configura Tailwind: + +```bash +npm install -D tailwindcss postcss autoprefixer +npx tailwindcss init -p +``` + +`tailwind.config.js`: + +```js +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{js,ts,jsx,tsx}", + "./node_modules/@effectify/solid-ui/**/*.{js,ts,jsx,tsx}", + ], + theme: { extend: {} }, + plugins: [], +} +``` + +Directivas en CSS: + +```css +/* src/index.css */ +@tailwind base; +@tailwind components; +@tailwind utilities; +``` diff --git a/apps/docs/src/content/docs/es/solid/packages/chat-solid.md b/apps/docs/src/content/docs/es/solid/packages/chat-solid.md new file mode 100644 index 00000000..d3125542 --- /dev/null +++ b/apps/docs/src/content/docs/es/solid/packages/chat-solid.md @@ -0,0 +1,177 @@ +--- +title: "@effectify/chat-solid" +description: Componentes y servicios de chat en tiempo real para aplicaciones SolidJS +--- + +# @effectify/chat-solid + +El paquete `@effectify/chat-solid` ofrece componentes y servicios de chat en tiempo real para aplicaciones SolidJS. Construido con Effect para un manejo de estado robusto y SolidJS para actualizaciones reactivas de UI, incluye soporte WebSocket, persistencia de mensajes y gestión de usuarios. + +## Instalación + +```bash +npm install @effectify/chat-solid +``` + +**Peer Dependencies:** + +```bash +npm install @effectify/solid-query @effectify/chat-domain solid-js +``` + +## Uso básico + +### Componente de chat simple + +```tsx +import { ChatRoom } from "@effectify/chat-solid/components/chat-room" +import { ChatProvider } from "@effectify/chat-solid/components/chat-provider" + +function App() { + return ( + + + + ) +} +``` + +### Interfaz de chat personalizada + +```tsx +import { ChatInput, ChatMessages, ChatUserList } from "@effectify/chat-solid/components" +import { useChatRoom } from "@effectify/chat-solid/hooks/use-chat-room" + +function CustomChatRoom() { + const { messages, users, sendMessage, isConnected } = useChatRoom() + + return ( +
+
+
+ +
+ +
+
+ +
+
+ ) +} +``` + +## Componentes + +### ChatProvider + +Proveedor raíz que gestiona estado y conexión WebSocket: + +```tsx +import { ChatProvider } from "@effectify/chat-solid/components/chat-provider" + +function App() { + return ( + + + + ) +} +``` + +### ChatRoom + +Componente completo de sala de chat: + +```tsx +import { ChatRoom } from "@effectify/chat-solid/components/chat-room" + +function MyChatApp() { + return ( +
+ +
+ ) +} +``` + +### ChatMessages + +Lista de mensajes: + +```tsx +import { ChatMessages } from "@effectify/chat-solid/components/chat-messages" +import { useChatRoom } from "@effectify/chat-solid/hooks/use-chat-room" + +function MessageArea() { + const { messages } = useChatRoom() + return ( + ( +
+ {message.user.name}: {message.content} +
+ )} + /> + ) +} +``` + +### ChatInput + +Entrada para enviar mensajes: + +```tsx +import { ChatInput } from "@effectify/chat-solid/components/chat-input" +import { useChatRoom } from "@effectify/chat-solid/hooks/use-chat-room" + +function MessageInput() { + const { sendMessage, isConnected } = useChatRoom() + return ( + { + // Indicador de escritura + }} + /> + ) +} +``` + +### ChatUserList + +Lista de usuarios: + +```tsx +import { ChatUserList } from "@effectify/chat-solid/components/chat-user-list" +import { useChatRoom } from "@effectify/chat-solid/hooks/use-chat-room" + +function UserSidebar() { + const { users } = useChatRoom() + return ( +
+

Usuarios en línea

+ ( +
+
+ {user.name} +
+ )} + /> +
+ ) +} +``` diff --git a/apps/docs/src/content/docs/es/solid/packages/solid-query.md b/apps/docs/src/content/docs/es/solid/packages/solid-query.md new file mode 100644 index 00000000..79d9e232 --- /dev/null +++ b/apps/docs/src/content/docs/es/solid/packages/solid-query.md @@ -0,0 +1,197 @@ +--- +title: "@effectify/solid-query" +description: Integración de Effect con TanStack Query para aplicaciones SolidJS +--- + +# @effectify/solid-query + +El paquete `@effectify/solid-query` integra Effect con TanStack Query para aplicaciones SolidJS. Permite usar el manejo de errores y la componibilidad de Effect junto con el caché y sincronización de TanStack Query dentro del sistema reactivo de SolidJS. + +## Instalación + +```bash +npm install @effectify/solid-query @tanstack/solid-query effect solid-js +``` + +## Uso básico + +### Obtención simple de datos con createQuery + +```tsx +import { createQuery } from "@tanstack/solid-query" +import { Effect } from "effect" +import { Show } from "solid-js" + +const fetchUser = (id: number) => + Effect.tryPromise({ + try: () => fetch(`/api/users/${id}`).then((res) => res.json()), + catch: (error) => new Error(`Failed to fetch user: ${error}`), + }) + +function UserProfile(props: { userId: number }) { + const userQuery = createQuery(() => ({ + queryKey: ["user", props.userId], + queryFn: () => Effect.runPromise(fetchUser(props.userId)), + })) + + return ( + Cargando...
}> + {(user) => ( +
+

{user().name}

+ +

Error: {userQuery.error?.message}

+
+
+ )} + + ) +} +``` + +### Usando recursos de SolidJS + +```tsx +import { createResource, Show } from "solid-js" +import { Effect } from "effect" + +function UserProfile(props: { userId: number }) { + const [user] = createResource( + () => props.userId, + (id) => Effect.runPromise(fetchUser(id)), + ) + + return ( + Cargando...}> + {(userData) => ( +
+

{userData().name}

+

{userData().email}

+
+ )} +
+ ) +} +``` + +### Con manejo de errores y reintentos + +```tsx +import { Effect, pipe } from "effect" + +const fetchUserWithRetry = (id: number) => + pipe( + fetchUser(id), + Effect.retry({ times: 3, delay: "1 second" }), + Effect.catchAll(() => Effect.succeed({ id, name: "Desconocido", email: "unknown@example.com" })), + ) + +function UserProfile(props: { userId: number }) { + const userQuery = createQuery(() => ({ + queryKey: ["user", props.userId], + queryFn: () => Effect.runPromise(fetchUserWithRetry(props.userId)), + })) + + return ( + + {(user) =>
Hola, {user().name}!
} +
+ ) +} +``` + +## Patrones avanzados + +### Queries reactivas con señales + +```tsx +import { createSignal, For, Show } from "solid-js" +import { createQuery } from "@tanstack/solid-query" + +function UserSearch() { + const [searchTerm, setSearchTerm] = createSignal("") + + const searchQuery = createQuery(() => ({ + queryKey: ["users", "search", searchTerm()], + queryFn: () => Effect.runPromise(searchUsers(searchTerm())), + enabled: searchTerm().length > 2, + })) + + return ( +
+ setSearchTerm(e.currentTarget.value)} + placeholder="Buscar usuarios..." + /> + + {(user) =>
{user.name}
}
+
+
+ ) +} +``` + +### Combinando múltiples Effects + +```tsx +const fetchUserWithPosts = (userId: number) => + Effect.all([fetchUser(userId), fetchUserPosts(userId)]).pipe( + Effect.map(([user, posts]) => ({ user, posts })), + ) + +function UserDashboard(props: { userId: number }) { + const dashboardQuery = createQuery(() => ({ + queryKey: ["userDashboard", props.userId], + queryFn: () => Effect.runPromise(fetchUserWithPosts(props.userId)), + })) + + return ( + Cargando panel...}> + {(data) => ( +
+

{data().user.name}

+
Posts: {data().posts.length}
+
+ )} +
+ ) +} +``` + +### Tipos de errores personalizados + +```tsx +class UserNotFoundError { + readonly _tag = "UserNotFoundError" + constructor(readonly userId: number) {} +} + +class NetworkError { + readonly _tag = "NetworkError" + constructor(readonly message: string) {} +} + +const fetchUserTyped = (id: number) => + Effect.tryPromise({ + try: async () => { + const response = await fetch(`/api/users/${id}`) + if (response.status === 404) throw new UserNotFoundError(id) + if (!response.ok) throw new NetworkError(`HTTP ${response.status}`) + return response.json() + }, + catch: (error) => { + if (error instanceof UserNotFoundError) return error + if (error instanceof NetworkError) return error + return new NetworkError(String(error)) + }, + }) + +function UserProfile(props: { userId: number }) { + const userQuery = createQuery(() => ({ + queryKey: ["user", props.userId], + queryFn: () => Effect.runPromise(fetchUserTyped(props.userId)), + })) +} +``` diff --git a/apps/docs/src/content/docs/es/solid/packages/solid-ui.md b/apps/docs/src/content/docs/es/solid/packages/solid-ui.md new file mode 100644 index 00000000..e15eb8c9 --- /dev/null +++ b/apps/docs/src/content/docs/es/solid/packages/solid-ui.md @@ -0,0 +1,201 @@ +--- +title: "@effectify/solid-ui" +description: Biblioteca de componentes de UI para aplicaciones SolidJS +--- + +# @effectify/solid-ui + +El paquete `@effectify/solid-ui` proporciona un conjunto completo de componentes de UI construidos con SolidJS, Kobalte y Tailwind CSS. Incluye formularios, layouts, componentes interactivos y utilidades diseñadas para funcionar con aplicaciones basadas en Effect y el sistema reactivo de SolidJS. + +## Instalación + +```bash +npm install @effectify/solid-ui +``` + +**Peer Dependencies:** + +```bash +npm install solid-js tailwindcss @kobalte/core +``` + +## Configuración + +### 1. Configura Tailwind CSS + +```js +// tailwind.config.js +module.exports = { + content: [ + "./src/**/*.{js,ts,jsx,tsx}", + "./node_modules/@effectify/solid-ui/**/*.{js,ts,jsx,tsx}", + ], + theme: { extend: {} }, + plugins: [], +} +``` + +### 2. Importa estilos globales + +```tsx +// src/index.tsx +import "@effectify/solid-ui/globals.css" +``` + +## Componentes + +### Componentes de formulario + +#### Button + +```tsx +import { Button } from "@effectify/solid-ui/components/button" + +function MyComponent() { + return ( +
+ + + + + + +
+ ) +} +``` + +#### Input + +```tsx +import { Input } from "@effectify/solid-ui/components/input" +import { Label } from "@effectify/solid-ui/components/label" + +function LoginForm() { + return ( +
+
+ + +
+
+ + +
+
+ ) +} +``` + +#### Form con TanStack Form + +```tsx +import { createForm } from "@tanstack/solid-form" +import { Button } from "@effectify/solid-ui/components/button" +import { Input } from "@effectify/solid-ui/components/input" +import { Label } from "@effectify/solid-ui/components/label" +import { Show } from "solid-js" + +interface LoginData { + email: string + password: string +} + +function LoginForm() { + const form = createForm(() => ({ + defaultValues: { email: "", password: "" } as LoginData, + onSubmit: async ({ value }) => { + console.log("Form data:", value) + }, + })) + + return ( +
{ + e.preventDefault() + e.stopPropagation() + form.handleSubmit() + }} + class="space-y-4" + > + ( +
+ + field().handleChange(e.currentTarget.value)} + type="email" + placeholder="Introduce tu email" + /> + 0}> +

{field().state.meta.errors.join(", ")}

+
+
+ )} + /> + + ( +
+ + field().handleChange(e.currentTarget.value)} + type="password" + placeholder="Introduce tu contraseña" + /> + 0}> +

{field().state.meta.errors.join(", ")}

+
+
+ )} + /> + + + + ) +} +``` + +### Componentes de layout + +#### Card + +```tsx +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@effectify/solid-ui/components/card" +import { Button } from "@effectify/solid-ui/components/button" + +function UserCard(props: { user: User }) { + return ( + + + {props.user.name} + {props.user.email} + + +

Miembro desde {props.user.joinDate}

+
+ + + + +
+ ) +} +``` diff --git a/apps/docs/src/content/docs/es/solid/reference/api.md b/apps/docs/src/content/docs/es/solid/reference/api.md new file mode 100644 index 00000000..396bee84 --- /dev/null +++ b/apps/docs/src/content/docs/es/solid/reference/api.md @@ -0,0 +1,234 @@ +--- +title: Referencia de API de SolidJS +description: Referencia completa de API para los paquetes SolidJS de Effectify +--- + +# Referencia de API de SolidJS + +Esta página proporciona una referencia completa de API para todos los paquetes SolidJS de Effectify. + +## @effectify/solid-query + +### Integración con TanStack Query + +#### `createQuery` con Effect + +```tsx +import { createQuery } from "@tanstack/solid-query" +import { Effect } from "effect" + +const userQuery = createQuery(() => ({ + queryKey: ["user", userId()], + queryFn: () => Effect.runPromise(fetchUserEffect(userId())), +})) +``` + +#### `createMutation` con Effect + +```tsx +import { createMutation } from "@tanstack/solid-query" + +const updateMutation = createMutation(() => ({ + mutationFn: (data: UserData) => Effect.runPromise(updateUserEffect(data)), +})) +``` + +#### `createInfiniteQuery` con Effect + +```tsx +import { createInfiniteQuery } from "@tanstack/solid-query" + +const postsQuery = createInfiniteQuery(() => ({ + queryKey: ["posts"], + queryFn: ({ pageParam = 1 }) => Effect.runPromise(fetchPostsEffect(pageParam)), + getNextPageParam: (lastPage) => lastPage.nextPage, + initialPageParam: 1, +})) +``` + +### Integración con recursos de SolidJS + +#### `createResource` con Effect + +```tsx +import { createResource } from "solid-js" + +const [user] = createResource(() => userId(), (id) => Effect.runPromise(fetchUserEffect(id))) +``` + +## @effectify/solid-ui + +### Componentes + +#### Button + +```tsx +interface ButtonProps extends JSX.ButtonHTMLAttributes { + variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" + size?: "default" | "sm" | "lg" | "icon" + children: JSX.Element +} +function Button(props: ButtonProps): JSX.Element +``` + +#### Input + +```tsx +interface InputProps extends JSX.InputHTMLAttributes { + error?: boolean +} +function Input(props: InputProps): JSX.Element +``` + +#### Componentes Card + +```tsx +interface CardProps extends JSX.HTMLAttributes { + children: JSX.Element +} +function Card(props: CardProps): JSX.Element +function CardHeader(props: CardProps): JSX.Element +function CardTitle(props: JSX.HTMLAttributes): JSX.Element +function CardDescription(props: JSX.HTMLAttributes): JSX.Element +function CardContent(props: CardProps): JSX.Element +function CardFooter(props: CardProps): JSX.Element +``` + +#### Componentes Dialog + +```tsx +interface DialogProps { + children: JSX.Element + open?: boolean + onOpenChange?: (open: boolean) => void +} +function Dialog(props: DialogProps): JSX.Element + +interface DialogTriggerProps { + children: JSX.Element + as?: Component +} +function DialogTrigger(props: DialogTriggerProps): JSX.Element +function DialogContent(props: JSX.HTMLAttributes): JSX.Element +function DialogHeader(props: JSX.HTMLAttributes): JSX.Element +function DialogTitle(props: JSX.HTMLAttributes): JSX.Element +function DialogDescription(props: JSX.HTMLAttributes): JSX.Element +function DialogFooter(props: JSX.HTMLAttributes): JSX.Element +``` + +#### Componentes Drawer + +```tsx +interface DrawerProps { + children: JSX.Element + open?: boolean + onOpenChange?: (open: boolean) => void +} +function Drawer(props: DrawerProps): JSX.Element +function DrawerTrigger(props: DialogTriggerProps): JSX.Element +function DrawerContent(props: JSX.HTMLAttributes): JSX.Element +function DrawerHeader(props: JSX.HTMLAttributes): JSX.Element +function DrawerTitle(props: JSX.HTMLAttributes): JSX.Element +function DrawerDescription(props: JSX.HTMLAttributes): JSX.Element +function DrawerFooter(props: JSX.HTMLAttributes): JSX.Element +``` + +### Utilidades + +#### `cn` - Utilidad de className + +```tsx +function cn(...inputs: ClassValue[]): string +``` + +Uso: + +```tsx +import { cn } from "@effectify/solid-ui/lib/utils" + +function MyComponent(props: { class?: string }) { + return
+} +``` + +#### Funciones de validación + +```tsx +function validateEmail(email: string): string | undefined +function validateRequired(value: any): string | undefined +function validateMinLength(value: string, min: number): string | undefined +function validateMaxLength(value: string, max: number): string | undefined +``` + +## @effectify/chat-solid + +### Componentes + +#### ChatProvider + +```tsx +interface ChatProviderProps { + userId: string + roomId: string + websocketUrl: string + options?: ChatOptions + children: JSX.Element +} +interface ChatOptions { + reconnectAttempts?: number + reconnectDelay?: number + messageHistory?: number + heartbeatInterval?: number + typingTimeout?: number +} +function ChatProvider(props: ChatProviderProps): JSX.Element +``` + +#### ChatRoom + +```tsx +interface ChatRoomProps extends JSX.HTMLAttributes { + showUserList?: boolean + showTypingIndicator?: boolean + messageLimit?: number +} +function ChatRoom(props: ChatRoomProps): JSX.Element +``` + +#### ChatMessages + +```tsx +interface ChatMessagesProps extends JSX.HTMLAttributes { + messages: Message[] + renderMessage?: (message: Message) => JSX.Element + onLoadMore?: () => void + hasMore?: boolean +} +function ChatMessages(props: ChatMessagesProps): JSX.Element +``` + +#### ChatInput + +```tsx +interface ChatInputProps extends JSX.HTMLAttributes { + onSendMessage: (message: { content: string; type: MessageType }) => void + disabled?: boolean + placeholder?: string + maxLength?: number + showEmojiPicker?: boolean + onTyping?: () => void + onStopTyping?: () => void +} +function ChatInput(props: ChatInputProps): JSX.Element +``` + +#### ChatUserList + +```tsx +interface ChatUserListProps extends JSX.HTMLAttributes { + users: User[] + renderUser?: (user: User) => JSX.Element + onUserClick?: (user: User) => void +} +function ChatUserList(props: ChatUserListProps): JSX.Element +``` diff --git a/apps/docs/src/content/docs/es/universal/getting-started.md b/apps/docs/src/content/docs/es/universal/getting-started.md new file mode 100644 index 00000000..cfba301e --- /dev/null +++ b/apps/docs/src/content/docs/es/universal/getting-started.md @@ -0,0 +1,123 @@ +--- +title: Primeros pasos con Paquetes Universales +description: Aprende los conceptos y patrones usados en todos los paquetes +--- + +# Primeros pasos con Paquetes Universales + +Los paquetes universales de Effectify son la base para construir aplicaciones consistentes y seguras por tipos en React, SolidJS y Node.js. Esta guía explica los conceptos y patrones clave. + +## Conceptos clave + +### 1. Diseño centrado en Effect + +- Componibilidad +- Seguridad de tipos +- Testeabilidad +- Fiabilidad + +```typescript +import { Effect } from "effect" + +const greetUser = (name: string) => Effect.succeed(`Hola, ${name}!`) + +const processUser = (userId: string) => + Effect.gen(function*() { + const user = yield* fetchUser(userId) + const greeting = yield* greetUser(user.name) + const result = yield* saveGreeting(greeting) + return result + }) +``` + +### 2. Diseño dirigido por dominio + +```typescript +interface User { + readonly id: UserId + readonly email: Email + readonly name: UserName + readonly createdAt: Date +} + +const UserDomain = { + create: (data: CreateUserData) => Effect, + validate: (user: User) => Effect, + updateProfile: (user: User, updates: UserUpdates) => Effect, +} + +class UserCreatedEvent { + readonly _tag = "UserCreatedEvent" + constructor(readonly user: User, readonly timestamp: Date) {} +} +``` + +### 3. Inversión de dependencias + +```typescript +export interface UserRepository { + readonly save: (user: User) => Effect + readonly findById: (id: UserId) => Effect + readonly findByEmail: (email: Email) => Effect +} +``` + +## Patrones de tipos + +### Tipos con marca + +```typescript +export type UserId = string & { readonly _brand: "UserId" } +export type Email = string & { readonly _brand: "Email" } +export type UserName = string & { readonly _brand: "UserName" } +``` + +Constructores: + +```typescript +export const UserId = { + make: (value: string): Effect => + value.length > 0 && value.length <= 50 + ? Effect.succeed(value as UserId) + : Effect.fail(new ValidationError("Invalid user ID", { field: "userId" })), +} + +export const Email = { + make: (value: string): Effect => + /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) + ? Effect.succeed(value as Email) + : Effect.fail(new ValidationError("Invalid email format", { field: "email" })), +} +``` + +### Validación con Schema + +```typescript +import { Schema } from "@effect/schema" + +export const UserSchema = Schema.struct({ + id: Schema.string, + email: Schema.string.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)), + name: Schema.string.pipe(Schema.minLength(1), Schema.maxLength(100)), + createdAt: Schema.Date, +}) +``` + +## Manejo de errores + +```typescript +import { Data } from "effect" + +export class DomainError + extends Data.TaggedError("DomainError")<{ readonly message: string; readonly cause?: unknown }> +{} +export class ValidationError + extends Data.TaggedError("ValidationError")<{ readonly field: string; readonly message: string }> +{} +export class BusinessRuleViolationError + extends Data.TaggedError("BusinessRuleViolationError")<{ readonly rule: string; readonly message: string }> +{} +export class ResourceNotFoundError + extends Data.TaggedError("ResourceNotFoundError")<{ readonly resource: string; readonly id: string }> +{} +``` diff --git a/apps/docs/src/content/docs/es/universal/index.mdx b/apps/docs/src/content/docs/es/universal/index.mdx new file mode 100644 index 00000000..135108a8 --- /dev/null +++ b/apps/docs/src/content/docs/es/universal/index.mdx @@ -0,0 +1,119 @@ +--- +title: Paquetes Universales +description: Paquetes y conceptos compartidos que funcionan en todas las plataformas +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +# Paquetes Universales + +Los paquetes universales de Effectify proporcionan funcionalidad compartida, tipos y lógica de dominio que funcionan en React, SolidJS y Node.js. Encarnan los principios de Effect y habilitan comportamiento consistente en todo tu stack. + +## Paquetes disponibles + + + + Lógica de dominio y reglas de negocio para chat. Operaciones puras y agnósticas al framework construidas con Effect. + + [Más información →](packages/chat-domain/) + + + + Tipos e interfaces comunes de TypeScript compartidos por todos los paquetes. Asegura consistencia de tipos en tu aplicación. + + [Más información →](packages/shared-types/) + + + +## Primeros pasos + + + Aprende los conceptos y patrones usados en todos los paquetes de Effectify. + + [Empezar →](getting-started/) + + +## Principios clave + +### 1. Agnóstico al framework + +```typescript +import { ChatDomain } from '@effectify/chat-domain' +import { Effect } from 'effect' + +const sendMessage = (roomId: string, content: string, userId: string) => + Effect.gen(function* () { + const message = yield* ChatDomain.createMessage({ roomId, content, userId, timestamp: new Date() }) + const validation = yield* ChatDomain.validateMessage(message) + return validation + }) +``` + +### 2. Diseño centrado en Effect + +- Componible +- Seguro por tipos +- Testeable +- Confiable + +### 3. Diseño dirigido por dominio + +```typescript +interface User { + readonly id: UserId + readonly email: Email + readonly name: UserName + readonly createdAt: Date +} + +const UserDomain = { + create: (data: CreateUserData) => Effect, + validate: (user: User) => Effect, + updateProfile: (user: User, updates: UserUpdates) => Effect +} + +class UserCreatedEvent { + readonly _tag = 'UserCreatedEvent' + constructor(readonly user: User, readonly timestamp: Date) {} +} +``` + +## Patrones de arquitectura + +### Arquitectura por capas + +``` +┌─────────────────────────────────────┐ +│ Capa de Presentación │ +│ (React, SolidJS, Express) │ +├─────────────────────────────────────┤ +│ Capa de Aplicación │ +│ (Casos de uso, Servicios) │ +├─────────────────────────────────────┤ +│ Capa de Dominio │ +│ (Paquetes Universales) │ +├─────────────────────────────────────┤ +│ Capa de Infraestructura │ +│ (DB, APIs externas) │ +└─────────────────────────────────────┘ +``` + +### Inversión de dependencias + +```typescript +export interface MessageRepository { + readonly save: (message: Message) => Effect + readonly findById: (id: MessageId) => Effect + readonly findByRoom: (roomId: RoomId) => Effect +} +``` + +## Seguridad de tipos + +### Tipos con marca + +```typescript +export type UserId = string & { readonly _brand: 'UserId' } +export type RoomId = string & { readonly _brand: 'RoomId' } +export type MessageId = string & { readonly _brand: 'MessageId' } +``` diff --git a/apps/docs/src/content/docs/es/universal/packages/chat-domain.md b/apps/docs/src/content/docs/es/universal/packages/chat-domain.md new file mode 100644 index 00000000..029cb6f0 --- /dev/null +++ b/apps/docs/src/content/docs/es/universal/packages/chat-domain.md @@ -0,0 +1,142 @@ +--- +title: "@effectify/chat-domain" +description: Lógica de dominio y reglas de negocio para funcionalidad de chat +--- + +# @effectify/chat-domain + +El paquete `@effectify/chat-domain` proporciona lógica de dominio de chat agnóstica al framework construida con Effect. Contiene las reglas de negocio, entidades y servicios que impulsan la funcionalidad de chat en React, SolidJS y Node.js. + +## Instalación + +```bash +npm install @effectify/chat-domain effect +``` + +## Entidades principales + +### Message + +```typescript +import { Message, MessageId, RoomId, UserId } from "@effectify/chat-domain" + +interface Message { + readonly id: MessageId + readonly content: string + readonly type: MessageType + readonly userId: UserId + readonly roomId: RoomId + readonly timestamp: Date + readonly edited?: boolean + readonly editedAt?: Date + readonly reactions: Reaction[] + readonly replyTo?: MessageId +} + +type MessageType = "text" | "image" | "file" | "system" + +interface Reaction { + readonly emoji: string + readonly userId: UserId + readonly timestamp: Date +} +``` + +### ChatRoom + +```typescript +interface ChatRoom { + readonly id: RoomId + readonly name: RoomName + readonly description?: string + readonly type: RoomType + readonly participants: Set + readonly settings: RoomSettings + readonly createdBy: UserId + readonly createdAt: Date + readonly updatedAt: Date +} + +type RoomType = "public" | "private" | "direct" + +interface RoomSettings { + readonly maxParticipants: number + readonly allowFileUploads: boolean + readonly allowReactions: boolean + readonly messageRetentionDays: number +} +``` + +### User + +```typescript +interface User { + readonly id: UserId + readonly name: UserName + readonly avatar?: string + readonly status: UserStatus + readonly lastSeen: Date +} + +type UserStatus = "online" | "away" | "busy" | "offline" +``` + +## Servicios de dominio + +### MessageDomain + +```typescript +import { MessageDomain } from "@effectify/chat-domain" + +const MessageDomain = { + create: (data: CreateMessageData) => + Effect.gen(function*() { + const messageId = yield* MessageId.generate() + const content = yield* validateMessageContent(data.content) + return { + id: messageId, + content, + type: data.type || "text", + userId: data.userId, + roomId: data.roomId, + timestamp: new Date(), + reactions: [], + edited: false, + } + }), + + edit: (message: Message, newContent: string, userId: UserId) => + Effect.gen(function*() { + if (message.userId !== userId) { + yield* Effect.fail(new UnauthorizedError({ message: "Only message author can edit" })) + } + const validContent = yield* validateMessageContent(newContent) + return { ...message, content: validContent, edited: true, editedAt: new Date() } + }), + + addReaction: (message: Message, emoji: string, userId: UserId) => + Effect.gen(function*() { + const exists = message.reactions.find((r) => r.emoji === emoji && r.userId === userId) + if (exists) { + yield* Effect.fail( + new BusinessRuleViolationError({ + rule: "unique-reaction", + message: "User already reacted with this emoji", + }), + ) + } + const reaction = { emoji, userId, timestamp: new Date() } + return { ...message, reactions: [...message.reactions, reaction] } + }), + + removeReaction: (message: Message, emoji: string, userId: UserId) => + Effect.gen(function*() { + const idx = message.reactions.findIndex((r) => r.emoji === emoji && r.userId === userId) + if (idx === -1) { + yield* Effect.fail(new ResourceNotFoundError({ resource: "Reaction", id: `${emoji}-${userId}` })) + } + const updated = message.reactions.filter((_, i) => i !== idx) + return { ...message, reactions: updated } + }), +} +``` diff --git a/apps/docs/src/content/docs/es/universal/packages/shared-types.md b/apps/docs/src/content/docs/es/universal/packages/shared-types.md new file mode 100644 index 00000000..5e339cea --- /dev/null +++ b/apps/docs/src/content/docs/es/universal/packages/shared-types.md @@ -0,0 +1,111 @@ +--- +title: "@effectify/shared-types" +description: Tipos e interfaces comunes de TypeScript compartidos por todos los paquetes +--- + +# @effectify/shared-types + +El paquete `@effectify/shared-types` proporciona tipos, interfaces y utilidades comunes de TypeScript compartidos por todos los paquetes de Effectify. Asegura consistencia de tipos y proporciona fundamentos para construir aplicaciones seguras por tipos. + +## Instalación + +```bash +npm install @effectify/shared-types +``` + +## Tipos principales + +### Tipos con marca + +```typescript +import { Brand } from "@effectify/shared-types" + +export type UserId = Brand +export type Email = Brand +export type Timestamp = Brand +``` + +### Tipos Result + +```typescript +import { Failure, Result, Success } from "@effectify/shared-types" + +type Result = Success | Failure + +interface Success { + readonly _tag: "Success" + readonly value: T +} +interface Failure { + readonly _tag: "Failure" + readonly error: E +} + +export const success = (value: T): Success => ({ _tag: "Success", value }) +export const failure = (error: E): Failure => ({ _tag: "Failure", error }) +``` + +### Tipos Option + +```typescript +import { None, Option, Some } from "@effectify/shared-types" + +type Option = Some | None +interface Some { + readonly _tag: "Some" + readonly value: T +} +interface None { + readonly _tag: "None" +} + +export const some = (value: T): Some => ({ _tag: "Some", value }) +export const none: None = { _tag: "None" } +export const fromNullable = (value: T | null | undefined): Option => value != null ? some(value) : none +``` + +## Tipos de entidad + +### Entidad base + +```typescript +import { BaseEntity, EntityId } from "@effectify/shared-types" + +interface BaseEntity { + readonly id: EntityId + readonly createdAt: Date + readonly updatedAt: Date + readonly version: number +} + +type EntityId = Brand`> +``` + +## Tipos de Aggregate Root + +```typescript +import { AggregateRoot, DomainEvent } from "@effectify/shared-types" + +interface AggregateRoot extends BaseEntity { + readonly events: ReadonlyArray +} + +interface DomainEvent { + readonly eventId: string + readonly eventType: string + readonly aggregateId: string + readonly occurredAt: Date + readonly version: number +} +``` + +## Tipos de Value Object + +```typescript +import { ValueObject } from "@effectify/shared-types" + +interface ValueObject { + readonly equals: (other: ValueObject) => boolean + readonly toString: () => string +} +``` diff --git a/packages/prisma/project.json b/packages/prisma/project.json index 89bd3e10..533a0476 100644 --- a/packages/prisma/project.json +++ b/packages/prisma/project.json @@ -1,6 +1,6 @@ { "name": "@effectify/prisma", - "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/prisma/src", "projectType": "library", "targets": {