@things-factory/integration-base 9.0.28 → 9.0.32

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.
package/README.md CHANGED
@@ -1,2 +1,513 @@
1
- # integration-service
2
-
1
+ # @things-factory/integration-base
2
+
3
+ A comprehensive integration framework for the Things Factory ecosystem that enables seamless connectivity with external systems and services through scenario-driven automation.
4
+
5
+ [![Version](https://img.shields.io/npm/v/@things-factory/integration-base.svg)](https://npmjs.org/package/@things-factory/integration-base)
6
+ [![License](https://img.shields.io/npm/l/@things-factory/integration-base.svg)](https://github.com/hatiolab/things-factory/blob/main/packages/integration-base/LICENSE)
7
+
8
+ ## Overview
9
+
10
+ The Integration Base module provides a robust, scenario-driven automation engine that enables complex integration workflows through a visual, step-based approach. It serves as the central nervous system for enterprise automation within the Things Factory framework.
11
+
12
+ ### Key Features
13
+
14
+ - **Scenario Engine**: Visual workflow designer with 50+ built-in task types
15
+ - **Multi-Protocol Support**: Database, HTTP/REST, GraphQL, MQTT, WebSocket integrations
16
+ - **Headless Automation**: Web scraping and browser automation via Puppeteer
17
+ - **Multi-Tenant Architecture**: Domain-based isolation with role-based access control
18
+ - **Real-Time Monitoring**: GraphQL subscriptions for live status updates
19
+ - **Scheduling System**: Cron-based automated execution with timezone support
20
+ - **Edge Computing**: Distributed execution through edge appliances
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @things-factory/integration-base
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Basic Scenario Configuration
31
+
32
+ ```typescript
33
+ import { ScenarioEngine } from '@things-factory/integration-base'
34
+
35
+ // Create a simple HTTP integration scenario
36
+ const scenario = {
37
+ name: 'fetch-user-data',
38
+ description: 'Fetch user data from external API',
39
+ steps: [
40
+ {
41
+ name: 'fetch-users',
42
+ task: 'http-get',
43
+ connection: 'api-server',
44
+ params: {
45
+ path: '/api/users',
46
+ headers: { 'Authorization': 'Bearer ${token}' }
47
+ }
48
+ },
49
+ {
50
+ name: 'store-data',
51
+ task: 'database-query',
52
+ connection: 'main-db',
53
+ params: {
54
+ query: 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',
55
+ bindings: '${fetch-users.data}'
56
+ }
57
+ }
58
+ ]
59
+ }
60
+ ```
61
+
62
+ ### 2. Connection Setup
63
+
64
+ ```typescript
65
+ // Database connection
66
+ const dbConnection = {
67
+ name: 'main-db',
68
+ type: 'postgresql-connector',
69
+ endpoint: 'postgresql://localhost:5432/mydb',
70
+ params: {
71
+ username: 'dbuser',
72
+ password: 'dbpass',
73
+ database: 'myapp'
74
+ }
75
+ }
76
+
77
+ // HTTP API connection
78
+ const apiConnection = {
79
+ name: 'api-server',
80
+ type: 'http-connector',
81
+ endpoint: 'https://api.example.com',
82
+ params: {
83
+ timeout: 30000,
84
+ rejectUnauthorized: true
85
+ }
86
+ }
87
+ ```
88
+
89
+ ## Supported Connectors
90
+
91
+ ### Database Connectors
92
+ - **PostgreSQL** (`postgresql-connector`)
93
+ - **MySQL** (`mysql-connector`)
94
+ - **Microsoft SQL Server** (`mssql-connector`)
95
+ - **Oracle** (`oracle-connector`)
96
+ - **SQLite** (`sqlite-connector`)
97
+
98
+ ### Communication Connectors
99
+ - **HTTP/REST** (`http-connector`)
100
+ - **GraphQL** (`graphql-connector`) - Apollo Client with OAuth2 support
101
+ - **MQTT** (`mqtt-connector`) - Async message pub/sub
102
+ - **WebSocket** (`socket-server`) - Raw TCP socket communication
103
+
104
+ ### Automation Connectors
105
+ - **Headless Browser** (`headless-connector`) - Puppeteer-based web automation
106
+ - **Python Runtime** (`pyrun-connector`) - Python script execution
107
+ - **Operato Integration** (`operato-connector`) - Things Factory cross-instance
108
+
109
+ ### Utility Connectors
110
+ - **Echo Service** (`echo-back-connector`) - Testing and debugging
111
+ - **Proxy Connector** (`proxy-connector`) - Edge appliance delegation
112
+
113
+ ## Task Types
114
+
115
+ ### Data Operations
116
+ - `database-query` - Execute SQL queries with parameter binding
117
+ - `graphql-query` / `graphql-mutate` - GraphQL operations
118
+ - `data-mapper` - Transform data structures
119
+ - `jsonata` - JSONata transformations
120
+ - `csv-readline` - CSV file processing
121
+
122
+ ### Communication Tasks
123
+ - `http-get` / `http-post` / `http-put` / `http-patch` / `http-delete`
124
+ - `headless-get` / `headless-post` / `headless-put` / `headless-patch` / `headless-delete`
125
+ - `mqtt-publish` / `mqtt-subscribe`
126
+ - `socket-listener` / `publish`
127
+
128
+ ### Control Flow
129
+ - `goto` / `end` - Flow control
130
+ - `switch-goto` / `switch-range-goto` - Conditional branching
131
+ - `sub-scenario` - Nested scenario execution
132
+ - `empty-check` - Validation tasks
133
+ - `variables` - Variable management
134
+
135
+ ### Automation & Utility
136
+ - `headless-scrap` - Web scraping
137
+ - `pyrun-execute` - Python script execution
138
+ - `sleep` / `random` - Utility functions
139
+ - `script` - Custom JavaScript execution
140
+
141
+ ## Configuration
142
+
143
+ ### Environment Variables
144
+
145
+ ```bash
146
+ # Database connection
147
+ DB_HOST=localhost
148
+ DB_PORT=5432
149
+ DB_NAME=integration_db
150
+ DB_USERNAME=user
151
+ DB_PASSWORD=password
152
+
153
+ # Headless browser pool
154
+ HEADLESS_POOL_MIN=2
155
+ HEADLESS_POOL_MAX=10
156
+
157
+ # Logging
158
+ DEBUG=things-factory:integration-base:*
159
+ LOG_LEVEL=info
160
+ ```
161
+
162
+ ### Domain Configuration
163
+
164
+ ```typescript
165
+ // Domain-specific environment variables
166
+ const domainConfig = {
167
+ domain: 'tenant1.example.com',
168
+ envVars: [
169
+ { name: 'Connection::api-server::apiKey', value: 'secret-key-123' },
170
+ { name: 'Connection::database::password', value: 'encrypted-password' }
171
+ ]
172
+ }
173
+ ```
174
+
175
+ ## API Reference
176
+
177
+ ### GraphQL Schema
178
+
179
+ ```graphql
180
+ type Scenario {
181
+ id: String!
182
+ name: String!
183
+ description: String
184
+ active: Boolean!
185
+ schedule: String
186
+ timezone: String
187
+ steps: [Step!]!
188
+ }
189
+
190
+ type Connection {
191
+ id: String!
192
+ name: String!
193
+ type: String!
194
+ endpoint: String!
195
+ active: Boolean!
196
+ state: ConnectionStatus!
197
+ }
198
+
199
+ type ScenarioInstance {
200
+ id: String!
201
+ scenario: Scenario!
202
+ status: ScenarioStatus!
203
+ startedAt: String
204
+ finishedAt: String
205
+ result: JSON
206
+ }
207
+ ```
208
+
209
+ ### REST API Endpoints
210
+
211
+ ```
212
+ GET /api/unstable/scenarios # List scenarios
213
+ POST /api/unstable/scenarios # Create scenario
214
+ PUT /api/unstable/scenarios/:id # Update scenario
215
+ DELETE /api/unstable/scenarios/:id # Delete scenario
216
+
217
+ GET /api/unstable/scenario-instances # List instances
218
+ POST /api/unstable/run-scenario # Execute scenario
219
+ POST /api/unstable/start-scenario # Start async scenario
220
+ POST /api/unstable/stop-scenario # Stop running scenario
221
+ ```
222
+
223
+ ## Examples
224
+
225
+ ### Database Integration
226
+
227
+ ```typescript
228
+ const databaseScenario = {
229
+ name: 'sync-customer-data',
230
+ steps: [
231
+ {
232
+ name: 'fetch-customers',
233
+ task: 'database-query',
234
+ connection: 'source-db',
235
+ params: {
236
+ query: 'SELECT * FROM customers WHERE updated_at > ?',
237
+ bindings: ['${lastSync}']
238
+ }
239
+ },
240
+ {
241
+ name: 'transform-data',
242
+ task: 'data-mapper',
243
+ params: {
244
+ mapping: {
245
+ customer_id: 'id',
246
+ full_name: 'name',
247
+ email_address: 'email'
248
+ }
249
+ }
250
+ },
251
+ {
252
+ name: 'insert-customers',
253
+ task: 'database-query',
254
+ connection: 'target-db',
255
+ params: {
256
+ query: 'INSERT INTO customers (id, name, email) VALUES ${fetch-customers.data}',
257
+ batchSize: 1000
258
+ }
259
+ }
260
+ ]
261
+ }
262
+ ```
263
+
264
+ ### Web Scraping with Headless Browser
265
+
266
+ ```typescript
267
+ const scrapingScenario = {
268
+ name: 'scrape-product-prices',
269
+ steps: [
270
+ {
271
+ name: 'login-to-site',
272
+ task: 'headless-post',
273
+ connection: 'ecommerce-site',
274
+ params: {
275
+ path: '/login',
276
+ contentType: 'application/json',
277
+ accessor: 'credentials'
278
+ }
279
+ },
280
+ {
281
+ name: 'scrape-products',
282
+ task: 'headless-scrap',
283
+ connection: 'ecommerce-site',
284
+ params: {
285
+ path: '/products',
286
+ selectors: [
287
+ { text: 'productName', value: '.product-title' },
288
+ { text: 'price', value: '.price-display' },
289
+ { text: 'availability', value: '.stock-status' }
290
+ ],
291
+ waitForSelectors: '.product-list',
292
+ waitForTimeout: 5000
293
+ }
294
+ },
295
+ {
296
+ name: 'save-results',
297
+ task: 'database-query',
298
+ connection: 'analytics-db',
299
+ params: {
300
+ query: 'INSERT INTO product_prices (name, price, availability, scraped_at) VALUES ${scrape-products.data}'
301
+ }
302
+ }
303
+ ]
304
+ }
305
+ ```
306
+
307
+ ### MQTT Integration
308
+
309
+ ```typescript
310
+ const mqttScenario = {
311
+ name: 'iot-data-processing',
312
+ steps: [
313
+ {
314
+ name: 'subscribe-sensors',
315
+ task: 'mqtt-subscribe',
316
+ connection: 'iot-broker',
317
+ params: {
318
+ topic: 'sensors/+/temperature',
319
+ qos: 1
320
+ }
321
+ },
322
+ {
323
+ name: 'validate-data',
324
+ task: 'empty-check',
325
+ params: {
326
+ accessor: 'temperature',
327
+ throwOnEmpty: true
328
+ }
329
+ },
330
+ {
331
+ name: 'store-reading',
332
+ task: 'database-query',
333
+ connection: 'timeseries-db',
334
+ params: {
335
+ query: 'INSERT INTO sensor_readings (sensor_id, temperature, timestamp) VALUES (?, ?, NOW())',
336
+ bindings: ['${deviceId}', '${temperature}']
337
+ }
338
+ },
339
+ {
340
+ name: 'alert-if-critical',
341
+ task: 'switch-range-goto',
342
+ params: {
343
+ value: '${temperature}',
344
+ cases: [
345
+ { min: 80, max: 999, goto: 'send-alert' }
346
+ ]
347
+ }
348
+ },
349
+ {
350
+ name: 'send-alert',
351
+ task: 'http-post',
352
+ connection: 'alert-service',
353
+ params: {
354
+ path: '/alerts',
355
+ contentType: 'application/json',
356
+ accessor: 'alertData'
357
+ }
358
+ }
359
+ ]
360
+ }
361
+ ```
362
+
363
+ ## Scheduling
364
+
365
+ Configure automated scenario execution using cron expressions:
366
+
367
+ ```typescript
368
+ const scheduledScenario = {
369
+ name: 'daily-data-sync',
370
+ description: 'Sync data every day at 2 AM',
371
+ schedule: '0 2 * * *', // Cron expression
372
+ timezone: 'Asia/Seoul',
373
+ active: true,
374
+ steps: [/* scenario steps */]
375
+ }
376
+ ```
377
+
378
+ ## Security
379
+
380
+ ### Authentication & Authorization
381
+ - **Role-based access control** (RBAC) integration
382
+ - **Domain-based isolation** for multi-tenant environments
383
+ - **OAuth2 authentication** for external GraphQL services
384
+ - **Encrypted parameter storage** for sensitive configuration
385
+
386
+ ### Best Practices
387
+ - Store sensitive data in domain environment variables
388
+ - Use parameter encryption for passwords and API keys
389
+ - Implement IP-based access control where needed
390
+ - Regular security audits of scenario configurations
391
+
392
+ ## Monitoring & Debugging
393
+
394
+ ### Logging
395
+ ```bash
396
+ # Enable debug logging
397
+ DEBUG=things-factory:integration-base:* npm start
398
+
399
+ # Specific component logging
400
+ DEBUG=things-factory:integration-base:scenario-engine npm start
401
+ DEBUG=things-factory:integration-base:connector:* npm start
402
+ ```
403
+
404
+ ### Real-time Monitoring
405
+ ```graphql
406
+ # Subscribe to scenario status updates
407
+ subscription {
408
+ scenarioInstanceStatusUpdated {
409
+ id
410
+ status
411
+ progress
412
+ result
413
+ error
414
+ }
415
+ }
416
+
417
+ # Subscribe to connection status
418
+ subscription {
419
+ connectionStatusUpdated {
420
+ name
421
+ state
422
+ lastConnected
423
+ }
424
+ }
425
+ ```
426
+
427
+ ## Development
428
+
429
+ ### Building from Source
430
+
431
+ ```bash
432
+ # Clone the repository
433
+ git clone https://github.com/hatiolab/things-factory.git
434
+ cd things-factory/packages/integration-base
435
+
436
+ # Install dependencies
437
+ npm install
438
+
439
+ # Build the module
440
+ npm run build
441
+
442
+ # Run tests
443
+ npm test
444
+ ```
445
+
446
+ ### Creating Custom Connectors
447
+
448
+ ```typescript
449
+ import { Connector } from '@things-factory/integration-base'
450
+
451
+ export class CustomConnector implements Connector {
452
+ async connect(connection) {
453
+ // Implementation
454
+ }
455
+
456
+ async disconnect(connection) {
457
+ // Cleanup
458
+ }
459
+
460
+ get parameterSpec() {
461
+ return [
462
+ { type: 'string', name: 'apiKey', label: 'API Key' },
463
+ { type: 'number', name: 'timeout', label: 'Timeout (ms)' }
464
+ ]
465
+ }
466
+
467
+ get taskPrefixes() {
468
+ return ['custom']
469
+ }
470
+ }
471
+ ```
472
+
473
+ ### Creating Custom Tasks
474
+
475
+ ```typescript
476
+ import { TaskRegistry } from '@things-factory/integration-base'
477
+
478
+ async function CustomTask(step, context) {
479
+ const { params } = step
480
+ const { logger, data, domain } = context
481
+
482
+ // Task implementation
483
+ return { data: result }
484
+ }
485
+
486
+ CustomTask.parameterSpec = [
487
+ { type: 'string', name: 'parameter1', label: 'Parameter 1' }
488
+ ]
489
+
490
+ TaskRegistry.registerTaskHandler('custom-task', CustomTask)
491
+ ```
492
+
493
+ ## Contributing
494
+
495
+ 1. Fork the repository
496
+ 2. Create a feature branch
497
+ 3. Make your changes
498
+ 4. Add tests for new functionality
499
+ 5. Submit a pull request
500
+
501
+ ## License
502
+
503
+ MIT © [Hatiolab](https://github.com/hatiolab)
504
+
505
+ ## Support
506
+
507
+ - [Documentation](https://docs.things-factory.com/integration-base)
508
+ - [Issue Tracker](https://github.com/hatiolab/things-factory/issues)
509
+ - [Discussions](https://github.com/hatiolab/things-factory/discussions)
510
+
511
+ ---
512
+
513
+ **Note**: This module is part of the Things Factory ecosystem. For complete setup and configuration, refer to the main [Things Factory documentation](https://docs.things-factory.com).
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const https_1 = tslib_1.__importDefault(require("https"));
5
+ const url_1 = require("url");
6
+ const task_registry_1 = require("../task-registry");
7
+ const connection_manager_1 = require("../connection-manager");
8
+ async function HeadlessDelete(step, { logger, data, domain }) {
9
+ const { connection: connectionName, params: stepOptions } = step;
10
+ const { headers: requestHeaders, path, queryParams } = stepOptions || {};
11
+ const connection = await connection_manager_1.ConnectionManager.getConnectionInstanceByName(domain, connectionName);
12
+ if (!connection) {
13
+ throw new Error(`Connection '${connectionName}' is not established.`);
14
+ }
15
+ const { endpoint, params: connectionParams, acquireSessionPage, releasePage } = connection;
16
+ const headers = {
17
+ ...requestHeaders
18
+ };
19
+ const options = {
20
+ method: 'DELETE',
21
+ headers
22
+ };
23
+ const { rejectUnauthorized } = connectionParams;
24
+ if (!rejectUnauthorized) {
25
+ const httpsAgent = new https_1.default.Agent({
26
+ rejectUnauthorized
27
+ });
28
+ options.agent = httpsAgent;
29
+ }
30
+ const page = await acquireSessionPage();
31
+ try {
32
+ page.on('console', async (msg) => {
33
+ console.log(`[browser ${msg.type()}] ${msg.text()}`);
34
+ });
35
+ page.on('requestfailed', request => {
36
+ console.log('Request failed:', request.url());
37
+ });
38
+ await page.goto(endpoint, { waitUntil: 'networkidle2' });
39
+ // URL 구성 - queryParams가 있으면 추가
40
+ let requestUrl = new url_1.URL(path, endpoint);
41
+ if (queryParams && typeof queryParams === 'object') {
42
+ Object.keys(queryParams).forEach(key => {
43
+ if (queryParams[key] !== null && queryParams[key] !== undefined) {
44
+ requestUrl.searchParams.append(key, String(queryParams[key]));
45
+ }
46
+ });
47
+ }
48
+ const response = await page.evaluate(async (urlString, options) => {
49
+ const response = await fetch(urlString, options);
50
+ if (!response.ok) {
51
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
52
+ }
53
+ const contentType = response.headers.get('content-type') || '';
54
+ if (contentType.includes('application/json')) {
55
+ return await response.json();
56
+ }
57
+ else if (contentType.includes('text/')) {
58
+ return await response.text();
59
+ }
60
+ else {
61
+ // 응답이 없는 경우 (204 No Content 등)
62
+ return null;
63
+ }
64
+ }, requestUrl.toString(), options);
65
+ return {
66
+ data: response
67
+ };
68
+ }
69
+ catch (error) {
70
+ logger.error('Error in HeadlessDelete:', error);
71
+ throw error;
72
+ }
73
+ finally {
74
+ await releasePage(page);
75
+ }
76
+ }
77
+ HeadlessDelete.parameterSpec = [
78
+ {
79
+ type: 'string',
80
+ name: 'path',
81
+ label: 'path'
82
+ },
83
+ {
84
+ type: 'http-headers',
85
+ name: 'headers',
86
+ label: 'headers'
87
+ },
88
+ {
89
+ type: 'options',
90
+ name: 'queryParams',
91
+ label: 'query-parameters'
92
+ }
93
+ ];
94
+ task_registry_1.TaskRegistry.registerTaskHandler('headless-delete', HeadlessDelete);
95
+ //# sourceMappingURL=headless-delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headless-delete.js","sourceRoot":"","sources":["../../../server/engine/task/headless-delete.ts"],"names":[],"mappings":"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","sourcesContent":["import https from 'https'\nimport { URL } from 'url'\n\nimport { TaskRegistry } from '../task-registry'\nimport { ConnectionManager } from '../connection-manager'\n\nasync function HeadlessDelete(step, { logger, data, domain }) {\n const { connection: connectionName, params: stepOptions } = step\n const { headers: requestHeaders, path, queryParams } = stepOptions || {}\n\n const connection = await ConnectionManager.getConnectionInstanceByName(domain, connectionName)\n\n if (!connection) {\n throw new Error(`Connection '${connectionName}' is not established.`)\n }\n\n const { endpoint, params: connectionParams, acquireSessionPage, releasePage } = connection\n\n const headers = {\n ...requestHeaders\n }\n\n const options = {\n method: 'DELETE',\n headers\n } as any\n\n const { rejectUnauthorized } = connectionParams\n\n if (!rejectUnauthorized) {\n const httpsAgent = new https.Agent({\n rejectUnauthorized\n })\n options.agent = httpsAgent\n }\n\n const page = await acquireSessionPage()\n\n try {\n page.on('console', async msg => {\n console.log(`[browser ${msg.type()}] ${msg.text()}`)\n })\n\n page.on('requestfailed', request => {\n console.log('Request failed:', request.url())\n })\n\n await page.goto(endpoint, { waitUntil: 'networkidle2' })\n\n // URL 구성 - queryParams가 있으면 추가\n let requestUrl = new URL(path, endpoint)\n if (queryParams && typeof queryParams === 'object') {\n Object.keys(queryParams).forEach(key => {\n if (queryParams[key] !== null && queryParams[key] !== undefined) {\n requestUrl.searchParams.append(key, String(queryParams[key]))\n }\n })\n }\n\n const response = await page.evaluate(\n async (urlString, options) => {\n const response = await fetch(urlString, options)\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const contentType = response.headers.get('content-type') || ''\n \n if (contentType.includes('application/json')) {\n return await response.json()\n } else if (contentType.includes('text/')) {\n return await response.text()\n } else {\n // 응답이 없는 경우 (204 No Content 등)\n return null\n }\n },\n requestUrl.toString(),\n options\n )\n\n return {\n data: response\n }\n } catch (error) {\n logger.error('Error in HeadlessDelete:', error)\n throw error\n } finally {\n await releasePage(page)\n }\n}\n\nHeadlessDelete.parameterSpec = [\n {\n type: 'string',\n name: 'path',\n label: 'path'\n },\n {\n type: 'http-headers',\n name: 'headers',\n label: 'headers'\n },\n {\n type: 'options',\n name: 'queryParams',\n label: 'query-parameters'\n }\n]\n\nTaskRegistry.registerTaskHandler('headless-delete', HeadlessDelete)"]}
@@ -0,0 +1 @@
1
+ export {};