digitaltwin-core 0.14.0 → 0.14.1

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 (169) hide show
  1. package/package.json +101 -106
  2. package/dist/auth/apisix_parser.d.ts +0 -146
  3. package/dist/auth/apisix_parser.d.ts.map +0 -1
  4. package/dist/auth/apisix_parser.js +0 -185
  5. package/dist/auth/apisix_parser.js.map +0 -1
  6. package/dist/auth/auth_config.d.ts +0 -126
  7. package/dist/auth/auth_config.d.ts.map +0 -1
  8. package/dist/auth/auth_config.js +0 -169
  9. package/dist/auth/auth_config.js.map +0 -1
  10. package/dist/auth/index.d.ts +0 -5
  11. package/dist/auth/index.d.ts.map +0 -1
  12. package/dist/auth/index.js +0 -4
  13. package/dist/auth/index.js.map +0 -1
  14. package/dist/auth/types.d.ts +0 -100
  15. package/dist/auth/types.d.ts.map +0 -1
  16. package/dist/auth/types.js +0 -2
  17. package/dist/auth/types.js.map +0 -1
  18. package/dist/auth/user_service.d.ts +0 -86
  19. package/dist/auth/user_service.d.ts.map +0 -1
  20. package/dist/auth/user_service.js +0 -237
  21. package/dist/auth/user_service.js.map +0 -1
  22. package/dist/components/assets_manager.d.ts +0 -662
  23. package/dist/components/assets_manager.d.ts.map +0 -1
  24. package/dist/components/assets_manager.js +0 -1529
  25. package/dist/components/assets_manager.js.map +0 -1
  26. package/dist/components/async_upload.d.ts +0 -20
  27. package/dist/components/async_upload.d.ts.map +0 -1
  28. package/dist/components/async_upload.js +0 -10
  29. package/dist/components/async_upload.js.map +0 -1
  30. package/dist/components/collector.d.ts +0 -203
  31. package/dist/components/collector.d.ts.map +0 -1
  32. package/dist/components/collector.js +0 -202
  33. package/dist/components/collector.js.map +0 -1
  34. package/dist/components/custom_table_manager.d.ts +0 -503
  35. package/dist/components/custom_table_manager.d.ts.map +0 -1
  36. package/dist/components/custom_table_manager.js +0 -1052
  37. package/dist/components/custom_table_manager.js.map +0 -1
  38. package/dist/components/global_assets_handler.d.ts +0 -63
  39. package/dist/components/global_assets_handler.d.ts.map +0 -1
  40. package/dist/components/global_assets_handler.js +0 -127
  41. package/dist/components/global_assets_handler.js.map +0 -1
  42. package/dist/components/handler.d.ts +0 -104
  43. package/dist/components/handler.d.ts.map +0 -1
  44. package/dist/components/handler.js +0 -110
  45. package/dist/components/handler.js.map +0 -1
  46. package/dist/components/harvester.d.ts +0 -182
  47. package/dist/components/harvester.d.ts.map +0 -1
  48. package/dist/components/harvester.js +0 -393
  49. package/dist/components/harvester.js.map +0 -1
  50. package/dist/components/index.d.ts +0 -11
  51. package/dist/components/index.d.ts.map +0 -1
  52. package/dist/components/index.js +0 -9
  53. package/dist/components/index.js.map +0 -1
  54. package/dist/components/interfaces.d.ts +0 -126
  55. package/dist/components/interfaces.d.ts.map +0 -1
  56. package/dist/components/interfaces.js +0 -8
  57. package/dist/components/interfaces.js.map +0 -1
  58. package/dist/components/map_manager.d.ts +0 -61
  59. package/dist/components/map_manager.d.ts.map +0 -1
  60. package/dist/components/map_manager.js +0 -242
  61. package/dist/components/map_manager.js.map +0 -1
  62. package/dist/components/tileset_manager.d.ts +0 -125
  63. package/dist/components/tileset_manager.d.ts.map +0 -1
  64. package/dist/components/tileset_manager.js +0 -618
  65. package/dist/components/tileset_manager.js.map +0 -1
  66. package/dist/components/types.d.ts +0 -226
  67. package/dist/components/types.d.ts.map +0 -1
  68. package/dist/components/types.js +0 -8
  69. package/dist/components/types.js.map +0 -1
  70. package/dist/database/adapters/knex_database_adapter.d.ts +0 -92
  71. package/dist/database/adapters/knex_database_adapter.d.ts.map +0 -1
  72. package/dist/database/adapters/knex_database_adapter.js +0 -647
  73. package/dist/database/adapters/knex_database_adapter.js.map +0 -1
  74. package/dist/database/database_adapter.d.ts +0 -251
  75. package/dist/database/database_adapter.d.ts.map +0 -1
  76. package/dist/database/database_adapter.js +0 -46
  77. package/dist/database/database_adapter.js.map +0 -1
  78. package/dist/engine/digital_twin_engine.d.ts +0 -253
  79. package/dist/engine/digital_twin_engine.d.ts.map +0 -1
  80. package/dist/engine/digital_twin_engine.js +0 -790
  81. package/dist/engine/digital_twin_engine.js.map +0 -1
  82. package/dist/engine/endpoints.d.ts +0 -47
  83. package/dist/engine/endpoints.d.ts.map +0 -1
  84. package/dist/engine/endpoints.js +0 -56
  85. package/dist/engine/endpoints.js.map +0 -1
  86. package/dist/engine/events.d.ts +0 -93
  87. package/dist/engine/events.d.ts.map +0 -1
  88. package/dist/engine/events.js +0 -71
  89. package/dist/engine/events.js.map +0 -1
  90. package/dist/engine/initializer.d.ts +0 -62
  91. package/dist/engine/initializer.d.ts.map +0 -1
  92. package/dist/engine/initializer.js +0 -108
  93. package/dist/engine/initializer.js.map +0 -1
  94. package/dist/engine/queue_manager.d.ts +0 -87
  95. package/dist/engine/queue_manager.d.ts.map +0 -1
  96. package/dist/engine/queue_manager.js +0 -196
  97. package/dist/engine/queue_manager.js.map +0 -1
  98. package/dist/engine/scheduler.d.ts +0 -30
  99. package/dist/engine/scheduler.d.ts.map +0 -1
  100. package/dist/engine/scheduler.js +0 -370
  101. package/dist/engine/scheduler.js.map +0 -1
  102. package/dist/engine/upload_processor.d.ts +0 -36
  103. package/dist/engine/upload_processor.d.ts.map +0 -1
  104. package/dist/engine/upload_processor.js +0 -101
  105. package/dist/engine/upload_processor.js.map +0 -1
  106. package/dist/env/env.d.ts +0 -134
  107. package/dist/env/env.d.ts.map +0 -1
  108. package/dist/env/env.js +0 -177
  109. package/dist/env/env.js.map +0 -1
  110. package/dist/index.d.ts +0 -49
  111. package/dist/index.d.ts.map +0 -1
  112. package/dist/index.js +0 -57
  113. package/dist/index.js.map +0 -1
  114. package/dist/openapi/generator.d.ts +0 -93
  115. package/dist/openapi/generator.d.ts.map +0 -1
  116. package/dist/openapi/generator.js +0 -293
  117. package/dist/openapi/generator.js.map +0 -1
  118. package/dist/openapi/index.d.ts +0 -9
  119. package/dist/openapi/index.d.ts.map +0 -1
  120. package/dist/openapi/index.js +0 -9
  121. package/dist/openapi/index.js.map +0 -1
  122. package/dist/openapi/types.d.ts +0 -182
  123. package/dist/openapi/types.d.ts.map +0 -1
  124. package/dist/openapi/types.js +0 -16
  125. package/dist/openapi/types.js.map +0 -1
  126. package/dist/storage/adapters/local_storage_service.d.ts +0 -51
  127. package/dist/storage/adapters/local_storage_service.d.ts.map +0 -1
  128. package/dist/storage/adapters/local_storage_service.js +0 -110
  129. package/dist/storage/adapters/local_storage_service.js.map +0 -1
  130. package/dist/storage/adapters/ovh_storage_service.d.ts +0 -72
  131. package/dist/storage/adapters/ovh_storage_service.d.ts.map +0 -1
  132. package/dist/storage/adapters/ovh_storage_service.js +0 -206
  133. package/dist/storage/adapters/ovh_storage_service.js.map +0 -1
  134. package/dist/storage/storage_factory.d.ts +0 -14
  135. package/dist/storage/storage_factory.d.ts.map +0 -1
  136. package/dist/storage/storage_factory.js +0 -40
  137. package/dist/storage/storage_factory.js.map +0 -1
  138. package/dist/storage/storage_service.d.ts +0 -163
  139. package/dist/storage/storage_service.d.ts.map +0 -1
  140. package/dist/storage/storage_service.js +0 -54
  141. package/dist/storage/storage_service.js.map +0 -1
  142. package/dist/types/data_record.d.ts +0 -123
  143. package/dist/types/data_record.d.ts.map +0 -1
  144. package/dist/types/data_record.js +0 -8
  145. package/dist/types/data_record.js.map +0 -1
  146. package/dist/utils/http_responses.d.ts +0 -155
  147. package/dist/utils/http_responses.d.ts.map +0 -1
  148. package/dist/utils/http_responses.js +0 -190
  149. package/dist/utils/http_responses.js.map +0 -1
  150. package/dist/utils/index.d.ts +0 -8
  151. package/dist/utils/index.d.ts.map +0 -1
  152. package/dist/utils/index.js +0 -6
  153. package/dist/utils/index.js.map +0 -1
  154. package/dist/utils/logger.d.ts +0 -74
  155. package/dist/utils/logger.d.ts.map +0 -1
  156. package/dist/utils/logger.js +0 -92
  157. package/dist/utils/logger.js.map +0 -1
  158. package/dist/utils/map_to_data_record.d.ts +0 -10
  159. package/dist/utils/map_to_data_record.d.ts.map +0 -1
  160. package/dist/utils/map_to_data_record.js +0 -36
  161. package/dist/utils/map_to_data_record.js.map +0 -1
  162. package/dist/utils/servable_endpoint.d.ts +0 -63
  163. package/dist/utils/servable_endpoint.d.ts.map +0 -1
  164. package/dist/utils/servable_endpoint.js +0 -67
  165. package/dist/utils/servable_endpoint.js.map +0 -1
  166. package/dist/utils/zip_utils.d.ts +0 -66
  167. package/dist/utils/zip_utils.d.ts.map +0 -1
  168. package/dist/utils/zip_utils.js +0 -169
  169. package/dist/utils/zip_utils.js.map +0 -1
