oneie
Version:
Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.
1,122 lines (968 loc) • 32.3 kB
Markdown
---
title: Service Providers
dimension: things
category: service-providers.md
tags: ai
related_dimensions: events, knowledge, people
scope: global
created: 2025-11-03
updated: 2025-11-03
version: 1.0.0
ai_context: |
This document is part of the things dimension in the service-providers.md category.
Location: one/things/service-providers.md
Purpose: Videos: ${content.videos.
Related dimensions: events, knowledge, people
For AI agents: Read this to understand service providers.
---
// ============================================================================
// convex/services/providers/openai.ts
// OpenAI Provider - LLM operations, embeddings, chat completions
// ============================================================================
import { Effect, Context, Layer } from "effect";
import OpenAI from "openai";
// Error Types
export class OpenAIError {
readonly \_tag = "OpenAIError";
constructor(readonly message: string, readonly code?: string) {}
}
export class RateLimitError {
readonly \_tag = "RateLimitError";
constructor(readonly retryAfter?: number) {}
}
export class InvalidResponseError {
readonly \_tag = "InvalidResponseError";
constructor(readonly response: any) {}
}
// Service Interface
export class OpenAIProvider extends Context.Tag("OpenAIProvider")<
OpenAIProvider,
{
readonly chat: (params: {
systemPrompt: string;
messages: Array<{ role: string; content: string }>;
context?: string;
model?: string;
temperature?: number;
maxTokens?: number;
}) => Effect.Effect<
{
content: string;
usage: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
},
OpenAIError | RateLimitError >;
readonly embed: (
text: string,
model?: string
) => Effect.Effect<number[], OpenAIError | RateLimitError>;
readonly generateImage: (
prompt: string,
options?: { size?: string; quality?: string }
) => Effect.Effect<{ url: string }, OpenAIError | RateLimitError>;
readonly analyzePersonality: (content: {
videos: string[];
posts: string[];
interactions: string[];
}) => Effect.Effect<
{
systemPrompt: string;
traits: string[];
style: string;
values: string[];
},
OpenAIError
>;
}
> () {}
// Implementation
export const OpenAIProviderLive = Layer.effect(
OpenAIProvider,
Effect.gen(function* () {
const apiKey = yield* Effect.sync(() => process.env.OPENAI_API_KEY);
if (!apiKey) {
return yield* Effect.fail(
new OpenAIError("OPENAI_API_KEY environment variable not set")
);
}
const client = new OpenAI({ apiKey });
return OpenAIProvider.of({
chat: (params) =>
Effect.gen(function* () {
const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: params.systemPrompt },
];
if (params.context) {
messages.push({
role: "system",
content: `Context:\n${params.context}`,
});
}
messages.push(
...params.messages.map(
(m): OpenAI.Chat.ChatCompletionMessageParam => ({
role: m.role as "user" | "assistant",
content: m.content,
})
)
);
const response = yield* Effect.tryPromise({
try: () =>
client.chat.completions.create({
model: params.model || "gpt-4-turbo",
messages,
temperature: params.temperature ?? 0.7,
max_tokens: params.maxTokens,
}),
catch: (error: any) => {
if (error.status === 429) {
return new RateLimitError(error.headers?.["retry-after"]);
}
return new OpenAIError(error.message, error.code);
},
});
const choice = response.choices[0];
if (!choice?.message?.content) {
return yield* Effect.fail(
new InvalidResponseError(response)
);
}
return {
content: choice.message.content,
usage: {
promptTokens: response.usage?.prompt_tokens || 0,
completionTokens: response.usage?.completion_tokens || 0,
totalTokens: response.usage?.total_tokens || 0,
},
};
}).pipe(
Effect.retry({ times: 3, delay: "1 second" }),
Effect.timeout("60 seconds")
),
embed: (text, model = "text-embedding-3-small") =>
Effect.gen(function* () {
const response = yield* Effect.tryPromise({
try: () =>
client.embeddings.create({
model,
input: text,
dimensions: 1536,
}),
catch: (error: any) => {
if (error.status === 429) {
return new RateLimitError(error.headers?.["retry-after"]);
}
return new OpenAIError(error.message, error.code);
},
});
return response.data[0].embedding;
}).pipe(Effect.retry({ times: 3, delay: "1 second" })),
generateImage: (prompt, options) =>
Effect.gen(function* () {
const response = yield* Effect.tryPromise({
try: () =>
client.images.generate({
model: "dall-e-3",
prompt,
size: (options?.size as any) || "1024x1024",
quality: (options?.quality as any) || "standard",
n: 1,
}),
catch: (error: any) =>
new OpenAIError(error.message, error.code),
});
const url = response.data[0]?.url;
if (!url) {
return yield* Effect.fail(
new InvalidResponseError(response)
);
}
return { url };
}),
analyzePersonality: (content) =>
Effect.gen(function* () {
const prompt = `Analyze this creator's personality and communication style to create an AI clone system prompt.
Videos: ${content.videos.join("\n")}
Posts: ${content.posts.join("\n")}
Interactions: ${content.interactions.join("\n")}
Provide a JSON response with:
{
"systemPrompt": "A detailed system prompt for the AI clone",
"traits": ["trait1", "trait2", ...],
"style": "communication style description",
"values": ["value1", "value2", ...]
}`;
const response = yield* Effect.gen(function* () {
return yield* this.chat({
systemPrompt:
"You are a personality analysis expert. Respond with valid JSON only.",
messages: [{ role: "user", content: prompt }],
temperature: 0.3,
});
});
const parsed = yield* Effect.try({
try: () => JSON.parse(response.content),
catch: () => new InvalidResponseError(response.content),
});
return parsed;
}),
});
})
);
// Mock for Testing
export const OpenAIProviderTest = Layer.succeed(
OpenAIProvider,
OpenAIProvider.of({
chat: () =>
Effect.succeed({
content: "Mock AI response",
usage: { promptTokens: 10, completionTokens: 20, totalTokens: 30 },
}),
embed: () => Effect.succeed(Array(1536).fill(0.1)),
generateImage: () => Effect.succeed({ url: "https://mock-image.jpg" }),
analyzePersonality: () =>
Effect.succeed({
systemPrompt: "Mock system prompt",
traits: ["friendly", "knowledgeable"],
style: "casual and helpful",
values: ["authenticity", "growth"],
}),
})
);
// ============================================================================
// convex/services/providers/elevenlabs.ts
// ElevenLabs Provider - Voice cloning and text-to-speech
// ============================================================================
import { Effect, Context, Layer } from "effect";
// Error Types
export class ElevenLabsError {
readonly \_tag = "ElevenLabsError";
constructor(readonly message: string) {}
}
export class VoiceCloneFailedError {
readonly \_tag = "VoiceCloneFailedError";
constructor(readonly reason: string) {}
}
export class InsufficientSamplesError {
readonly \_tag = "InsufficientSamplesError";
constructor(readonly provided: number, readonly required: number) {}
}
// Service Interface
export class ElevenLabsProvider extends Context.Tag("ElevenLabsProvider")<
ElevenLabsProvider,
{
readonly cloneVoice: (params: {
name: string;
samples: string[]; // Storage IDs or URLs
description?: string;
}) => Effect.Effect<
{ voiceId: string },
ElevenLabsError | VoiceCloneFailedError | InsufficientSamplesError >;
readonly textToSpeech: (params: {
voiceId: string;
text: string;
model?: string;
}) => Effect.Effect<{ audioUrl: string }, ElevenLabsError>;
readonly listVoices: () => Effect.Effect<
Array<{ id: string; name: string }>,
ElevenLabsError
>;
readonly deleteVoice: (
voiceId: string
) => Effect.Effect<void, ElevenLabsError>;
}
> () {}
// Implementation
export const ElevenLabsProviderLive = Layer.effect(
ElevenLabsProvider,
Effect.gen(function* () {
const apiKey = yield* Effect.sync(() => process.env.ELEVENLABS_API_KEY);
if (!apiKey) {
return yield* Effect.fail(
new ElevenLabsError("ELEVENLABS_API_KEY not set")
);
}
const baseUrl = "https://api.elevenlabs.io/v1";
return ElevenLabsProvider.of({
cloneVoice: (params) =>
Effect.gen(function* () {
// Validate sample count
if (params.samples.length < 5) {
return yield* Effect.fail(
new InsufficientSamplesError(params.samples.length, 5)
);
}
// Create FormData with samples
const formData = new FormData();
formData.append("name", params.name);
if (params.description) {
formData.append("description", params.description);
}
// Add audio samples
for (const sample of params.samples) {
// Fetch audio file from storage/URL
const audioBlob = yield* Effect.tryPromise({
try: async () => {
const response = await fetch(sample);
return await response.blob();
},
catch: (error: any) =>
new ElevenLabsError(`Failed to fetch sample: ${error.message}`),
});
formData.append("files", audioBlob, `sample-${Date.now()}.mp3`);
}
// API call to create voice
const response = yield* Effect.tryPromise({
try: async () => {
const res = await fetch(`${baseUrl}/voices/add`, {
method: "POST",
headers: { "xi-api-key": apiKey },
body: formData,
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.detail?.message || "Voice clone failed");
}
return await res.json();
},
catch: (error: any) =>
new VoiceCloneFailedError(error.message),
});
return { voiceId: response.voice_id };
}).pipe(
Effect.retry({ times: 2, delay: "5 seconds" }),
Effect.timeout("5 minutes")
),
textToSpeech: (params) =>
Effect.gen(function* () {
const response = yield* Effect.tryPromise({
try: async () => {
const res = await fetch(
`${baseUrl}/text-to-speech/${params.voiceId}`,
{
method: "POST",
headers: {
"xi-api-key": apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: params.text,
model_id: params.model || "eleven_multilingual_v2",
voice_settings: {
stability: 0.5,
similarity_boost: 0.75,
},
}),
}
);
if (!res.ok) {
throw new Error("Text-to-speech failed");
}
return await res.blob();
},
catch: (error: any) => new ElevenLabsError(error.message),
});
// Upload to storage and return URL
const audioUrl = yield* Effect.succeed(
URL.createObjectURL(response)
);
return { audioUrl };
}),
listVoices: () =>
Effect.gen(function* () {
const response = yield* Effect.tryPromise({
try: async () => {
const res = await fetch(`${baseUrl}/voices`, {
headers: { "xi-api-key": apiKey },
});
if (!res.ok) {
throw new Error("Failed to list voices");
}
return await res.json();
},
catch: (error: any) => new ElevenLabsError(error.message),
});
return response.voices.map((v: any) => ({
id: v.voice_id,
name: v.name,
}));
}),
deleteVoice: (voiceId) =>
Effect.gen(function* () {
yield* Effect.tryPromise({
try: async () => {
const res = await fetch(`${baseUrl}/voices/${voiceId}`, {
method: "DELETE",
headers: { "xi-api-key": apiKey },
});
if (!res.ok) {
throw new Error("Failed to delete voice");
}
},
catch: (error: any) => new ElevenLabsError(error.message),
});
}),
});
})
);
// Mock for Testing
export const ElevenLabsProviderTest = Layer.succeed(
ElevenLabsProvider,
ElevenLabsProvider.of({
cloneVoice: () => Effect.succeed({ voiceId: "mock-voice-id" }),
textToSpeech: () =>
Effect.succeed({ audioUrl: "https://mock-audio.mp3" }),
listVoices: () =>
Effect.succeed([
{ id: "voice-1", name: "Mock Voice 1" },
{ id: "voice-2", name: "Mock Voice 2" },
]),
deleteVoice: () => Effect.succeed(undefined),
})
);
// ============================================================================
// convex/services/providers/stripe.ts
// Stripe Provider - Payment processing
// ============================================================================
import { Effect, Context, Layer } from "effect";
import Stripe from "stripe";
// Error Types
export class StripeError {
readonly \_tag = "StripeError";
constructor(
readonly message: string,
readonly code?: string,
readonly declineCode?: string
) {}
}
export class PaymentFailedError {
readonly \_tag = "PaymentFailedError";
constructor(readonly reason: string) {}
}
// Service Interface
export class StripeProvider extends Context.Tag("StripeProvider")<
StripeProvider,
{
readonly charge: (params: {
amount: number; // in cents
currency: string;
metadata?: Record<string, string>;
}) => Effect.Effect<
{ id: string; amount: number; status: string },
StripeError | PaymentFailedError >;
readonly createCheckoutSession: (params: {
amount: number;
currency: string;
successUrl: string;
cancelUrl: string;
metadata?: Record<string, string>;
}) => Effect.Effect<
{ sessionId: string; url: string },
StripeError
>;
readonly refund: (
paymentIntentId: string,
amount?: number
) => Effect.Effect<{ refundId: string }, StripeError>;
readonly createConnectAccount: (params: {
email: string;
country: string;
}) => Effect.Effect<{ accountId: string }, StripeError>;
readonly getPaymentStatus: (
paymentIntentId: string
) => Effect.Effect<
{ status: string; amount: number },
StripeError
>;
}
> () {}
// Implementation
export const StripeProviderLive = Layer.effect(
StripeProvider,
Effect.gen(function* () {
const apiKey = yield* Effect.sync(() => process.env.STRIPE_SECRET_KEY);
if (!apiKey) {
return yield* Effect.fail(
new StripeError("STRIPE_SECRET_KEY not set")
);
}
const stripe = new Stripe(apiKey, {
apiVersion: "2024-11-20.acacia",
});
return StripeProvider.of({
charge: (params) =>
Effect.gen(function* () {
const paymentIntent = yield* Effect.tryPromise({
try: () =>
stripe.paymentIntents.create({
amount: params.amount,
currency: params.currency,
automatic_payment_methods: { enabled: true },
metadata: params.metadata,
}),
catch: (error: any) => {
if (error.type === "card_error") {
return new PaymentFailedError(
error.message || "Card declined"
);
}
return new StripeError(error.message, error.code);
},
});
return {
id: paymentIntent.id,
amount: paymentIntent.amount,
status: paymentIntent.status,
};
}),
createCheckoutSession: (params) =>
Effect.gen(function* () {
const session = yield* Effect.tryPromise({
try: () =>
stripe.checkout.sessions.create({
line_items: [
{
price_data: {
currency: params.currency,
unit_amount: params.amount,
product_data: {
name: "Token Purchase",
},
},
quantity: 1,
},
],
mode: "payment",
success_url: params.successUrl,
cancel_url: params.cancelUrl,
metadata: params.metadata,
}),
catch: (error: any) =>
new StripeError(error.message, error.code),
});
return {
sessionId: session.id,
url: session.url!,
};
}),
refund: (paymentIntentId, amount) =>
Effect.gen(function* () {
const refund = yield* Effect.tryPromise({
try: () =>
stripe.refunds.create({
payment_intent: paymentIntentId,
amount,
}),
catch: (error: any) =>
new StripeError(error.message, error.code),
});
return { refundId: refund.id };
}),
createConnectAccount: (params) =>
Effect.gen(function* () {
const account = yield* Effect.tryPromise({
try: () =>
stripe.accounts.create({
type: "express",
email: params.email,
country: params.country,
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
}),
catch: (error: any) =>
new StripeError(error.message, error.code),
});
return { accountId: account.id };
}),
getPaymentStatus: (paymentIntentId) =>
Effect.gen(function* () {
const paymentIntent = yield* Effect.tryPromise({
try: () => stripe.paymentIntents.retrieve(paymentIntentId),
catch: (error: any) =>
new StripeError(error.message, error.code),
});
return {
status: paymentIntent.status,
amount: paymentIntent.amount,
};
}),
});
})
);
// Mock for Testing
export const StripeProviderTest = Layer.succeed(
StripeProvider,
StripeProvider.of({
charge: () =>
Effect.succeed({
id: "pi_mock123",
amount: 1000,
status: "succeeded",
}),
createCheckoutSession: () =>
Effect.succeed({
sessionId: "cs_mock123",
url: "https://checkout.stripe.com/mock",
}),
refund: () => Effect.succeed({ refundId: "re_mock123" }),
createConnectAccount: () =>
Effect.succeed({ accountId: "acct_mock123" }),
getPaymentStatus: () =>
Effect.succeed({ status: "succeeded", amount: 1000 }),
})
);
// ============================================================================
// convex/services/providers/blockchain.ts
// Blockchain Provider - Base L2 token operations
// ============================================================================
import { Effect, Context, Layer } from "effect";
import { createPublicClient, createWalletClient, http } from "viem";
import { base } from "viem/chains";
// Error Types
export class BlockchainError {
readonly \_tag = "BlockchainError";
constructor(readonly message: string) {}
}
export class TransactionFailedError {
readonly \_tag = "TransactionFailedError";
constructor(readonly txHash?: string, readonly reason?: string) {}
}
// Service Interface
export class BlockchainProvider extends Context.Tag("BlockchainProvider")<
BlockchainProvider,
{
readonly deployToken: (params: {
name: string;
symbol: string;
totalSupply: number;
}) => Effect.Effect<
{ contractAddress: string; txHash: string },
BlockchainError | TransactionFailedError >;
readonly mint: (params: {
contractAddress: string;
toAddress: string;
amount: number;
}) => Effect.Effect<
{ transactionHash: string },
BlockchainError | TransactionFailedError
>;
readonly burn: (params: {
contractAddress: string;
amount: number;
}) => Effect.Effect<
{ transactionHash: string },
BlockchainError | TransactionFailedError
>;
readonly transfer: (params: {
contractAddress: string;
fromAddress: string;
toAddress: string;
amount: number;
}) => Effect.Effect<{ transactionHash: string }, BlockchainError>;
readonly getBalance: (params: {
contractAddress: string;
address: string;
}) => Effect.Effect<number, BlockchainError>;
}
> () {}
// Implementation
export const BlockchainProviderLive = Layer.effect(
BlockchainProvider,
Effect.gen(function* () {
const privateKey = yield* Effect.sync(
() => process.env.WALLET_PRIVATE_KEY as `0x${string}`
);
if (!privateKey) {
return yield* Effect.fail(
new BlockchainError("WALLET_PRIVATE_KEY not set")
);
}
const publicClient = createPublicClient({
chain: base,
transport: http(),
});
const walletClient = createWalletClient({
chain: base,
transport: http(),
});
return BlockchainProvider.of({
deployToken: (params) =>
Effect.gen(function* () {
// Deploy ERC20 contract
const txHash = yield* Effect.tryPromise({
try: async () => {
// Contract deployment logic here
// This is simplified - real implementation would use contract ABIs
return "0xmock-deploy-hash";
},
catch: (error: any) =>
new TransactionFailedError(undefined, error.message),
});
// Wait for confirmation
yield* Effect.sleep("30 seconds");
return {
contractAddress: "0xmock-contract-address",
txHash,
};
}).pipe(
Effect.retry({ times: 2, delay: "10 seconds" }),
Effect.timeout("5 minutes")
),
mint: (params) =>
Effect.gen(function* () {
const txHash = yield* Effect.tryPromise({
try: async () => {
// Mint tokens on contract
// Simplified - real implementation would call contract
return "0xmock-mint-hash";
},
catch: (error: any) =>
new TransactionFailedError(undefined, error.message),
});
return { transactionHash: txHash };
}),
burn: (params) =>
Effect.gen(function* () {
const txHash = yield* Effect.tryPromise({
try: async () => {
// Burn tokens
return "0xmock-burn-hash";
},
catch: (error: any) =>
new TransactionFailedError(undefined, error.message),
});
return { transactionHash: txHash };
}),
transfer: (params) =>
Effect.gen(function* () {
const txHash = yield* Effect.tryPromise({
try: async () => {
// Transfer tokens
return "0xmock-transfer-hash";
},
catch: (error: any) => new BlockchainError(error.message),
});
return { transactionHash: txHash };
}),
getBalance: (params) =>
Effect.gen(function* () {
const balance = yield* Effect.tryPromise({
try: async () => {
// Get balance from contract
return 1000; // Mock balance
},
catch: (error: any) => new BlockchainError(error.message),
});
return balance;
}),
});
})
);
// Mock for Testing
export const BlockchainProviderTest = Layer.succeed(
BlockchainProvider,
BlockchainProvider.of({
deployToken: () =>
Effect.succeed({
contractAddress: "0xmock-contract",
txHash: "0xmock-deploy",
}),
mint: () => Effect.succeed({ transactionHash: "0xmock-mint" }),
burn: () => Effect.succeed({ transactionHash: "0xmock-burn" }),
transfer: () => Effect.succeed({ transactionHash: "0xmock-transfer" }),
getBalance: () => Effect.succeed(1000),
})
);
// ============================================================================
// convex/services/providers/resend.ts
// Resend Provider - Transactional emails
// ============================================================================
import { Effect, Context, Layer } from "effect";
import { Resend } from "resend";
// Error Types
export class ResendError {
readonly \_tag = "ResendError";
constructor(readonly message: string) {}
}
export class EmailSendFailedError {
readonly \_tag = "EmailSendFailedError";
constructor(readonly reason: string) {}
}
// Service Interface
export class ResendProvider extends Context.Tag("ResendProvider")<
ResendProvider,
{
readonly sendVerificationEmail: (params: {
to: string;
verificationUrl: string;
}) => Effect.Effect<{ messageId: string }, ResendError | EmailSendFailedError>;
readonly sendPasswordReset: (params: {
to: string;
resetUrl: string;
}) => Effect.Effect<{ messageId: string }, ResendError | EmailSendFailedError>;
readonly sendNotification: (params: {
to: string;
subject: string;
body: string;
}) => Effect.Effect<{ messageId: string }, ResendError | EmailSendFailedError>;
readonly sendInsightsReport: (params: {
to: string;
insights: any;
}) => Effect.Effect<{ messageId: string }, ResendError | EmailSendFailedError>;
}
> () {}
// Implementation
export const ResendProviderLive = Layer.effect(
ResendProvider,
Effect.gen(function* () {
const apiKey = yield* Effect.sync(() => process.env.RESEND_API_KEY);
if (!apiKey) {
return yield* Effect.fail(new ResendError("RESEND_API_KEY not set"));
}
const resend = new Resend(apiKey);
const fromEmail = process.env.RESEND_FROM_EMAIL || "noreply@one.ie";
return ResendProvider.of({
sendVerificationEmail: (params) =>
Effect.gen(function* () {
const { data, error } = yield* Effect.tryPromise({
try: () =>
resend.emails.send({
from: fromEmail,
to: params.to,
subject: "Verify your email",
html: `
<h1>Welcome to ONE Platform!</h1>
<p>Click the link below to verify your email:</p>
<a href="${params.verificationUrl}">Verify Email</a>
<p>This link expires in 24 hours.</p>
`,
}),
catch: (error: any) =>
new EmailSendFailedError(error.message),
});
if (error) {
return yield* Effect.fail(
new EmailSendFailedError(error.message)
);
}
return { messageId: data!.id };
}),
sendPasswordReset: (params) =>
Effect.gen(function* () {
const { data, error } = yield* Effect.tryPromise({
try: () =>
resend.emails.send({
from: fromEmail,
to: params.to,
subject: "Reset your password",
html: `
<h1>Password Reset Request</h1>
<p>Click the link below to reset your password:</p>
<a href="${params.resetUrl}">Reset Password</a>
<p>This link expires in 1 hour.</p>
<p>If you didn't request this, please ignore this email.</p>
`,
}),
catch: (error: any) =>
new EmailSendFailedError(error.message),
});
if (error) {
return yield* Effect.fail(
new EmailSendFailedError(error.message)
);
}
return { messageId: data!.id };
}),
sendNotification: (params) =>
Effect.gen(function* () {
const { data, error } = yield* Effect.tryPromise({
try: () =>
resend.emails.send({
from: fromEmail,
to: params.to,
subject: params.subject,
html: params.body,
}),
catch: (error: any) =>
new EmailSendFailedError(error.message),
});
if (error) {
return yield* Effect.fail(
new EmailSendFailedError(error.message)
);
}
return { messageId: data!.id };
}),
sendInsightsReport: (params) =>
Effect.gen(function* () {
const { data, error } = yield* Effect.tryPromise({
try: () =>
resend.emails.send({
from: fromEmail,
to: params.to,
subject: "Your Business Insights Report",
html: `
<h1>Your Business Insights</h1>
<p>Here's your personalized analysis:</p>
<pre>${JSON.stringify(params.insights, null, 2)}</pre>
`,
}),
catch: (error: any) =>
new EmailSendFailedError(error.message),
});
if (error) {
return yield* Effect.fail(
new EmailSendFailedError(error.message)
);
}
return { messageId: data!.id };
}),
});
})
);
// Mock for Testing
export const ResendProviderTest = Layer.succeed(
ResendProvider,
ResendProvider.of({
sendVerificationEmail: () =>
Effect.succeed({ messageId: "mock-verify-123" }),
sendPasswordReset: () => Effect.succeed({ messageId: "mock-reset-123" }),
sendNotification: () => Effect.succeed({ messageId: "mock-notif-123" }),
sendInsightsReport: () =>
Effect.succeed({ messageId: "mock-insights-123" }),
})
);
// ============================================================================
// convex/services/index.ts
// Main Layer - Combines all providers
// ============================================================================
import { Layer } from "effect";
import { OpenAIProviderLive } from "./providers/openai";
import { ElevenLabsProviderLive } from "./providers/elevenlabs";
import { StripeProviderLive } from "./providers/stripe";
import { BlockchainProviderLive } from "./providers/blockchain";
import { ResendProviderLive } from "./providers/resend";
import { ConvexDatabaseLive } from "./core/database";
// Production Layer - All real providers
export const MainLayer = Layer.mergeAll(
OpenAIProviderLive,
ElevenLabsProviderLive,
StripeProviderLive,
BlockchainProviderLive,
ResendProviderLive,
ConvexDatabaseLive
);
// Test Layer - All mock providers
export const TestLayer = Layer.mergeAll(
OpenAIProviderTest,
ElevenLabsProviderTest,
StripeProviderTest,
BlockchainProviderTest,
ResendProviderTest,
ConvexDatabaseTest
);