dbwatcher 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -210
  3. data/app/assets/config/dbwatcher_manifest.js +15 -0
  4. data/app/assets/javascripts/dbwatcher/alpine_registrations.js +39 -0
  5. data/app/assets/javascripts/dbwatcher/auto_init.js +23 -0
  6. data/app/assets/javascripts/dbwatcher/components/base.js +141 -0
  7. data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +1008 -0
  8. data/app/assets/javascripts/dbwatcher/components/diagrams.js +449 -0
  9. data/app/assets/javascripts/dbwatcher/components/summary.js +234 -0
  10. data/app/assets/javascripts/dbwatcher/core/alpine_store.js +138 -0
  11. data/app/assets/javascripts/dbwatcher/core/api_client.js +162 -0
  12. data/app/assets/javascripts/dbwatcher/core/component_loader.js +70 -0
  13. data/app/assets/javascripts/dbwatcher/core/component_registry.js +94 -0
  14. data/app/assets/javascripts/dbwatcher/dbwatcher.js +120 -0
  15. data/app/assets/javascripts/dbwatcher/services/mermaid.js +315 -0
  16. data/app/assets/javascripts/dbwatcher/services/mermaid_service.js +199 -0
  17. data/app/assets/javascripts/dbwatcher/vendor/date-fns-browser.js +99 -0
  18. data/app/assets/javascripts/dbwatcher/vendor/lodash.min.js +140 -0
  19. data/app/assets/javascripts/dbwatcher/vendor/tabulator.min.js +3 -0
  20. data/app/assets/stylesheets/dbwatcher/application.css +423 -0
  21. data/app/assets/stylesheets/dbwatcher/application.scss +15 -0
  22. data/app/assets/stylesheets/dbwatcher/components/_badges.scss +38 -0
  23. data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +162 -0
  24. data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +51 -0
  25. data/app/assets/stylesheets/dbwatcher/components/_forms.scss +27 -0
  26. data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +55 -0
  27. data/app/assets/stylesheets/dbwatcher/core/_base.scss +34 -0
  28. data/app/assets/stylesheets/dbwatcher/core/_variables.scss +47 -0
  29. data/app/assets/stylesheets/dbwatcher/vendor/tabulator.min.css +2 -0
  30. data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +64 -0
  31. data/app/controllers/dbwatcher/base_controller.rb +8 -2
  32. data/app/controllers/dbwatcher/dashboard_controller.rb +8 -0
  33. data/app/controllers/dbwatcher/sessions_controller.rb +25 -10
  34. data/app/helpers/dbwatcher/component_helper.rb +29 -0
  35. data/app/helpers/dbwatcher/diagram_helper.rb +110 -0
  36. data/app/helpers/dbwatcher/session_helper.rb +3 -2
  37. data/app/views/dbwatcher/sessions/_changes_tab.html.erb +265 -0
  38. data/app/views/dbwatcher/sessions/_diagrams_tab.html.erb +166 -0
  39. data/app/views/dbwatcher/sessions/_session_header.html.erb +11 -0
  40. data/app/views/dbwatcher/sessions/_summary_tab.html.erb +88 -0
  41. data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +12 -0
  42. data/app/views/dbwatcher/sessions/changes.html.erb +21 -0
  43. data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +44 -0
  44. data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +96 -0
  45. data/app/views/dbwatcher/sessions/diagrams.html.erb +21 -0
  46. data/app/views/dbwatcher/sessions/index.html.erb +14 -10
  47. data/app/views/dbwatcher/sessions/shared/_layout.html.erb +8 -0
  48. data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +35 -0
  49. data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +25 -0
  50. data/app/views/dbwatcher/sessions/show.html.erb +3 -346
  51. data/app/views/dbwatcher/sessions/summary.html.erb +21 -0
  52. data/app/views/layouts/dbwatcher/application.html.erb +125 -247
  53. data/bin/compile_scss +49 -0
  54. data/config/routes.rb +26 -0
  55. data/lib/dbwatcher/configuration.rb +102 -8
  56. data/lib/dbwatcher/engine.rb +17 -7
  57. data/lib/dbwatcher/services/analyzers/session_data_processor.rb +98 -0
  58. data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +202 -0
  59. data/lib/dbwatcher/services/api/base_api_service.rb +100 -0
  60. data/lib/dbwatcher/services/api/changes_data_service.rb +112 -0
  61. data/lib/dbwatcher/services/api/diagram_data_service.rb +145 -0
  62. data/lib/dbwatcher/services/api/summary_data_service.rb +158 -0
  63. data/lib/dbwatcher/services/base_service.rb +64 -0
  64. data/lib/dbwatcher/services/diagram_analyzers/base_analyzer.rb +162 -0
  65. data/lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb +354 -0
  66. data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +502 -0
  67. data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +564 -0
  68. data/lib/dbwatcher/services/diagram_data/attribute.rb +154 -0
  69. data/lib/dbwatcher/services/diagram_data/dataset.rb +278 -0
  70. data/lib/dbwatcher/services/diagram_data/entity.rb +180 -0
  71. data/lib/dbwatcher/services/diagram_data/relationship.rb +188 -0
  72. data/lib/dbwatcher/services/diagram_data/relationship_params.rb +55 -0
  73. data/lib/dbwatcher/services/diagram_data.rb +65 -0
  74. data/lib/dbwatcher/services/diagram_error_handler.rb +239 -0
  75. data/lib/dbwatcher/services/diagram_generator.rb +154 -0
  76. data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +149 -0
  77. data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +49 -0
  78. data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +52 -0
  79. data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +52 -0
  80. data/lib/dbwatcher/services/diagram_system.rb +69 -0
  81. data/lib/dbwatcher/services/diagram_type_registry.rb +164 -0
  82. data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +127 -0
  83. data/lib/dbwatcher/services/mermaid_syntax/cardinality_mapper.rb +90 -0
  84. data/lib/dbwatcher/services/mermaid_syntax/class_diagram_builder.rb +136 -0
  85. data/lib/dbwatcher/services/mermaid_syntax/class_diagram_helper.rb +46 -0
  86. data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +116 -0
  87. data/lib/dbwatcher/services/mermaid_syntax/flowchart_builder.rb +109 -0
  88. data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +102 -0
  89. data/lib/dbwatcher/services/mermaid_syntax_builder.rb +155 -0
  90. data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +15 -128
  91. data/lib/dbwatcher/storage/api/session_api.rb +47 -0
  92. data/lib/dbwatcher/storage/base_storage.rb +7 -0
  93. data/lib/dbwatcher/version.rb +1 -1
  94. data/lib/dbwatcher.rb +58 -1
  95. metadata +94 -2
