Updated some interfaces to be more future proof

This commit is contained in:
Arunavo Ray
2025-07-16 22:47:33 +05:30
parent beedbaf9a4
commit 948250deef
15 changed files with 10156 additions and 19 deletions

View File

@@ -1,21 +1,40 @@
# Docker Registry Configuration # Gitea Mirror Configuration
DOCKER_REGISTRY=ghcr.io # Copy this to .env and update with your values
DOCKER_IMAGE=arunavo4/gitea-mirror
DOCKER_TAG=latest # ===========================================
# CORE CONFIGURATION
# ===========================================
# Application Configuration # Application Configuration
NODE_ENV=production NODE_ENV=production
HOST=0.0.0.0 HOST=0.0.0.0
PORT=4321 PORT=4321
# Database Configuration
# For self-hosted, SQLite is used by default
DATABASE_URL=sqlite://data/gitea-mirror.db DATABASE_URL=sqlite://data/gitea-mirror.db
# Security # Security
# Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=change-this-to-a-secure-random-string-in-production BETTER_AUTH_SECRET=change-this-to-a-secure-random-string-in-production
BETTER_AUTH_URL=http://localhost:4321 BETTER_AUTH_URL=http://localhost:4321
# ENCRYPTION_SECRET=optional-encryption-key-for-token-encryption # Generate with: openssl rand -base64 48 # ENCRYPTION_SECRET=optional-encryption-key-for-token-encryption # Generate with: openssl rand -base64 48
# Optional GitHub/Gitea Mirror Configuration (for docker-compose, can also be set via web UI) # ===========================================
# Uncomment and set as needed. These are passed as environment variables to the container. # DOCKER CONFIGURATION (Optional)
# ===========================================
# Docker Registry Configuration
DOCKER_REGISTRY=ghcr.io
DOCKER_IMAGE=arunavo4/gitea-mirror
DOCKER_TAG=latest
# ===========================================
# MIRROR CONFIGURATION (Optional)
# Can also be configured via web UI
# ===========================================
# GitHub Configuration
# GITHUB_USERNAME=your-github-username # GITHUB_USERNAME=your-github-username
# GITHUB_TOKEN=your-github-personal-access-token # GITHUB_TOKEN=your-github-personal-access-token
# SKIP_FORKS=false # SKIP_FORKS=false
@@ -27,6 +46,8 @@ BETTER_AUTH_URL=http://localhost:4321
# PRESERVE_ORG_STRUCTURE=false # PRESERVE_ORG_STRUCTURE=false
# ONLY_MIRROR_ORGS=false # ONLY_MIRROR_ORGS=false
# SKIP_STARRED_ISSUES=false # SKIP_STARRED_ISSUES=false
# Gitea Configuration
# GITEA_URL=http://gitea:3000 # GITEA_URL=http://gitea:3000
# GITEA_TOKEN=your-local-gitea-token # GITEA_TOKEN=your-local-gitea-token
# GITEA_USERNAME=your-local-gitea-username # GITEA_USERNAME=your-local-gitea-username
@@ -34,15 +55,14 @@ BETTER_AUTH_URL=http://localhost:4321
# GITEA_ORG_VISIBILITY=public # GITEA_ORG_VISIBILITY=public
# DELAY=3600 # DELAY=3600
# Optional Database Cleanup Configuration (configured via web UI) # ===========================================
# These environment variables are optional and only used as defaults # OPTIONAL FEATURES
# Users can configure cleanup settings through the web interface # ===========================================
# Database Cleanup Configuration
# CLEANUP_ENABLED=false # CLEANUP_ENABLED=false
# CLEANUP_RETENTION_DAYS=7 # CLEANUP_RETENTION_DAYS=7
# Optional TLS/SSL Configuration # TLS/SSL Configuration
# Option 1: Mount custom CA certificates in ./certs directory as .crt files # GITEA_SKIP_TLS_VERIFY=false # WARNING: Only use for testing
# The container will automatically combine them into a CA bundle
# Option 2: Mount your system CA bundle at /etc/ssl/certs/ca-certificates.crt
# See docker-compose.yml for volume mount examples
# GITEA_SKIP_TLS_VERIFY=false # WARNING: Only use for testing, disables TLS verification