@@ -1,790 +0,0 @@
1
- import express from 'ultimate-express';
2
- import multer from 'multer';
3
- import fs from 'fs/promises';
4
- import cors from 'cors';
5
- import { initializeComponents, initializeAssetsManagers } from './initializer.js';
6
- import { UserService } from '../auth/user_service.js';
7
- import { exposeEndpoints } from './endpoints.js';
8
- import { scheduleComponents } from './scheduler.js';
9
- import { LogLevel } from '../utils/logger.js';
10
- import { QueueManager } from './queue_manager.js';
11
- import { UploadProcessor } from './upload_processor.js';
12
- import { isAsyncUploadable } from '../components/async_upload.js';
13
- /**
14
- * Digital Twin Engine - Core orchestrator for collectors, harvesters, and handlers
15
- *
16
- * The engine manages the lifecycle of all components, sets up queues for processing,
17
- * exposes HTTP endpoints, and handles the overall coordination of the digital twin system.
18
- *
19
- * @class DigitalTwinEngine
20
- * @example
21
- * ```TypeScript
22
- * import { DigitalTwinEngine } from './digital_twin_engine.js'
23
- * import { StorageServiceFactory } from '../storage/storage_factory.js'
24
- * import { KnexDatabaseAdapter } from '../database/adapters/knex_database_adapter.js'
25
- *
26
- * const storage = StorageServiceFactory.create()
27
- * const database = new KnexDatabaseAdapter({ client: 'sqlite3', connection: ':memory:' }, storage)
28
- *
29
- * const engine = new DigitalTwinEngine({
30
- * storage,
31
- * database,
32
- * collectors: [myCollector],
33
- * server: { port: 3000 }
34
- * })
35
- *
36
- * await engine.start()
37
- * ```
38
- */
39
- export class DigitalTwinEngine {
40
- #collectors;
41
- #harvesters;
42
- #handlers;
43
- #assetsManagers;
44
- #customTableManagers;
45
- #storage;
46
- #database;
47
- #app;
48
- #router;
49
- #options;
50
- #queueManager;
51
- #uploadProcessor;
52
- #server;
53
- #workers = [];
54
- /** Get all active components (collectors and harvesters) */
55
- get #activeComponents() {
56
- return [...this.#collectors, ...this.#harvesters];
57
- }
58
- /** Get all components (collectors + harvesters + handlers + assetsManagers + customTableManagers) */
59
- get #allComponents() {
60
- return [
61
- ...this.#collectors,
62
- ...this.#harvesters,
63
- ...this.#handlers,
64
- ...this.#assetsManagers,
65
- ...this.#customTableManagers
66
- ];
67
- }
68
- /** Check if multi-queue mode is enabled */
69
- get #isMultiQueueEnabled() {
70
- return this.#options.queues?.multiQueue ?? true;
71
- }
72
- /**
73
- * Creates a new Digital Twin Engine instance
74
- *
75
- * @param {EngineOptions} options - Configuration options for the engine
76
- * @throws {Error} If required options (storage, database) are missing
77
- *
78
- * @example
79
- * ```TypeScript
80
- * const engine = new DigitalTwinEngine({
81
- * storage: myStorageService,
82
- * database: myDatabaseAdapter,
83
- * collectors: [collector1, collector2],
84
- * server: { port: 4000, host: 'localhost' }
85
- * })
86
- * ```
87
- */
88
- constructor(options) {
89
- this.#options = this.#applyDefaults(options);
90
- this.#collectors = this.#options.collectors ?? [];
91
- this.#harvesters = this.#options.harvesters ?? [];
92
- this.#handlers = this.#options.handlers ?? [];
93
- this.#assetsManagers = this.#options.assetsManagers ?? [];
94
- this.#customTableManagers = this.#options.customTableManagers ?? [];
95
- this.#storage = this.#options.storage;
96
- this.#database = this.#options.database;
97
- this.#app = express();
98
- this.#router = express.Router();
99
- this.#queueManager = this.#createQueueManager();
100
- this.#uploadProcessor = this.#createUploadProcessor();
101
- }
102
- #createUploadProcessor() {
103
- // Only create upload processor if we have a queue manager (which means Redis is available)
104
- if (!this.#queueManager) {
105
- return null;
106
- }
107
- return new UploadProcessor(this.#storage, this.#database);
108
- }
109
- #applyDefaults(options) {
110
- return {
111
- collectors: [],
112
- harvesters: [],
113
- handlers: [],
114
- assetsManagers: [],
115
- customTableManagers: [],
116
- server: {
117
- port: 3000,
118
- host: '0.0.0.0',
119
- ...options.server
120
- },
121
- queues: {
122
- multiQueue: true,
123
- workers: {
124
- collectors: 1,
125
- harvesters: 1,
126
- ...options.queues?.workers
127
- },
128
- ...options.queues
129
- },
130
- logging: {
131
- level: LogLevel.INFO,
132
- format: 'text',
133
- ...options.logging
134
- },
135
- dryRun: false,
136
- ...options
137
- };
138
- }
139
- #createQueueManager() {
140
- // Create queue manager if we have collectors, harvesters, OR assets managers that may need async uploads
141
- const hasActiveComponents = this.#collectors.length > 0 || this.#harvesters.length > 0;
142
- const hasAssetsManagers = this.#assetsManagers.length > 0;
143
- if (!hasActiveComponents && !hasAssetsManagers) {
144
- return null;
145
- }
146
- return new QueueManager({
147
- redis: this.#options.redis,
148
- collectorWorkers: this.#options.queues?.workers?.collectors,
149
- harvesterWorkers: this.#options.queues?.workers?.harvesters,
150
- queueOptions: this.#options.queues?.options
151
- });
152
- }
153
- /**
154
- * Initialize store managers and create their database tables
155
- * @private
156
- */
157
- async #initializeCustomTableManagers() {
158
- for (const customTableManager of this.#customTableManagers) {
159
- // Inject dependencies
160
- customTableManager.setDependencies(this.#database);
161
- // Initialize the table with custom columns
162
- await customTableManager.initializeTable();
163
- }
164
- }
165
- /**
166
- * Ensure temporary upload directory exists
167
- * @private
168
- */
169
- async #ensureTempUploadDir() {
170
- const tempDir = process.env.TEMP_UPLOAD_DIR || '/tmp/digitaltwin-uploads';
171
- try {
172
- await fs.mkdir(tempDir, { recursive: true });
173
- }
174
- catch (error) {
175
- throw new Error(`Failed to create temp upload directory ${tempDir}: ${error}`);
176
- }
177
- }
178
- /**
179
- * Setup monitoring endpoints for queue statistics and health checks
180
- * @private
181
- */
182
- #setupMonitoringEndpoints() {
183
- // Health check endpoint
184
- this.#router.get('/api/health', async (req, res) => {
185
- const health = {
186
- status: 'ok',
187
- timestamp: new Date().toISOString(),
188
- uptime: process.uptime(),
189
- components: {
190
- collectors: this.#collectors.length,
191
- harvesters: this.#harvesters.length,
192
- handlers: this.#handlers.length,
193
- assetsManagers: this.#assetsManagers.length,
194
- customTableManagers: this.#customTableManagers.length
195
- }
196
- };
197
- res.json(health);
198
- });
199
- // Queue statistics endpoint
200
- this.#router.get('/api/queues/stats', async (req, res) => {
201
- if (this.#queueManager) {
202
- const stats = await this.#queueManager.getQueueStats();
203
- res.json(stats);
204
- }
205
- else {
206
- res.json({
207
- collectors: { status: 'No collectors configured' },
208
- harvesters: { status: 'No harvesters configured' }
209
- });
210
- }
211
- });
212
- }
213
- /**
214
- * Starts the Digital Twin Engine
215
- *
216
- * This method:
217
- * 1. Initializes all registered components (collectors, harvesters, handlers)
218
- * 2. Set up HTTP endpoints for component access
219
- * 3. Configures and starts background job queues
220
- * 4. Starts the HTTP server
221
- * 5. Exposes queue monitoring endpoints
222
- *
223
- * @async
224
- * @returns {Promise<void>}
225
- *
226
- * @example
227
- * ```TypeScript
228
- * await engine.start()
229
- * console.log('Engine is running!')
230
- * ```
231
- */
232
- async start() {
233
- const isDryRun = this.#options.dryRun ?? false;
234
- if (isDryRun) {
235
- // In dry run, just validate everything without creating tables
236
- const validationResult = await this.validateConfiguration();
237
- if (!validationResult.valid) {
238
- throw new Error(`Validation failed:\n${validationResult.engineErrors.join('\n')}`);
239
- }
240
- return;
241
- }
242
- // Normal startup - initialize user management tables first
243
- const userService = new UserService(this.#database);
244
- await userService.initializeTables();
245
- // Get autoMigration setting (default: true)
246
- const autoMigration = this.#options.autoMigration ?? true;
247
- // Initialize components and create tables if needed
248
- await initializeComponents(this.#activeComponents, this.#database, this.#storage, autoMigration);
249
- // Initialize assets managers and create their tables if needed
250
- await initializeAssetsManagers(this.#assetsManagers, this.#database, this.#storage, autoMigration);
251
- // Initialize store managers and create their tables if needed
252
- await this.#initializeCustomTableManagers();
253
- // Initialize handlers (inject dependencies if needed)
254
- for (const handler of this.#handlers) {
255
- if ('setDependencies' in handler && typeof handler.setDependencies === 'function') {
256
- handler.setDependencies(this.#database, this.#storage);
257
- }
258
- // If it's a GlobalAssetsHandler, inject the AssetsManager instances
259
- if ('setAssetsManagers' in handler && typeof handler.setAssetsManagers === 'function') {
260
- handler.setAssetsManagers(this.#assetsManagers);
261
- }
262
- }
263
- // Inject upload queue to components that support async uploads
264
- if (this.#queueManager) {
265
- const allManagers = [...this.#assetsManagers];
266
- for (const manager of allManagers) {
267
- if (isAsyncUploadable(manager)) {
268
- manager.setUploadQueue(this.#queueManager.uploadQueue);
269
- }
270
- }
271
- }
272
- // Start upload processor worker (for async file processing)
273
- // Uses same Redis config as QueueManager (defaults to localhost:6379 if not specified)
274
- if (this.#uploadProcessor) {
275
- const redisConfig = this.#options.redis || {
276
- host: 'localhost',
277
- port: 6379,
278
- maxRetriesPerRequest: null
279
- };
280
- this.#uploadProcessor.start(redisConfig);
281
- }
282
- await exposeEndpoints(this.#router, this.#allComponents);
283
- // Setup component scheduling with queue manager (only if we have active components)
284
- if (this.#activeComponents.length > 0 && this.#queueManager) {
285
- this.#workers = await scheduleComponents(this.#activeComponents, this.#queueManager, this.#isMultiQueueEnabled);
286
- }
287
- this.#setupMonitoringEndpoints();
288
- // Ensure temporary upload directory exists
289
- await this.#ensureTempUploadDir();
290
- // Enable CORS for cross-origin requests from frontend applications
291
- this.#app.use(cors({
292
- origin: process.env.CORS_ORIGIN || true, // Allow all origins by default, configure in production
293
- methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
294
- allowedHeaders: ['Content-Type', 'Authorization'],
295
- credentials: true // Allow cookies/credentials
296
- }));
297
- // Configure Express middlewares for body parsing - no limits for large files
298
- this.#app.use(express.json({ limit: '10gb' }));
299
- this.#app.use(express.urlencoded({ extended: true, limit: '10gb' }));
300
- // Add multipart/form-data support for file uploads with disk storage for large files
301
- const upload = multer({
302
- storage: multer.diskStorage({
303
- destination: (req, file, cb) => {
304
- // Use temporary directory, will be cleaned up after processing
305
- const tempDir = process.env.TEMP_UPLOAD_DIR || '/tmp/digitaltwin-uploads';
306
- cb(null, tempDir);
307
- },
308
- filename: (req, file, cb) => {
309
- // Generate unique filename to avoid conflicts
310
- const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
311
- cb(null, file.fieldname + '-' + uniqueSuffix + '-' + file.originalname);
312
- }
313
- }),
314
- limits: {
315
- // Remove file size limit to allow large files (10GB+)
316
- files: 1, // Only one file per request for safety
317
- parts: 10, // Limit form parts
318
- headerPairs: 2000 // Limit header pairs
319
- }
320
- });
321
- this.#app.use(upload.single('file'));
322
- this.#app.use(this.#router);
323
- const { port, host = '0.0.0.0' } = this.#options.server ?? {
324
- port: 3000,
325
- host: '0.0.0.0'
326
- };
327
- // Wait for server to be ready
328
- await new Promise(resolve => {
329
- this.#server = this.#app.listen(port, host, () => {
330
- resolve();
331
- });
332
- });
333
- // Set server timeouts for large file uploads (10 minutes)
334
- if (this.#server) {
335
- this.#server.timeout = 600000; // 10 minutes for request processing
336
- this.#server.keepAliveTimeout = 620000; // Slightly longer than timeout
337
- this.#server.headersTimeout = 621000; // Slightly longer than keepAliveTimeout
338
- }
339
- }
340
- /**
341
- * Get the server port
342
- *
343
- * @returns {number | undefined} The server port or undefined if not started
344
- *
345
- * @example
346
- * ```TypeScript
347
- * const port = engine.getPort()
348
- * console.log(`Server running on port ${port}`)
349
- * ```
350
- */
351
- getPort() {
352
- if (!this.#server)
353
- return undefined;
354
- try {
355
- const address = this.#server.address();
356
- if (typeof address === 'object' && address !== null && 'port' in address) {
357
- return address.port;
358
- }
359
- }
360
- catch {
361
- // If address() fails, return the configured port
362
- return this.#options.server?.port;
363
- }
364
- return this.#options.server?.port;
365
- }
366
- /**
367
- * Stops the Digital Twin Engine gracefully
368
- *
369
- * This method:
370
- * 1. Closes HTTP server
371
- * 2. Stops background workers
372
- * 3. Closes all queue connections
373
- * 4. Closes database connections
374
- * 5. Clean up resources
375
- *
376
- * @async
377
- * @returns {Promise<void>}
378
- *
379
- * @example
380
- * ```TypeScript
381
- * await engine.stop()
382
- * console.log('Engine stopped gracefully')
383
- * ```
384
- */
385
- async stop() {
386
- const errors = [];
387
- // 1. Close HTTP server first
388
- if (this.#server) {
389
- const server = this.#server;
390
- try {
391
- await new Promise((resolve, reject) => {
392
- server.close(err => {
393
- if (err)
394
- reject(err);
395
- else
396
- resolve();
397
- });
398
- });
399
- }
400
- catch (error) {
401
- errors.push(new Error(`Server close error: ${error instanceof Error ? error.message : String(error)}`));
402
- }
403
- }
404
- // 2. Close all workers with extended timeout and force close
405
- if (this.#workers.length > 0) {
406
- await Promise.all(this.#workers.map(async (worker) => {
407
- try {
408
- await Promise.race([
409
- worker.close(),
410
- new Promise((_, reject) => setTimeout(() => reject(new Error('Worker close timeout')), 5000))
411
- ]);
412
- }
413
- catch {
414
- // Force close if timeout or error
415
- try {
416
- await worker.disconnect();
417
- }
418
- catch (disconnectError) {
419
- errors.push(new Error(`Worker force close error: ${disconnectError instanceof Error ? disconnectError.message : String(disconnectError)}`));
420
- }
421
- }
422
- }));
423
- }
424
- // 3. Stop upload processor worker
425
- if (this.#uploadProcessor) {
426
- try {
427
- await this.#uploadProcessor.stop();
428
- }
429
- catch (error) {
430
- errors.push(new Error(`Upload processor close error: ${error instanceof Error ? error.message : String(error)}`));
431
- }
432
- }
433
- // 4. Close queue connections (only if we have a queue manager)
434
- if (this.#queueManager) {
435
- try {
436
- await this.#queueManager.close();
437
- }
438
- catch (error) {
439
- errors.push(new Error(`Queue manager close error: ${error instanceof Error ? error.message : String(error)}`));
440
- }
441
- }
442
- // 5. Close database connections
443
- try {
444
- await this.#database.close();
445
- }
446
- catch (error) {
447
- errors.push(new Error(`Database close error: ${error instanceof Error ? error.message : String(error)}`));
448
- }
449
- if (errors.length > 0 && process.env.NODE_ENV !== 'test') {
450
- console.warn('[DigitalTwin] Stopped with warnings:', errors.map(e => e.message).join(', '));
451
- }
452
- }
453
- /**
454
- * Validate the engine configuration and all components
455
- *
456
- * This method checks that all components are properly configured and can be initialized
457
- * without actually creating tables or starting the server.
458
- *
459
- * @returns {Promise<ValidationResult>} Comprehensive validation results
460
- *
461
- * @example
462
- * ```typescript
463
- * const result = await engine.validateConfiguration()
464
- * if (!result.valid) {
465
- * console.error('Validation errors:', result.engineErrors)
466
- * }
467
- * ```
468
- */
469
- async validateConfiguration() {
470
- const componentResults = [];
471
- const engineErrors = [];
472
- // Validate collectors
473
- for (const collector of this.#collectors) {
474
- componentResults.push(await this.#validateComponent(collector, 'collector'));
475
- }
476
- // Validate harvesters
477
- for (const harvester of this.#harvesters) {
478
- componentResults.push(await this.#validateComponent(harvester, 'harvester'));
479
- }
480
- // Validate handlers
481
- for (const handler of this.#handlers) {
482
- componentResults.push(await this.#validateComponent(handler, 'handler'));
483
- }
484
- // Validate assets managers
485
- for (const assetsManager of this.#assetsManagers) {
486
- componentResults.push(await this.#validateComponent(assetsManager, 'assets_manager'));
487
- }
488
- // Validate store managers
489
- for (const customTableManager of this.#customTableManagers) {
490
- componentResults.push(await this.#validateComponent(customTableManager, 'custom_table_manager'));
491
- }
492
- // Validate engine-level configuration
493
- try {
494
- if (!this.#storage) {
495
- engineErrors.push('Storage service is required');
496
- }
497
- if (!this.#database) {
498
- engineErrors.push('Database adapter is required');
499
- }
500
- // Test storage connection
501
- if (this.#storage && typeof this.#storage.save === 'function') {
502
- // Storage validation passed
503
- }
504
- else {
505
- engineErrors.push('Storage service does not implement required methods');
506
- }
507
- // Test database connection
508
- if (this.#database && typeof this.#database.save === 'function') {
509
- // Database validation passed
510
- }
511
- else {
512
- engineErrors.push('Database adapter does not implement required methods');
513
- }
514
- }
515
- catch (error) {
516
- engineErrors.push(`Engine configuration error: ${error instanceof Error ? error.message : String(error)}`);
517
- }
518
- // Calculate summary
519
- const validComponents = componentResults.filter(c => c.valid).length;
520
- const totalWarnings = componentResults.reduce((acc, c) => acc + c.warnings.length, 0);
521
- const result = {
522
- valid: componentResults.every(c => c.valid) && engineErrors.length === 0,
523
- components: componentResults,
524
- engineErrors,
525
- summary: {
526
- total: componentResults.length,
527
- valid: validComponents,
528
- invalid: componentResults.length - validComponents,
529
- warnings: totalWarnings
530
- }
531
- };
532
- return result;
533
- }
534
- /**
535
- * Test all components by running their core methods without persistence
536
- *
537
- * @returns {Promise<ComponentValidationResult[]>} Test results for each component
538
- *
539
- * @example
540
- * ```typescript
541
- * const results = await engine.testComponents()
542
- * results.forEach(result => {
543
- * console.log(`${result.name}: ${result.valid ? '✅' : '❌'}`)
544
- * })
545
- * ```
546
- */
547
- async testComponents() {
548
- const results = [];
549
- // Test collectors
550
- for (const collector of this.#collectors) {
551
- const result = await this.#testCollector(collector);
552
- results.push(result);
553
- }
554
- // Test harvesters
555
- for (const harvester of this.#harvesters) {
556
- const result = await this.#testHarvester(harvester);
557
- results.push(result);
558
- }
559
- // Test handlers
560
- for (const handler of this.#handlers) {
561
- const result = await this.#testHandler(handler);
562
- results.push(result);
563
- }
564
- // Test assets managers
565
- for (const assetsManager of this.#assetsManagers) {
566
- const result = await this.#testAssetsManager(assetsManager);
567
- results.push(result);
568
- }
569
- return results;
570
- }
571
- /**
572
- * Validate a single component
573
- */
574
- async #validateComponent(component, type) {
575
- const errors = [];
576
- const warnings = [];
577
- try {
578
- // Check if component has required methods
579
- if (typeof component.getConfiguration !== 'function') {
580
- errors.push('Component must implement getConfiguration() method');
581
- }
582
- const config = component.getConfiguration();
583
- // Validate configuration
584
- if (!config.name) {
585
- errors.push('Component configuration must have a name');
586
- }
587
- if (!config.description) {
588
- warnings.push('Component configuration should have a description');
589
- }
590
- // Type-specific validation
591
- if (type === 'collector' || type === 'harvester') {
592
- const activeComponent = component;
593
- if (typeof activeComponent.setDependencies !== 'function') {
594
- errors.push('Active components must implement setDependencies() method');
595
- }
596
- }
597
- if (type === 'collector') {
598
- const collector = component;
599
- if (typeof collector.collect !== 'function') {
600
- errors.push('Collector must implement collect() method');
601
- }
602
- if (typeof collector.getSchedule !== 'function') {
603
- errors.push('Collector must implement getSchedule() method');
604
- }
605
- }
606
- if (type === 'harvester') {
607
- const harvester = component;
608
- if (typeof harvester.harvest !== 'function') {
609
- errors.push('Harvester must implement harvest() method');
610
- }
611
- }
612
- if (type === 'assets_manager') {
613
- const assetsManager = component;
614
- if (typeof assetsManager.uploadAsset !== 'function') {
615
- errors.push('AssetsManager must implement uploadAsset() method');
616
- }
617
- if (typeof assetsManager.getAllAssets !== 'function') {
618
- errors.push('AssetsManager must implement getAllAssets() method');
619
- }
620
- }
621
- if (type === 'custom_table_manager') {
622
- const customTableManager = component;
623
- if (typeof customTableManager.setDependencies !== 'function') {
624
- errors.push('CustomTableManager must implement setDependencies() method');
625
- }
626
- if (typeof customTableManager.initializeTable !== 'function') {
627
- errors.push('CustomTableManager must implement initializeTable() method');
628
- }
629
- // Validate store configuration
630
- const config = customTableManager.getConfiguration();
631
- if (typeof config !== 'object' || config === null) {
632
- errors.push('CustomTableManager must return a valid configuration object');
633
- }
634
- else {
635
- if (!config.columns || typeof config.columns !== 'object') {
636
- errors.push('CustomTableManager configuration must define columns');
637
- }
638
- else {
639
- // Validate columns definition
640
- const columnCount = Object.keys(config.columns).length;
641
- if (columnCount === 0) {
642
- warnings.push('CustomTableManager has no custom columns defined');
643
- }
644
- // Validate column names and types
645
- for (const [columnName, columnType] of Object.entries(config.columns)) {
646
- if (!columnName || typeof columnName !== 'string') {
647
- errors.push('Column names must be non-empty strings');
648
- }
649
- if (!columnType || typeof columnType !== 'string') {
650
- errors.push(`Column '${columnName}' must have a valid SQL type`);
651
- }
652
- }
653
- }
654
- }
655
- }
656
- }
657
- catch (error) {
658
- errors.push(`Validation error: ${error instanceof Error ? error.message : String(error)}`);
659
- }
660
- return {
661
- name: component.getConfiguration?.()?.name || 'unknown',
662
- type,
663
- valid: errors.length === 0,
664
- errors,
665
- warnings
666
- };
667
- }
668
- /**
669
- * Test a collector by running its collect method
670
- */
671
- async #testCollector(collector) {
672
- const errors = [];
673
- const warnings = [];
674
- const config = collector.getConfiguration();
675
- try {
676
- // Test the collect method
677
- const result = await collector.collect();
678
- if (!Buffer.isBuffer(result)) {
679
- errors.push('collect() method must return a Buffer');
680
- }
681
- if (result.length === 0) {
682
- warnings.push('collect() method returned empty buffer');
683
- }
684
- }
685
- catch (error) {
686
- errors.push(`collect() method failed: ${error instanceof Error ? error.message : String(error)}`);
687
- }
688
- return {
689
- name: config.name,
690
- type: 'collector',
691
- valid: errors.length === 0,
692
- errors,
693
- warnings
694
- };
695
- }
696
- /**
697
- * Test a harvester (more complex as it needs mock data)
698
- */
699
- async #testHarvester(harvester) {
700
- const errors = [];
701
- const warnings = [];
702
- const config = harvester.getConfiguration();
703
- try {
704
- // Create mock data for testing
705
- const mockData = {
706
- id: 1,
707
- name: 'test',
708
- date: new Date(),
709
- contentType: 'application/json',
710
- url: 'test://url',
711
- data: async () => Buffer.from('{"test": true}')
712
- };
713
- // Test the harvest method
714
- const result = await harvester.harvest(mockData, {});
715
- if (!Buffer.isBuffer(result)) {
716
- errors.push('harvest() method must return a Buffer');
717
- }
718
- }
719
- catch (error) {
720
- errors.push(`harvest() method failed: ${error instanceof Error ? error.message : String(error)}`);
721
- }
722
- return {
723
- name: config.name,
724
- type: 'harvester',
725
- valid: errors.length === 0,
726
- errors,
727
- warnings
728
- };
729
- }
730
- /**
731
- * Test a handler
732
- */
733
- async #testHandler(handler) {
734
- const errors = [];
735
- const warnings = [];
736
- const config = handler.getConfiguration();
737
- try {
738
- // Handlers are mostly validated through their endpoint configuration
739
- if (typeof handler.getEndpoints === 'function') {
740
- const endpoints = handler.getEndpoints();
741
- if (!Array.isArray(endpoints)) {
742
- errors.push('getEndpoints() must return an array');
743
- }
744
- }
745
- }
746
- catch (error) {
747
- errors.push(`Handler test failed: ${error instanceof Error ? error.message : String(error)}`);
748
- }
749
- return {
750
- name: config.name,
751
- type: 'handler',
752
- valid: errors.length === 0,
753
- errors,
754
- warnings
755
- };
756
- }
757
- /**
758
- * Test an assets manager
759
- */
760
- async #testAssetsManager(assetsManager) {
761
- const errors = [];
762
- const warnings = [];
763
- const config = assetsManager.getConfiguration();
764
- try {
765
- // Test configuration
766
- if (!config.contentType) {
767
- errors.push('AssetsManager configuration must have a contentType');
768
- }
769
- // In dry run mode, we can't test actual upload/download without dependencies
770
- // Just validate that the methods exist and are callable
771
- if (typeof assetsManager.getEndpoints === 'function') {
772
- const endpoints = assetsManager.getEndpoints();
773
- if (!Array.isArray(endpoints)) {
774
- errors.push('getEndpoints() must return an array');
775
- }
776
- }
777
- }
778
- catch (error) {
779
- errors.push(`AssetsManager test failed: ${error instanceof Error ? error.message : String(error)}`);
780
- }
781
- return {
782
- name: config.name,
783
- type: 'assets_manager',
784
- valid: errors.length === 0,
785
- errors,
786
- warnings
787
- };
788
- }
789
- }
790
- //# sourceMappingURL=digital_twin_engine.js.map