diff --git a/dbal/development/src/core/client.ts b/dbal/development/src/core/client.ts new file mode 100644 index 000000000..789cabfc1 --- /dev/null +++ b/dbal/development/src/core/client.ts @@ -0,0 +1,8 @@ +import type { DBALConfig } from '../runtime/config' +import { DBALClient } from './client/client' +export { buildAdapter, buildEntityOperations } from './client/builders' +export { normalizeClientConfig, validateClientConfig } from './client/mappers' + +export const createDBALClient = (config: DBALConfig) => new DBALClient(config) + +export { DBALClient } diff --git a/dbal/development/src/core/client/builders.ts b/dbal/development/src/core/client/builders.ts new file mode 100644 index 000000000..56fcf095f --- /dev/null +++ b/dbal/development/src/core/client/builders.ts @@ -0,0 +1,24 @@ +import type { DBALAdapter } from '../../adapters/adapter' +import type { DBALConfig } from '../../runtime/config' +import { createAdapter } from './adapter-factory' +import { + createComponentOperations, + createLuaScriptOperations, + createPackageOperations, + createPageOperations, + createSessionOperations, + createUserOperations, + createWorkflowOperations +} from '../entities' + +export const buildAdapter = (config: DBALConfig): DBALAdapter => createAdapter(config) + +export const buildEntityOperations = (adapter: DBALAdapter) => ({ + users: createUserOperations(adapter), + pages: createPageOperations(adapter), + components: createComponentOperations(adapter), + workflows: createWorkflowOperations(adapter), + luaScripts: createLuaScriptOperations(adapter), + packages: createPackageOperations(adapter), + sessions: createSessionOperations(adapter) +}) diff --git a/dbal/development/src/core/client/client.ts b/dbal/development/src/core/client/client.ts index b57eb6ea9..6c9a98691 100644 --- a/dbal/development/src/core/client/client.ts +++ b/dbal/development/src/core/client/client.ts @@ -1,7 +1,7 @@ /** * @file client.ts * @description DBAL Client - Main interface for database operations - * + * * Provides CRUD operations for all entities through modular operation handlers. * Each entity type has its own dedicated operations module following the * single-responsibility pattern. @@ -9,82 +9,67 @@ import type { DBALConfig } from '../../runtime/config' import type { DBALAdapter } from '../../adapters/adapter' -import { createAdapter } from './adapter-factory' -import { - createUserOperations, - createPageOperations, - createComponentOperations, - createWorkflowOperations, - createLuaScriptOperations, - createPackageOperations, - createSessionOperations, -} from '../entities' +import { buildAdapter, buildEntityOperations } from './builders' +import { normalizeClientConfig, validateClientConfig } from './mappers' export class DBALClient { private adapter: DBALAdapter private config: DBALConfig + private operations: ReturnType constructor(config: DBALConfig) { - this.config = config - - // Validate configuration - if (!config.adapter) { - throw new Error('Adapter type must be specified') - } - if (config.mode !== 'production' && !config.database?.url) { - throw new Error('Database URL must be specified for non-production mode') - } - - this.adapter = createAdapter(config) + this.config = normalizeClientConfig(validateClientConfig(config)) + this.adapter = buildAdapter(this.config) + this.operations = buildEntityOperations(this.adapter) } /** * User entity operations */ get users() { - return createUserOperations(this.adapter) + return this.operations.users } /** * Page entity operations */ get pages() { - return createPageOperations(this.adapter) + return this.operations.pages } /** * Component hierarchy entity operations */ get components() { - return createComponentOperations(this.adapter) + return this.operations.components } /** * Workflow entity operations */ get workflows() { - return createWorkflowOperations(this.adapter) + return this.operations.workflows } /** * Lua script entity operations */ get luaScripts() { - return createLuaScriptOperations(this.adapter) + return this.operations.luaScripts } /** * Package entity operations */ get packages() { - return createPackageOperations(this.adapter) + return this.operations.packages } /** * Session entity operations */ get sessions() { - return createSessionOperations(this.adapter) + return this.operations.sessions } /** diff --git a/dbal/development/src/core/client/mappers.ts b/dbal/development/src/core/client/mappers.ts new file mode 100644 index 000000000..b9abc9661 --- /dev/null +++ b/dbal/development/src/core/client/mappers.ts @@ -0,0 +1,25 @@ +import type { DBALConfig } from '../../runtime/config' +import { DBALError } from '../foundation/errors' + +export const validateClientConfig = (config: DBALConfig): DBALConfig => { + if (!config.adapter) { + throw DBALError.validationError('Adapter type must be specified', []) + } + + if (config.mode !== 'production' && !config.database?.url) { + throw DBALError.validationError('Database URL must be specified for non-production mode', []) + } + + return config +} + +export const normalizeClientConfig = (config: DBALConfig): DBALConfig => ({ + ...config, + security: { + sandbox: config.security?.sandbox ?? 'strict', + enableAuditLog: config.security?.enableAuditLog ?? true + }, + performance: { + ...config.performance + } +}) diff --git a/dbal/development/src/index.ts b/dbal/development/src/index.ts index 7acf658e0..e98734f17 100644 --- a/dbal/development/src/index.ts +++ b/dbal/development/src/index.ts @@ -1,4 +1,4 @@ -export { DBALClient } from './core/client/client' +export { DBALClient, createDBALClient } from './core/client' export type { DBALConfig } from './runtime/config' export type * from './core/foundation/types' export { DBALError, DBALErrorCode } from './core/foundation/errors' diff --git a/frontends/nextjs/src/components/managers/package/PackageDetailsDialog.tsx b/frontends/nextjs/src/components/managers/package/PackageDetailsDialog.tsx new file mode 100644 index 000000000..14b63509e --- /dev/null +++ b/frontends/nextjs/src/components/managers/package/PackageDetailsDialog.tsx @@ -0,0 +1,185 @@ +import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, ScrollArea, Separator, Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui' +import type { PackageCatalogData } from '@/lib/packages/core/package-catalog' +import type { InstalledPackage } from '@/lib/package-types' +import { Download, Star, Tag, Trash, User } from '@phosphor-icons/react' +import { DependenciesTab } from './tabs/DependenciesTab' +import { ScriptsTab } from './tabs/ScriptsTab' + +interface PackageDetailsDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + selectedPackage: PackageCatalogData | null + installing: boolean + onInstall: (packageId: string) => void + onUninstall: (packageId: string) => void + installedPackages: InstalledPackage[] + getCatalogEntry: (packageId: string) => PackageCatalogData | undefined +} + +export function PackageDetailsDialog({ + open, + onOpenChange, + selectedPackage, + installing, + onInstall, + onUninstall, + installedPackages, + getCatalogEntry, +}: PackageDetailsDialogProps) { + if (!selectedPackage) return null + + const { manifest, content } = selectedPackage + + return ( + + + +
+
+ {manifest.icon} +
+
+ {manifest.name} + {manifest.description} +
+ {manifest.category} +
+ + {manifest.downloadCount.toLocaleString()} +
+
+ + {manifest.rating} +
+
+
+
+
+ + + + +
+ + Overview + Dependencies + Scripts + +
+ + + +
+
+
+

Author

+
+ + {manifest.author} +
+
+
+

Version

+

{manifest.version}

+
+
+ +
+

Tags

+
+ {manifest.tags.map(tag => ( + + + {tag} + + ))} +
+
+ +
+

Includes

+
+
+
Data Models
+
{content.schemas.length}
+
+
+
Pages
+
{content.pages.length}
+
+
+
Workflows
+
{content.workflows.length}
+
+
+
Scripts
+
{content.luaScripts.length}
+
+
+
+ + {content.schemas.length > 0 && ( +
+

Data Models

+
+ {content.schemas.map(schema => ( +
+
{schema.displayName || schema.name}
+
{schema.fields.length} fields
+
+ ))} +
+
+ )} + + {content.pages.length > 0 && ( +
+

Pages

+
+ {content.pages.map(page => ( +
+
{page.title}
+
{page.path}
+
+ ))} +
+
+ )} +
+
+
+ + + + + + + + + + + + +
+ + + {manifest.installed ? ( + + ) : ( + + )} + +
+
+ ) +} diff --git a/frontends/nextjs/src/components/managers/package/PackageManager.tsx b/frontends/nextjs/src/components/managers/package/PackageManager.tsx index 5bdeda96e..ea5300946 100644 --- a/frontends/nextjs/src/components/managers/package/PackageManager.tsx +++ b/frontends/nextjs/src/components/managers/package/PackageManager.tsx @@ -1,12 +1,12 @@ import { useState } from 'react' -import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, ScrollArea, Separator } from '@/components/ui' -import { toast } from 'sonner' -import { installPackage, togglePackageEnabled, uninstallPackage } from '@/lib/api/packages' +import { Button } from '@/components/ui' import type { PackageCatalogData } from '@/lib/packages/core/package-catalog' -import { ArrowSquareIn, Download, Export, Package, Star, Tag, Trash, User } from '@phosphor-icons/react' +import { ArrowSquareIn, Export, Package } from '@phosphor-icons/react' +import { PackageDetailsDialog } from './PackageDetailsDialog' import { PackageImportExport } from './PackageImportExport' import { PackageFilters } from './package-manager/PackageFilters' import { PackageTabs } from './package-manager/PackageTabs' +import { usePackageActions } from './package-manager/usePackageActions' import { usePackages } from './package-manager/usePackages' interface PackageManagerProps { @@ -31,61 +31,12 @@ export function PackageManager({ onClose }: PackageManagerProps) { } = usePackages() const [selectedPackage, setSelectedPackage] = useState(null) const [showDetails, setShowDetails] = useState(false) - const [installing, setInstalling] = useState(false) const [showImportExport, setShowImportExport] = useState(false) const [importExportMode, setImportExportMode] = useState<'import' | 'export'>('export') - - const handleInstallPackage = async (packageId: string) => { - setInstalling(true) - try { - const packageEntry = getCatalogEntry(packageId) - if (!packageEntry) { - toast.error('Package not found') - return - } - - await installPackage(packageId) - - toast.success(`${packageEntry.manifest.name} installed successfully!`) - await loadPackages() - setShowDetails(false) - } catch (error) { - console.error('Installation error:', error) - toast.error('Failed to install package') - } finally { - setInstalling(false) - } - } - - const handleUninstallPackage = async (packageId: string) => { - try { - const packageEntry = getCatalogEntry(packageId) - if (!packageEntry) { - toast.error('Package not found') - return - } - - await uninstallPackage(packageId) - - toast.success(`${packageEntry.manifest.name} uninstalled successfully!`) - await loadPackages() - setShowDetails(false) - } catch (error) { - console.error('Uninstallation error:', error) - toast.error('Failed to uninstall package') - } - } - - const handleTogglePackage = async (packageId: string, enabled: boolean) => { - try { - await togglePackageEnabled(packageId, enabled) - toast.success(enabled ? 'Package enabled' : 'Package disabled') - await loadPackages() - } catch (error) { - console.error('Toggle error:', error) - toast.error('Failed to toggle package') - } - } + const { installing, handleInstallPackage, handleUninstallPackage, handleTogglePackage } = usePackageActions({ + loadPackages, + getCatalogEntry, + }) const openPackageDetails = (packageId: string) => { const catalogEntry = getCatalogEntry(packageId) @@ -162,131 +113,16 @@ export function PackageManager({ onClose }: PackageManagerProps) { /> - - - {selectedPackage && ( - <> - -
-
- {selectedPackage.manifest.icon} -
-
- {selectedPackage.manifest.name} - {selectedPackage.manifest.description} -
- {selectedPackage.manifest.category} -
- - {selectedPackage.manifest.downloadCount.toLocaleString()} -
-
- - {selectedPackage.manifest.rating} -
-
-
-
-
- - - - -
-
-

Author

-
- - {selectedPackage.manifest.author} -
-
- -
-

Version

-

{selectedPackage.manifest.version}

-
- -
-

Tags

-
- {selectedPackage.manifest.tags.map(tag => ( - - - {tag} - - ))} -
-
- -
-

Includes

-
-
-
Data Models
-
{selectedPackage.content.schemas.length}
-
-
-
Pages
-
{selectedPackage.content.pages.length}
-
-
-
Workflows
-
{selectedPackage.content.workflows.length}
-
-
-
Scripts
-
{selectedPackage.content.luaScripts.length}
-
-
-
- - {selectedPackage.content.schemas.length > 0 && ( -
-

Data Models

-
- {selectedPackage.content.schemas.map(schema => ( -
-
{schema.displayName || schema.name}
-
{schema.fields.length} fields
-
- ))} -
-
- )} - - {selectedPackage.content.pages.length > 0 && ( -
-

Pages

-
- {selectedPackage.content.pages.map(page => ( -
-
{page.title}
-
{page.path}
-
- ))} -
-
- )} -
-
- - - {selectedPackage.manifest.installed ? ( - - ) : ( - - )} - - - )} -
-
+ handleInstallPackage(packageId, () => setShowDetails(false))} + onUninstall={(packageId) => handleUninstallPackage(packageId, () => setShowDetails(false))} + installedPackages={installedPackages} + getCatalogEntry={getCatalogEntry} + /> -
-
- - setManifest(prev => ({ ...prev, name: e.target.value }))} - /> -
- -
-
- - setManifest(prev => ({ ...prev, version: e.target.value }))} - /> -
- -
- - setManifest(prev => ({ ...prev, author: e.target.value }))} - /> -
-
- -
- -