2
.gitignore vendored
View File

@@ -32,5 +32,3 @@ certs/*.pem
certs/*.cer certs/*.cer
!certs/README.md !certs/README.md
# Hosted version documentation (local only)
docs/HOSTED_VERSION.md

View File

@@ -0,0 +1,91 @@
# GitHub Sponsors Integration
This guide shows how GitHub Sponsors is integrated into the open-source version of Gitea Mirror.
## Components
### GitHubSponsors Card
A card component that displays in the sidebar or dashboard:
```tsx
import { GitHubSponsors } from '@/components/sponsors/GitHubSponsors';
// In your layout or dashboard
<GitHubSponsors />
```
### SponsorButton
A smaller button for headers or navigation:
```tsx
import { SponsorButton } from '@/components/sponsors/GitHubSponsors';
// In your header
<SponsorButton />
```
## Integration Points
### 1. Dashboard Sidebar
Add the sponsor card to the dashboard sidebar for visibility:
```tsx
// src/components/layout/DashboardLayout.tsx
<aside>
{/* Other sidebar content */}
<GitHubSponsors />
</aside>
```
### 2. Header Navigation
Add the sponsor button to the main navigation:
```tsx
// src/components/layout/Header.tsx
<nav>
{/* Other nav items */}
<SponsorButton />
</nav>
```
### 3. Settings Page
Add a support section in settings:
```tsx
// src/components/settings/SupportSection.tsx
<Card>
<CardHeader>
<CardTitle>Support Development</CardTitle>
</CardHeader>
<CardContent>
<GitHubSponsors />
</CardContent>
</Card>
```
## Behavior
- **Only appears in self-hosted mode**: The components automatically hide in hosted mode
- **Non-intrusive**: Designed to be helpful without being annoying
- **Multiple options**: GitHub Sponsors, Buy Me a Coffee, and starring the repo
## Customization
You can customize the sponsor components by:
1. Updating the GitHub Sponsors URL
2. Adding/removing donation platforms
3. Changing the styling to match your theme
4. Adjusting the placement based on user feedback
## Best Practices
1. **Don't be pushy**: Show sponsor options tastefully
2. **Provide value first**: Ensure the tool is useful before asking for support
3. **Be transparent**: Explain how sponsorships help the project
4. **Thank sponsors**: Acknowledge supporters in README or releases

9087
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Heart, Coffee, Sparkles } from "lucide-react";
import { isSelfHostedMode } from "@/lib/deployment-mode";
export function SponsorCard() {
// Only show in self-hosted mode
if (!isSelfHostedMode()) {
return null;
}
return (
<div className="mt-auto p-4 border-t">
<Card className="bg-gradient-to-r from-purple-500/10 to-pink-500/10 border-purple-500/20">
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium flex items-center gap-2">
<Heart className="w-4 h-4 text-pink-500" />
Support Development
</CardTitle>
<CardDescription className="text-xs">
Help us improve Gitea Mirror
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-xs text-muted-foreground">
Gitea Mirror is open source and free. Your sponsorship helps us maintain and improve it.
</p>
<div className="space-y-2">
<Button
className="w-full h-8 text-xs"
size="sm"
asChild
>
<a
href="https://github.com/sponsors/RayLabsHQ"
target="_blank"
rel="noopener noreferrer"
>
<Heart className="w-3 h-3 mr-2" />
Sponsor on GitHub
</a>
</Button>
<Button
className="w-full h-8 text-xs"
size="sm"
variant="outline"
asChild
>
<a
href="https://buymeacoffee.com/raylabs"
target="_blank"
rel="noopener noreferrer"
>
<Coffee className="w-3 h-3 mr-2" />
Buy us a coffee
</a>
</Button>
</div>
<div className="pt-2 border-t">
<p className="text-xs text-muted-foreground flex items-center gap-1">
<Sparkles className="w-3 h-3" />
Pro features available in hosted version
</p>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,105 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Heart, Coffee, Zap } from "lucide-react";
import { isSelfHostedMode } from "@/lib/deployment-mode";
export function GitHubSponsors() {
// Only show in self-hosted mode
if (!isSelfHostedMode()) {
return null;
}
return (
<Card className="bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-950/20 dark:to-pink-950/20 border-purple-200 dark:border-purple-800">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-purple-900 dark:text-purple-100">
<Heart className="w-5 h-5 text-pink-500" />
Support Gitea Mirror
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-purple-800 dark:text-purple-200">
Gitea Mirror is open source and free to use. If you find it helpful,
consider supporting the project!
</p>
<div className="space-y-2">
<Button
variant="default"
className="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"
asChild
>
<a
href="https://github.com/sponsors/RayLabsHQ"
target="_blank"
rel="noopener noreferrer"
>
<Heart className="w-4 h-4 mr-2" />
Become a Sponsor
</a>
</Button>
<div className="grid grid-cols-2 gap-2">
<Button
variant="outline"
size="sm"
className="border-purple-300 hover:bg-purple-100 dark:border-purple-700 dark:hover:bg-purple-900"
asChild
>
<a
href="https://github.com/RayLabsHQ/gitea-mirror"
target="_blank"
rel="noopener noreferrer"
>
Star on GitHub
</a>
</Button>
<Button
variant="outline"
size="sm"
className="border-purple-300 hover:bg-purple-100 dark:border-purple-700 dark:hover:bg-purple-900"
asChild
>
<a
href="https://buymeacoffee.com/raylabs"
target="_blank"
rel="noopener noreferrer"
>
<Coffee className="w-4 h-4 mr-1" />
Buy Coffee
</a>
</Button>
</div>
</div>
<div className="text-xs text-purple-600 dark:text-purple-300 space-y-1">
<p className="flex items-center gap-1">
<Zap className="w-3 h-3" />
Your support helps maintain and improve the project
</p>
</div>
</CardContent>
</Card>
);
}
// Smaller inline sponsor button for headers/navbars
export function SponsorButton() {
if (!isSelfHostedMode()) {
return null;
}
return (
<Button variant="outline" size="sm" asChild>
<a
href="https://github.com/sponsors/RayLabsHQ"
target="_blank"
rel="noopener noreferrer"
>
<Heart className="w-4 h-4 mr-2" />
Sponsor
</a>
</Button>
);
}

View File

@@ -1,7 +1,6 @@
import { betterAuth } from "better-auth"; import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { oidcProvider } from "better-auth/plugins"; import { oidcProvider, sso } from "better-auth/plugins";
import { sso } from "better-auth/plugins/sso";
import { db, users } from "./db"; import { db, users } from "./db";
import * as schema from "./db/schema"; import * as schema from "./db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";

102
src/lib/db/adapter.ts Normal file
View File

@@ -0,0 +1,102 @@
/**
* Database adapter for SQLite
* For the self-hosted version of Gitea Mirror
*/
import { drizzle as drizzleSqlite } from 'drizzle-orm/bun-sqlite';
import { Database } from 'bun:sqlite';
import * as schema from './schema';
export type DatabaseClient = ReturnType<typeof createDatabase>;
/**
* Create SQLite database connection
*/
export function createDatabase() {
const dbPath = process.env.DATABASE_PATH || './data/gitea-mirror.db';
// Ensure directory exists
const fs = require('fs');
const path = require('path');
const dir = path.dirname(dbPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create SQLite connection
const sqlite = new Database(dbPath);
// Enable foreign keys and WAL mode for better performance
sqlite.exec('PRAGMA foreign_keys = ON');
sqlite.exec('PRAGMA journal_mode = WAL');
sqlite.exec('PRAGMA synchronous = NORMAL');
sqlite.exec('PRAGMA cache_size = -2000'); // 2MB cache
sqlite.exec('PRAGMA temp_store = MEMORY');
// Create Drizzle instance with SQLite
const db = drizzleSqlite(sqlite, {
schema,
logger: process.env.NODE_ENV === 'development',
});
return {
db,
client: sqlite,
type: 'sqlite' as const,
// Helper methods
async close() {
sqlite.close();
},
async healthCheck() {
try {
sqlite.query('SELECT 1').get();
return true;
} catch {
return false;
}
},
async transaction<T>(fn: (tx: any) => Promise<T>) {
return db.transaction(fn);
},
};
}
// Create singleton instance
let dbInstance: DatabaseClient | null = null;
/**
* Get database instance (singleton)
*/
export function getDatabase(): DatabaseClient {
if (!dbInstance) {
dbInstance = createDatabase();
}
return dbInstance;
}
/**
* Close database connection
*/
export async function closeDatabase() {
if (dbInstance) {
await dbInstance.close();
dbInstance = null;
}
}
// Export convenience references
export const { db, client, type: dbType } = getDatabase();
// Re-export schema for convenience
export * from './schema';
/**
* Database migration utilities
*/
export async function runMigrations() {
const { migrate } = await import('drizzle-orm/bun-sqlite/migrator');
await migrate(db, { migrationsFolder: './drizzle' });
}

View File

@@ -0,0 +1,21 @@
/**
* Deployment mode utilities
* For the open source self-hosted version
*/
export const DEPLOYMENT_MODE = 'selfhosted';
export const isSelfHostedMode = () => true;
/**
* Feature flags for self-hosted version
*/
export const features = {
// Core features available
githubSync: true,
giteaMirroring: true,
scheduling: true,
multiUser: true,
githubSponsors: true,
unlimitedRepos: true,
};

256
src/lib/events/realtime.ts Normal file
View File

@@ -0,0 +1,256 @@
/**
* Real-time event system using EventEmitter
* For the self-hosted version
*/
import { EventEmitter } from 'events';
export interface RealtimeEvent {
type: string;
userId?: string;
data: any;
timestamp: number;
}
/**
* Real-time event bus for local instance
*/
export class RealtimeEventBus extends EventEmitter {
private channels = new Map<string, Set<(event: RealtimeEvent) => void>>();
private userChannels = new Map<string, string[]>();
constructor() {
super();
}
/**
* Handle incoming events
*/
private handleIncomingEvent(channel: string, event: RealtimeEvent) {
// Emit to local listeners
this.emit(channel, event);
// Call channel-specific handlers
const handlers = this.channels.get(channel);
if (handlers) {
handlers.forEach(handler => {
try {
handler(event);
} catch (error) {
console.error('Error in event handler:', error);
}
});
}
}
/**
* Subscribe to a channel
*/
async subscribe(channel: string, handler?: (event: RealtimeEvent) => void) {
// Add handler if provided
if (handler) {
if (!this.channels.has(channel)) {
this.channels.set(channel, new Set());
}
this.channels.get(channel)!.add(handler);
}
// Add local listener
if (!this.listenerCount(channel)) {
this.on(channel, (event) => this.handleIncomingEvent(channel, event));
}
}
/**
* Subscribe to user-specific channels
*/
async subscribeUser(userId: string) {
const channels = [
`user:${userId}`,
`user:${userId}:notifications`,
`user:${userId}:updates`,
];
this.userChannels.set(userId, channels);
for (const channel of channels) {
await this.subscribe(channel);
}
}
/**
* Unsubscribe from a channel
*/
async unsubscribe(channel: string, handler?: (event: RealtimeEvent) => void) {
// Remove handler if provided
if (handler) {
this.channels.get(channel)?.delete(handler);
// Remove channel if no handlers left
if (this.channels.get(channel)?.size === 0) {
this.channels.delete(channel);
}
}
// Remove local listener if no handlers
if (!this.channels.has(channel)) {
this.removeAllListeners(channel);
}
}
/**
* Unsubscribe from user channels
*/
async unsubscribeUser(userId: string) {
const channels = this.userChannels.get(userId) || [];
for (const channel of channels) {
await this.unsubscribe(channel);
}
this.userChannels.delete(userId);
}
/**
* Publish an event
*/
async publish(channel: string, event: Omit<RealtimeEvent, 'timestamp'>) {
const fullEvent: RealtimeEvent = {
...event,
timestamp: Date.now(),
};
// Emit locally
this.handleIncomingEvent(channel, fullEvent);
}
/**
* Broadcast to all users
*/
async broadcast(event: Omit<RealtimeEvent, 'timestamp'>) {
await this.publish('broadcast', event);
}
/**
* Send event to specific user
*/
async sendToUser(userId: string, event: Omit<RealtimeEvent, 'timestamp' | 'userId'>) {
await this.publish(`user:${userId}`, {
...event,
userId,
});
}
/**
* Send activity update
*/
async sendActivity(activity: {
userId: string;
action: string;
resource: string;
resourceId: string;
details?: any;
}) {
const event = {
type: 'activity',
data: activity,
};
// Send to user
await this.sendToUser(activity.userId, event);
// Also publish to activity channel
await this.publish('activity', {
...event,
userId: activity.userId,
});
}
/**
* Get event statistics
*/
getStats() {
return {
channels: this.channels.size,
listeners: Array.from(this.channels.values()).reduce(
(sum, handlers) => sum + handlers.size,
0
),
userChannels: this.userChannels.size,
};
}
}
// Global event bus instance
export const eventBus = new RealtimeEventBus();
/**
* React hook for subscribing to events
*/
export function useRealtimeEvents(
channel: string,
handler: (event: RealtimeEvent) => void,
deps: any[] = []
) {
if (typeof window !== 'undefined') {
const { useEffect } = require('react');
useEffect(() => {
eventBus.subscribe(channel, handler);
return () => {
eventBus.unsubscribe(channel, handler);
};
}, deps);
}
}
/**
* Server-sent events endpoint handler
*/
export async function createSSEHandler(userId: string) {
const encoder = new TextEncoder();
// Create a readable stream for SSE
const stream = new ReadableStream({
async start(controller) {
// Send initial connection event
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ type: 'connected' })}\n\n`)
);
// Subscribe to user channels
await eventBus.subscribeUser(userId);
// Create event handler
const handleEvent = (event: RealtimeEvent) => {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(event)}\n\n`)
);
};
// Subscribe to channels
eventBus.on(`user:${userId}`, handleEvent);
// Keep connection alive with heartbeat
const heartbeat = setInterval(() => {
controller.enqueue(encoder.encode(': heartbeat\n\n'));
}, 30000);
// Cleanup on close
return () => {
clearInterval(heartbeat);
eventBus.off(`user:${userId}`, handleEvent);
eventBus.unsubscribeUser(userId);
};
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}

184
src/lib/modules/registry.ts Normal file
View File

@@ -0,0 +1,184 @@
/**
* Module registry implementation
* Manages loading and access to modular features
*/
import type {
Module,
ModuleRegistry,
AppContext,
RouteHandler,
Middleware,
DatabaseAdapter,
EventEmitter
} from './types';
// Module registry for extensibility
/**
* Simple event emitter implementation
*/
class SimpleEventEmitter implements EventEmitter {
private events: Map<string, Set<Function>> = new Map();
on(event: string, handler: (...args: any[]) => void): void {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event)!.add(handler);
}
off(event: string, handler: (...args: any[]) => void): void {
this.events.get(event)?.delete(handler);
}
emit(event: string, ...args: any[]): void {
this.events.get(event)?.forEach(handler => {
try {
handler(...args);
} catch (error) {
console.error(`Error in event handler for ${event}:`, error);
}
});
}
}
/**
* Module manager class
*/
export class ModuleManager {
private modules: Map<string, Module> = new Map();
private routes: Map<string, RouteHandler> = new Map();
private middlewares: Middleware[] = [];
private events = new SimpleEventEmitter();
private initialized = false;
/**
* Get app context for modules
*/
private getAppContext(): AppContext {
return {
addRoute: (path, handler) => this.addRoute(path, handler),
addMiddleware: (middleware) => this.middlewares.push(middleware),
db: this.getDatabaseAdapter(),
events: this.events,
modules: this.getRegistry(),
};
}
/**
* Get database adapter based on deployment mode
*/
private getDatabaseAdapter(): DatabaseAdapter {
// This would be implemented to use SQLite or PostgreSQL
// based on deployment mode
return {
query: async (sql, params) => [],
execute: async (sql, params) => {},
transaction: async (fn) => fn(),
};
}
/**
* Register a module
*/
async register(module: Module): Promise<void> {
if (this.modules.has(module.name)) {
console.warn(`Module ${module.name} is already registered`);
return;
}
try {
await module.init(this.getAppContext());
this.modules.set(module.name, module);
console.log(`Module ${module.name} registered successfully`);
} catch (error) {
console.error(`Failed to register module ${module.name}:`, error);
throw error;
}
}
/**
* Unregister a module
*/
async unregister(moduleName: string): Promise<void> {
const module = this.modules.get(moduleName);
if (!module) return;
if (module.cleanup) {
await module.cleanup();
}
this.modules.delete(moduleName);
// Remove routes registered by this module
// This would need to track which module registered which routes
}
/**
* Add a route handler
*/
private addRoute(path: string, handler: RouteHandler): void {
this.routes.set(path, handler);
}
/**
* Get route handler for a path
*/
getRouteHandler(path: string): RouteHandler | null {
return this.routes.get(path) || null;
}
/**
* Get all middleware
*/
getMiddleware(): Middleware[] {
return [...this.middlewares];
}
/**
* Get module registry
*/
getRegistry(): ModuleRegistry {
const registry: ModuleRegistry = {};
// Copy all modules to registry
for (const [name, module] of this.modules) {
registry[name] = module;
}
return registry;
}
/**
* Get a specific module
*/
get<K extends keyof ModuleRegistry>(name: K): ModuleRegistry[K] | null {
return this.getRegistry()[name] || null;
}
/**
* Check if a module is loaded
*/
has(name: string): boolean {
return this.modules.has(name);
}
/**
* Emit an event to all modules
*/
emit(event: string, ...args: any[]): void {
this.events.emit(event, ...args);
}
}
// Global module manager instance
export const modules = new ModuleManager();
// Initialize modules on app start
export async function initializeModules() {
// Load core modules here if any
// Emit initialization complete event
modules.emit('modules:initialized');
}

86
src/lib/modules/types.d.ts vendored Normal file
View File

@@ -0,0 +1,86 @@
/**
* Module system type definitions
* These interfaces allow for extensibility and plugins
*/
import type { APIContext } from 'astro';
import type { ComponentType, LazyExoticComponent } from 'react';
/**
* Base module interface that all modules must implement
*/
export interface Module {
/** Unique module identifier */
name: string;
/** Module version */
version: string;
/** Initialize the module with app context */
init(app: AppContext): Promise<void>;
/** Cleanup when module is unloaded */
cleanup?(): Promise<void>;
}
/**
* Application context passed to modules
*/
export interface AppContext {
/** Register API routes */
addRoute(path: string, handler: RouteHandler): void;
/** Register middleware */
addMiddleware(middleware: Middleware): void;
/** Access to database (abstracted) */
db: DatabaseAdapter;
/** Event emitter for cross-module communication */
events: EventEmitter;
/** Access to other modules */
modules: ModuleRegistry;
}
/**
* Route handler type
*/
export type RouteHandler = (context: APIContext) => Promise<Response> | Response;
/**
* Middleware type
*/
export type Middleware = (context: APIContext, next: () => Promise<Response>) => Promise<Response>;
/**
* Database adapter interface (abstract away implementation)
*/
export interface DatabaseAdapter {
query<T>(sql: string, params?: any[]): Promise<T[]>;
execute(sql: string, params?: any[]): Promise<void>;
transaction<T>(fn: () => Promise<T>): Promise<T>;
}
/**
* Event emitter for cross-module communication
*/
export interface EventEmitter {
on(event: string, handler: (...args: any[]) => void): void;
off(event: string, handler: (...args: any[]) => void): void;
emit(event: string, ...args: any[]): void;
}
/**
* Example module interfaces
* These are examples of how modules can be structured
*/
export interface FeatureModule extends Module {
/** React components provided by the module */
components?: Record<string, LazyExoticComponent<ComponentType<any>>>;
/** API methods provided by the module */
api?: Record<string, (...args: any[]) => Promise<any>>;
/** Lifecycle hooks */
hooks?: {
onInit?: () => Promise<void>;
onUserAction?: (action: string, data: any) => Promise<void>;
};
}
/**
* Module registry interface
*/
export interface ModuleRegistry {
[key: string]: Module | undefined;
}
export interface User {
id: string;
email: string;
name?: string;
username?: string;
}
//# sourceMappingURL=types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}

5
src/lib/modules/types.js Normal file
View File

@@ -0,0 +1,5 @@
/**
* Module system type definitions
* These interfaces allow for extensibility and plugins
*/
export {};

110
src/lib/modules/types.ts Normal file
View File

@@ -0,0 +1,110 @@
/**
* Module system type definitions
* These interfaces allow for extensibility and plugins
*/
import type { APIContext } from 'astro';
import type { ComponentType, LazyExoticComponent } from 'react';
/**
* Base module interface that all modules must implement
*/
export interface Module {
/** Unique module identifier */
name: string;
/** Module version */
version: string;
/** Initialize the module with app context */
init(app: AppContext): Promise<void>;
/** Cleanup when module is unloaded */
cleanup?(): Promise<void>;
}
/**
* Application context passed to modules
*/
export interface AppContext {
/** Register API routes */
addRoute(path: string, handler: RouteHandler): void;
/** Register middleware */
addMiddleware(middleware: Middleware): void;
/** Access to database (abstracted) */
db: DatabaseAdapter;
/** Event emitter for cross-module communication */
events: EventEmitter;
/** Access to other modules */
modules: ModuleRegistry;
}
/**
* Route handler type
*/
export type RouteHandler = (context: APIContext) => Promise<Response> | Response;
/**
* Middleware type
*/
export type Middleware = (
context: APIContext,
next: () => Promise<Response>
) => Promise<Response>;
/**
* Database adapter interface (abstract away implementation)
*/
export interface DatabaseAdapter {
query<T>(sql: string, params?: any[]): Promise<T[]>;
execute(sql: string, params?: any[]): Promise<void>;
transaction<T>(fn: () => Promise<T>): Promise<T>;
}
/**
* Event emitter for cross-module communication
*/
export interface EventEmitter {
on(event: string, handler: (...args: any[]) => void): void;
off(event: string, handler: (...args: any[]) => void): void;
emit(event: string, ...args: any[]): void;
}
/**
* Example module interfaces
* These are examples of how modules can be structured
*/
// Example: Feature module with components
export interface FeatureModule extends Module {
/** React components provided by the module */
components?: Record<string, LazyExoticComponent<ComponentType<any>>>;
/** API methods provided by the module */
api?: Record<string, (...args: any[]) => Promise<any>>;
/** Lifecycle hooks */
hooks?: {
onInit?: () => Promise<void>;
onUserAction?: (action: string, data: any) => Promise<void>;
};
}
/**
* Module registry interface
*/
export interface ModuleRegistry {
[key: string]: Module | undefined;
}
// Generic types that modules might use
export interface User {
id: string;
email: string;
name?: string;
username?: string;
}