@@ -0,0 +1,138 @@
1
+ // Alpine.js Global Store for DBWatcher
2
+ // Provides centralized state management for sessions UI
3
+
4
+ document.addEventListener('alpine:init', () => {
5
+ Alpine.store('dbwatcher', {
6
+ // Session state
7
+ currentSession: null,
8
+ sessions: [],
9
+
10
+ // UI state
11
+ activeTab: 'changes',
12
+ loading: false,
13
+ error: null,
14
+
15
+ // Cache for API responses
16
+ cache: {
17
+ changes: new Map(),
18
+ summary: new Map(),
19
+ diagrams: new Map()
20
+ },
21
+
22
+ // Cache TTL in milliseconds (5 minutes)
23
+ cacheTtl: 5 * 60 * 1000,
24
+
25
+ // Initialize store
26
+ init() {
27
+ console.log('DBWatcher Alpine store initialized');
28
+ },
29
+
30
+ // Session management
31
+ setCurrentSession(session) {
32
+ this.currentSession = session;
33
+ },
34
+
35
+ // Tab navigation
36
+ setActiveTab(tab) {
37
+ this.activeTab = tab;
38
+ this.updateUrl(tab);
39
+ },
40
+
41
+ updateUrl(tab) {
42
+ if (typeof window !== 'undefined') {
43
+ const url = new URL(window.location);
44
+ url.searchParams.set('tab', tab);
45
+ window.history.pushState({}, '', url);
46
+ }
47
+ },
48
+
49
+ // Cache management
50
+ getCacheKey(type, sessionId, params = {}) {
51
+ const paramString = Object.keys(params)
52
+ .sort()
53
+ .map(key => `${key}=${params[key]}`)
54
+ .join('&');
55
+ return `${type}_${sessionId}_${paramString}`;
56
+ },
57
+
58
+ setCache(type, key, data) {
59
+ this.cache[type].set(key, {
60
+ data: data,
61
+ timestamp: Date.now()
62
+ });
63
+ },
64
+
65
+ getCache(type, key) {
66
+ const cached = this.cache[type].get(key);
67
+ if (!cached) return null;
68
+
69
+ // Check if cache is still valid
70
+ if (Date.now() - cached.timestamp > this.cacheTtl) {
71
+ this.cache[type].delete(key);
72
+ return null;
73
+ }
74
+
75
+ return cached.data;
76
+ },
77
+
78
+ clearCache(type = null) {
79
+ if (type) {
80
+ this.cache[type].clear();
81
+ } else {
82
+ Object.keys(this.cache).forEach(key => {
83
+ this.cache[key].clear();
84
+ });
85
+ }
86
+ },
87
+
88
+ // Error handling
89
+ setError(error) {
90
+ this.error = error;
91
+ console.error('DBWatcher error:', error);
92
+ },
93
+
94
+ clearError() {
95
+ this.error = null;
96
+ },
97
+
98
+ // Loading state
99
+ setLoading(loading) {
100
+ this.loading = loading;
101
+ }
102
+ });
103
+ });
104
+
105
+ // Session navigation component
106
+ function sessionNavigation(sessionId) {
107
+ return {
108
+ sessionId: sessionId,
109
+
110
+ init() {
111
+ // Set current session in store
112
+ Alpine.store('dbwatcher').setCurrentSession({ id: this.sessionId });
113
+
114
+ // Handle browser navigation
115
+ window.addEventListener('popstate', () => {
116
+ const params = new URLSearchParams(window.location.search);
117
+ const tab = params.get('tab') || 'changes';
118
+ Alpine.store('dbwatcher').setActiveTab(tab);
119
+ });
120
+ },
121
+
122
+ navigateToTab(tab) {
123
+ Alpine.store('dbwatcher').setActiveTab(tab);
124
+ },
125
+
126
+ get activeTab() {
127
+ return Alpine.store('dbwatcher').activeTab;
128
+ },
129
+
130
+ get loading() {
131
+ return Alpine.store('dbwatcher').loading;
132
+ },
133
+
134
+ get error() {
135
+ return Alpine.store('dbwatcher').error;
136
+ }
137
+ };
138
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * API Client for DBWatcher
3
+ *
4
+ * Centralized API communication with error handling,
5
+ * authentication, and response processing.
6
+ */
7
+
8
+ const ApiClient = {
9
+ // Base configuration
10
+ baseURL: '/dbwatcher/api/v1',
11
+ timeout: 30000,
12
+
13
+ // Default headers
14
+ defaultHeaders: {
15
+ 'Content-Type': 'application/json',
16
+ 'Accept': 'application/json',
17
+ 'X-Requested-With': 'XMLHttpRequest'
18
+ },
19
+
20
+ // Add CSRF token if available
21
+ getHeaders(additionalHeaders = {}) {
22
+ const headers = { ...this.defaultHeaders, ...additionalHeaders };
23
+
24
+ // Add CSRF token for Rails
25
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
26
+ if (csrfToken) {
27
+ headers['X-CSRF-Token'] = csrfToken;
28
+ }
29
+
30
+ return headers;
31
+ },
32
+
33
+ // Build full URL
34
+ buildURL(endpoint) {
35
+ if (endpoint.startsWith('http') || endpoint.startsWith('/')) {
36
+ return endpoint;
37
+ }
38
+
39
+ return `${this.baseURL}/${endpoint}`;
40
+ },
41
+
42
+ // Generic request method
43
+ async request(method, endpoint, options = {}) {
44
+ const {
45
+ body,
46
+ headers = {},
47
+ params = {},
48
+ timeout = this.timeout,
49
+ ...fetchOptions
50
+ } = options;
51
+
52
+ const url = new URL(this.buildURL(endpoint), window.location.origin);
53
+
54
+ // Add query parameters
55
+ Object.entries(params).forEach(([key, value]) => {
56
+ if (value !== null && value !== undefined) {
57
+ url.searchParams.append(key, value);
58
+ }
59
+ });
60
+
61
+ const config = {
62
+ method,
63
+ headers: this.getHeaders(headers),
64
+ ...fetchOptions
65
+ };
66
+
67
+ // Add body for non-GET requests
68
+ if (body && method !== 'GET') {
69
+ config.body = typeof body === 'string' ? body : JSON.stringify(body);
70
+ }
71
+
72
+ // Create timeout promise
73
+ const timeoutPromise = new Promise((_, reject) => {
74
+ setTimeout(() => reject(new Error('Request timeout')), timeout);
75
+ });
76
+
77
+ try {
78
+ const response = await Promise.race([
79
+ fetch(url, config),
80
+ timeoutPromise
81
+ ]);
82
+
83
+ return await this.handleResponse(response);
84
+ } catch (error) {
85
+ return this.handleError(error);
86
+ }
87
+ },
88
+
89
+ // Handle response processing
90
+ async handleResponse(response) {
91
+ if (!response.ok) {
92
+ const error = new Error(`HTTP error ${response.status}: ${response.statusText}`);
93
+ error.status = response.status;
94
+ error.statusText = response.statusText;
95
+
96
+ try {
97
+ error.data = await response.json();
98
+ } catch (e) {
99
+ error.data = null;
100
+ }
101
+
102
+ throw error;
103
+ }
104
+
105
+ // Handle different content types
106
+ const contentType = response.headers.get('content-type');
107
+
108
+ if (contentType?.includes('application/json')) {
109
+ return await response.json();
110
+ } else if (contentType?.includes('text/')) {
111
+ return await response.text();
112
+ } else {
113
+ return response;
114
+ }
115
+ },
116
+
117
+ // Handle errors consistently
118
+ handleError(error) {
119
+ if (error.name === 'AbortError') {
120
+ console.error('Request aborted');
121
+ }
122
+
123
+ if (error.message === 'Request timeout') {
124
+ console.error('Request timeout');
125
+ }
126
+
127
+ if (error.status) {
128
+ console.error(`HTTP error: ${error.status} ${error.statusText}`);
129
+ }
130
+
131
+ throw error;
132
+ },
133
+
134
+ // Convenience methods
135
+ get(endpoint, params = {}, options = {}) {
136
+ return this.request('GET', endpoint, { params, ...options });
137
+ },
138
+
139
+ post(endpoint, body = {}, options = {}) {
140
+ return this.request('POST', endpoint, { body, ...options });
141
+ },
142
+
143
+ put(endpoint, body = {}, options = {}) {
144
+ return this.request('PUT', endpoint, { body, ...options });
145
+ },
146
+
147
+ patch(endpoint, body = {}, options = {}) {
148
+ return this.request('PATCH', endpoint, { body, ...options });
149
+ },
150
+
151
+ delete(endpoint, options = {}) {
152
+ return this.request('DELETE', endpoint, options);
153
+ }
154
+ };
155
+
156
+ // Register with DBWatcher if available
157
+ if (window.DBWatcher) {
158
+ window.DBWatcher.ApiClient = ApiClient;
159
+ }
160
+
161
+ // Make available globally
162
+ window.ApiClient = ApiClient;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * DBWatcher Component Loader
3
+ * Handles component initialization and dependencies
4
+ */
5
+
6
+ window.DBWatcher = window.DBWatcher || {};
7
+
8
+ DBWatcher.ComponentLoader = {
9
+ // Track loaded components to prevent duplicates
10
+ loadedComponents: new Set(),
11
+
12
+ // Map of component dependencies
13
+ componentDependencies: {
14
+ 'diagrams': ['mermaid_service'],
15
+ 'changes_table': [],
16
+ 'summary': []
17
+ },
18
+
19
+ // Load a component and its dependencies
20
+ async load(componentName) {
21
+ if (this.loadedComponents.has(componentName)) {
22
+ console.log(`Component ${componentName} already loaded`);
23
+ return true;
24
+ }
25
+
26
+ // Check for dependencies
27
+ const dependencies = this.componentDependencies[componentName] || [];
28
+
29
+ // Load dependencies first
30
+ for (const dependency of dependencies) {
31
+ await this.load(dependency);
32
+ }
33
+
34
+ // Mark as loaded
35
+ this.loadedComponents.add(componentName);
36
+ console.log(`Loaded component: ${componentName}`);
37
+ return true;
38
+ },
39
+
40
+ // Initialize the component system
41
+ init(config = {}) {
42
+ // Setup auto-loading for Alpine.js components
43
+ document.addEventListener('alpine:init', () => {
44
+ // Register loader directive for on-demand loading
45
+ window.Alpine.directive('dbcomponent', (el, { value, expression }, { evaluate }) => {
46
+ const componentName = evaluate(expression);
47
+ this.load(componentName).then(() => {
48
+ console.log(`Component ${componentName} loaded via directive`);
49
+ });
50
+ });
51
+
52
+ // Auto-load components from data attributes
53
+ document.querySelectorAll('[data-component]').forEach(el => {
54
+ const componentName = el.dataset.component;
55
+ if (componentName) {
56
+ this.load(componentName);
57
+ }
58
+ });
59
+ });
60
+
61
+ return this;
62
+ }
63
+ };
64
+
65
+ // Auto-init if DBWatcher is available
66
+ if (window.DBWatcher && window.DBWatcher.init) {
67
+ document.addEventListener('DOMContentLoaded', () => {
68
+ DBWatcher.ComponentLoader.init();
69
+ });
70
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * DBWatcher Component Registry
3
+ * Centralized system for component registration, initialization, and lifecycle management
4
+ */
5
+
6
+ // Component Registry - Single source of truth for all components
7
+ window.DBWatcher = window.DBWatcher || {};
8
+ DBWatcher.ComponentRegistry = {
9
+ // Component storage
10
+ _components: {},
11
+
12
+ // Default configuration that applies to all components
13
+ defaultConfig: {
14
+ debugMode: false
15
+ },
16
+
17
+ // Register a component
18
+ register(name, factory) {
19
+ if (!name || typeof name !== 'string') {
20
+ console.error('Component name must be a non-empty string');
21
+ return false;
22
+ }
23
+
24
+ if (typeof factory !== 'function') {
25
+ console.error(`Component factory for '${name}' must be a function`);
26
+ return false;
27
+ }
28
+
29
+ this._components[name] = factory;
30
+
31
+ // Also store in DBWatcher.components for backward compatibility
32
+ if (window.DBWatcher) {
33
+ window.DBWatcher.components = window.DBWatcher.components || {};
34
+ window.DBWatcher.components[name] = factory;
35
+ }
36
+
37
+ // Auto-register with Alpine if available
38
+ if (window.Alpine && window.Alpine.data) {
39
+ this._registerWithAlpine(name, factory);
40
+ }
41
+
42
+ return true;
43
+ },
44
+
45
+ // Get component factory by name
46
+ get(name) {
47
+ return this._components[name] || null;
48
+ },
49
+
50
+ // Initialize all components
51
+ initAll(globalConfig = {}) {
52
+ if (!window.Alpine) {
53
+ console.warn('Alpine.js not found, components will not be initialized');
54
+ return;
55
+ }
56
+
57
+ // Register all components with Alpine
58
+ Object.keys(this._components).forEach(name => {
59
+ this._registerWithAlpine(name, this._components[name], globalConfig);
60
+ });
61
+
62
+ console.log(`Initialized ${Object.keys(this._components).length} components`);
63
+ },
64
+
65
+ // Private: Register component with Alpine.js
66
+ _registerWithAlpine(name, factory, globalConfig = {}) {
67
+ // Safety check
68
+ if (!window.Alpine || !window.Alpine.data) return;
69
+
70
+ try {
71
+ // Create Alpine data function that wraps our component factory
72
+ window.Alpine.data(name, (config = {}) => {
73
+ // Merge global config, default config, and instance config
74
+ const mergedConfig = {
75
+ ...this.defaultConfig,
76
+ ...globalConfig,
77
+ ...config
78
+ };
79
+
80
+ // Create component instance
81
+ return factory(mergedConfig);
82
+ });
83
+
84
+ console.log(`✅ Registered ${name} with Alpine.js`);
85
+ } catch (error) {
86
+ console.error(`Failed to register ${name} with Alpine.js:`, error);
87
+ }
88
+ }
89
+ };
90
+
91
+ // Export for module systems
92
+ if (typeof module !== 'undefined' && module.exports) {
93
+ module.exports = DBWatcher.ComponentRegistry;
94
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * DBWatcher - Main Entry Point
3
+ * Single entry point for the entire library with auto-binding functionality
4
+ * Version 2.0.0 - Optimized architecture
5
+ */
6
+ window.DBWatcher = {
7
+ // Basic information
8
+ version: "2.0.0",
9
+ initialized: false,
10
+ debug: false,
11
+
12
+ // Dependencies and configuration
13
+ dependencies: {
14
+ required: ['alpine', 'alpine-collapse'],
15
+ optional: ['lodash', 'dateFns', 'mermaid', 'svgPanZoom']
16
+ },
17
+
18
+ // Component registry - will be initialized from core/component_registry.js
19
+ ComponentRegistry: null,
20
+
21
+ // Base Component - will be initialized from components/base.js
22
+ BaseComponent: null,
23
+
24
+ // Initialize the entire system
25
+ init(config = {}) {
26
+ if (this.initialized) {
27
+ console.warn('DBWatcher already initialized');
28
+ return this;
29
+ }
30
+
31
+ // Set debug mode
32
+ this.debug = config.debug || false;
33
+
34
+ // Validate dependencies
35
+ this._validateDependencies();
36
+
37
+ // Ensure critical parts are loaded
38
+ if (!this.ComponentRegistry) {
39
+ console.error('ComponentRegistry not loaded! Make sure core/component_registry.js is included before initialization.');
40
+ return this;
41
+ }
42
+
43
+ // Initialize component registry with global config
44
+ this.ComponentRegistry.initAll(config);
45
+
46
+ // Mark as initialized
47
+ this.initialized = true;
48
+ console.log(`DBWatcher ${this.version} initialized`);
49
+
50
+ // Setup auto-initialization for Alpine
51
+ document.addEventListener('alpine:initialized', () => {
52
+ console.log('Alpine.js initialized, binding components...');
53
+ this.ComponentRegistry.initAll(config);
54
+ });
55
+
56
+ return this;
57
+ },
58
+
59
+ // Shorthand for component registration
60
+ register(name, factory) {
61
+ if (this.ComponentRegistry) {
62
+ return this.ComponentRegistry.register(name, factory);
63
+ } else {
64
+ console.error('Cannot register component: ComponentRegistry not loaded');
65
+ return false;
66
+ }
67
+ },
68
+
69
+ // Validate that required dependencies are loaded
70
+ _validateDependencies() {
71
+ const missing = [];
72
+
73
+ // Check for Alpine.js
74
+ if (!window.Alpine) {
75
+ missing.push('Alpine.js');
76
+ } else if (!window.Alpine.directive('collapse')) {
77
+ missing.push('Alpine.js collapse plugin');
78
+ }
79
+
80
+ // Log warnings for missing dependencies
81
+ if (missing.length > 0) {
82
+ console.warn(`DBWatcher missing dependencies: ${missing.join(', ')}`);
83
+ }
84
+
85
+ return missing.length === 0;
86
+ },
87
+
88
+ // Create a new component instance
89
+ createComponent(name, config = {}) {
90
+ if (!this.ComponentRegistry) {
91
+ console.error('ComponentRegistry not loaded');
92
+ return null;
93
+ }
94
+
95
+ const factory = this.ComponentRegistry.get(name);
96
+ if (!factory) {
97
+ console.error(`Component '${name}' not registered`);
98
+ return null;
99
+ }
100
+
101
+ return factory(config);
102
+ },
103
+
104
+ // Register a component using the ComponentRegistry
105
+ register(name, factory) {
106
+ if (!this.ComponentRegistry) {
107
+ console.error('ComponentRegistry not loaded');
108
+ return false;
109
+ }
110
+ return this.ComponentRegistry.register(name, factory);
111
+ },
112
+
113
+ // Legacy support for old API
114
+ registerComponent(name, factory) {
115
+ return this.register(name, factory);
116
+ },
117
+
118
+ // Maintain compatibility with existing components
119
+ components: {}
120
+ };