@things-factory/integration-base 8.0.0-beta.8 → 8.0.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 (160) hide show
  1. package/dist-server/engine/connector/http-connector.js +1 -1
  2. package/dist-server/engine/connector/http-connector.js.map +1 -1
  3. package/dist-server/engine/connector/index.d.ts +0 -1
  4. package/dist-server/engine/connector/index.js +0 -1
  5. package/dist-server/engine/connector/index.js.map +1 -1
  6. package/dist-server/engine/index.d.ts +0 -1
  7. package/dist-server/engine/index.js +0 -1
  8. package/dist-server/engine/index.js.map +1 -1
  9. package/dist-server/engine/task/headless-post.js +33 -19
  10. package/dist-server/engine/task/headless-post.js.map +1 -1
  11. package/dist-server/engine/task/headless-scrap.js +13 -20
  12. package/dist-server/engine/task/headless-scrap.js.map +1 -1
  13. package/dist-server/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +11 -12
  15. package/server/controllers/index.ts +2 -0
  16. package/server/controllers/publish-data.ts +29 -0
  17. package/server/controllers/scenario-controller.ts +156 -0
  18. package/server/engine/analyzer/analyze-integration.ts +115 -0
  19. package/server/engine/connection-manager.ts +239 -0
  20. package/server/engine/connector/echo-back-connector.ts +51 -0
  21. package/server/engine/connector/echo-back-server.ts +72 -0
  22. package/server/engine/connector/graphql-connector.ts +126 -0
  23. package/server/engine/connector/http-connector.ts +65 -0
  24. package/server/engine/connector/index.ts +12 -0
  25. package/server/engine/connector/mqtt-connector.ts +78 -0
  26. package/server/engine/connector/mssql-connector.ts +152 -0
  27. package/server/engine/connector/mysql-connector.ts +94 -0
  28. package/server/engine/connector/operato-connector.ts +264 -0
  29. package/server/engine/connector/oracle-connector.ts +218 -0
  30. package/server/engine/connector/postgresql-connector.ts +152 -0
  31. package/server/engine/connector/proxy-connector.ts +53 -0
  32. package/server/engine/connector/socket-server.ts +86 -0
  33. package/server/engine/connector/sqlite-connector.ts +69 -0
  34. package/server/engine/edge-client.ts +45 -0
  35. package/server/engine/index.ts +10 -0
  36. package/server/engine/pending-queue.ts +97 -0
  37. package/server/engine/scenario-engine.ts +106 -0
  38. package/server/engine/task/book-up-scenario.ts +73 -0
  39. package/server/engine/task/csv-readline.ts +127 -0
  40. package/server/engine/task/data-accessor.ts +36 -0
  41. package/server/engine/task/data-mapper.ts +47 -0
  42. package/server/engine/task/database-query.ts +56 -0
  43. package/server/engine/task/echo-receive.ts +21 -0
  44. package/server/engine/task/echo-send.ts +32 -0
  45. package/server/engine/task/empty-check.ts +38 -0
  46. package/server/engine/task/end.ts +18 -0
  47. package/server/engine/task/floating-point.ts +71 -0
  48. package/server/engine/task/goto.ts +27 -0
  49. package/server/engine/task/graphql-mutate.ts +79 -0
  50. package/server/engine/task/graphql-query.ts +78 -0
  51. package/server/engine/task/headless-post.ts +147 -0
  52. package/server/engine/task/headless-scrap.ts +80 -0
  53. package/server/engine/task/http-get.ts +117 -0
  54. package/server/engine/task/http-post.ts +148 -0
  55. package/server/engine/task/index.ts +45 -0
  56. package/server/engine/task/jsonata.ts +45 -0
  57. package/server/engine/task/local-graphql-mutate.ts +100 -0
  58. package/server/engine/task/local-graphql-query.ts +100 -0
  59. package/server/engine/task/log.ts +78 -0
  60. package/server/engine/task/mqtt-publish.ts +45 -0
  61. package/server/engine/task/mqtt-subscribe.ts +139 -0
  62. package/server/engine/task/mssql-procedure.ts +128 -0
  63. package/server/engine/task/oracle-procedure.ts +124 -0
  64. package/server/engine/task/pick-pending-scenario.ts +80 -0
  65. package/server/engine/task/publish.ts +40 -0
  66. package/server/engine/task/random.ts +53 -0
  67. package/server/engine/task/reset-pending-queue.ts +17 -0
  68. package/server/engine/task/script.ts +63 -0
  69. package/server/engine/task/set-domain.ts +37 -0
  70. package/server/engine/task/sleep.ts +34 -0
  71. package/server/engine/task/socket-listener.ts +96 -0
  72. package/server/engine/task/state-group-read.ts +69 -0
  73. package/server/engine/task/state-read.ts +56 -0
  74. package/server/engine/task/state-write.ts +65 -0
  75. package/server/engine/task/stop-scenario.ts +44 -0
  76. package/server/engine/task/sub-scenario.ts +57 -0
  77. package/server/engine/task/switch-goto.ts +43 -0
  78. package/server/engine/task/switch-range-goto.ts +53 -0
  79. package/server/engine/task/switch-range-scenario.ts +79 -0
  80. package/server/engine/task/switch-range-set.ts +48 -0
  81. package/server/engine/task/switch-scenario.ts +67 -0
  82. package/server/engine/task/switch-set.ts +37 -0
  83. package/server/engine/task/throw.ts +27 -0
  84. package/server/engine/task/utils/headless-pool-for-scenario.ts +71 -0
  85. package/server/engine/task/utils/substitute.ts +44 -0
  86. package/server/engine/task/variables.ts +17 -0
  87. package/server/engine/task-registry.ts +23 -0
  88. package/server/engine/types.ts +114 -0
  89. package/server/index.ts +20 -0
  90. package/server/migrations/index.ts +9 -0
  91. package/server/restful/index.ts +1 -0
  92. package/server/restful/unstable/index.ts +7 -0
  93. package/server/restful/unstable/run-scenario.ts +51 -0
  94. package/server/restful/unstable/scenario-instance.ts +52 -0
  95. package/server/restful/unstable/scenario-instances.ts +80 -0
  96. package/server/restful/unstable/scenario.ts +41 -0
  97. package/server/restful/unstable/scenarios.ts +69 -0
  98. package/server/restful/unstable/start-scenario.ts +33 -0
  99. package/server/restful/unstable/stop-scenario.ts +30 -0
  100. package/server/routers/scenario-schedule-callback-router.ts +69 -0
  101. package/server/routers/scenario-view-router.ts +46 -0
  102. package/server/routes.ts +30 -0
  103. package/server/service/analysis/analysis-query.ts +13 -0
  104. package/server/service/analysis/index.ts +3 -0
  105. package/server/service/connection/connection-mutation.ts +190 -0
  106. package/server/service/connection/connection-query.ts +87 -0
  107. package/server/service/connection/connection-subscription.ts +104 -0
  108. package/server/service/connection/connection-type.ts +288 -0
  109. package/server/service/connection/index.ts +7 -0
  110. package/server/service/connector/connector-query.ts +62 -0
  111. package/server/service/connector/connector-type.ts +29 -0
  112. package/server/service/connector/index.ts +4 -0
  113. package/server/service/index.ts +52 -0
  114. package/server/service/payload-log/index.ts +7 -0
  115. package/server/service/payload-log/payload-log-mutation.ts +151 -0
  116. package/server/service/payload-log/payload-log-query.ts +49 -0
  117. package/server/service/payload-log/payload-log-type.ts +36 -0
  118. package/server/service/payload-log/payload-log.ts +100 -0
  119. package/server/service/property-spec.ts +24 -0
  120. package/server/service/scenario/index.ts +6 -0
  121. package/server/service/scenario/scenario-mutation.ts +396 -0
  122. package/server/service/scenario/scenario-query.ts +109 -0
  123. package/server/service/scenario/scenario-type.ts +78 -0
  124. package/server/service/scenario/scenario.ts +124 -0
  125. package/server/service/scenario-flow/scenario-flow.ts +17 -0
  126. package/server/service/scenario-instance/index.ts +6 -0
  127. package/server/service/scenario-instance/scenario-instance-mutation.ts +44 -0
  128. package/server/service/scenario-instance/scenario-instance-query.ts +42 -0
  129. package/server/service/scenario-instance/scenario-instance-subscription.ts +118 -0
  130. package/server/service/scenario-instance/scenario-instance-type.ts +563 -0
  131. package/server/service/scenario-queue/index.ts +4 -0
  132. package/server/service/scenario-queue/scenario-queue-subscription.ts +55 -0
  133. package/server/service/scenario-queue/scenario-queue-type.ts +27 -0
  134. package/server/service/state-register/data-resolver.ts +56 -0
  135. package/server/service/state-register/index.ts +8 -0
  136. package/server/service/state-register/state-register-mutation.ts +166 -0
  137. package/server/service/state-register/state-register-query.ts +80 -0
  138. package/server/service/state-register/state-register-type.ts +80 -0
  139. package/server/service/state-register/state-register.ts +113 -0
  140. package/server/service/step/index.ts +6 -0
  141. package/server/service/step/step-mutation.ts +52 -0
  142. package/server/service/step/step-query.ts +55 -0
  143. package/server/service/step/step-type.ts +215 -0
  144. package/server/service/task-type/index.ts +4 -0
  145. package/server/service/task-type/task-type-query.ts +95 -0
  146. package/server/service/task-type/task-type-type.ts +29 -0
  147. package/translations/en.json +4 -12
  148. package/translations/ja.json +4 -12
  149. package/translations/ko.json +4 -12
  150. package/translations/ms.json +4 -12
  151. package/translations/zh.json +4 -12
  152. package/dist-server/engine/connector/headless-connector.d.ts +0 -23
  153. package/dist-server/engine/connector/headless-connector.js +0 -357
  154. package/dist-server/engine/connector/headless-connector.js.map +0 -1
  155. package/dist-server/engine/resource-pool/headless-pool.d.ts +0 -1
  156. package/dist-server/engine/resource-pool/headless-pool.js +0 -62
  157. package/dist-server/engine/resource-pool/headless-pool.js.map +0 -1
  158. package/dist-server/engine/resource-pool/index.d.ts +0 -1
  159. package/dist-server/engine/resource-pool/index.js +0 -5
  160. package/dist-server/engine/resource-pool/index.js.map +0 -1
@@ -0,0 +1,264 @@
1
+ import 'cross-fetch/polyfill'
2
+
3
+ import { ApolloClient, InMemoryCache, createHttpLink, split } from '@apollo/client/core'
4
+ import { setContext } from '@apollo/client/link/context'
5
+
6
+ import WebSocket from 'ws'
7
+ import { createClient } from 'graphql-ws'
8
+ import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
9
+ import { getMainDefinition } from '@apollo/client/utilities'
10
+ import gql from 'graphql-tag'
11
+
12
+ import { ConnectionManager } from '../connection-manager'
13
+ import { Connector } from '../types'
14
+ import { InputConnection } from '../../service/connection/connection-type'
15
+
16
+ import { Scenario } from '../../service/scenario/scenario'
17
+ import { ScenarioInstance } from '../../service/scenario-instance/scenario-instance-type'
18
+
19
+ import { getRepository, GraphqlLocalClient, Domain } from '@things-factory/shell'
20
+ import { User, checkUserHasRole } from '@things-factory/auth-base'
21
+
22
+ const debug = require('debug')('things-factory:integration-base:operato-connector')
23
+
24
+ const defaultOptions: any = {
25
+ watchQuery: {
26
+ fetchPolicy: 'no-cache',
27
+ errorPolicy: 'ignore'
28
+ },
29
+ query: {
30
+ fetchPolicy: 'no-cache', //'network-only'
31
+ errorPolicy: 'all'
32
+ },
33
+ mutate: {
34
+ errorPolicy: 'all'
35
+ }
36
+ }
37
+
38
+ export const GRAPHQL_URI = '/graphql'
39
+ export const SUBSCRIPTION_URI = GRAPHQL_URI
40
+
41
+ interface SubscriberData {
42
+ tag: string
43
+ scenario: any
44
+ subscriptionObserver: any
45
+ }
46
+
47
+ export class OperatoConnector implements Connector {
48
+ private context: any
49
+
50
+ async ready(connectionConfigs: InputConnection[]) {
51
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)))
52
+
53
+ ConnectionManager.logger.info('operato-connector connections are ready')
54
+ }
55
+
56
+ async connect(connection: InputConnection) {
57
+ const {
58
+ endpoint: uri,
59
+ params: { authKey, domain, subscriptionHandlers = {} }
60
+ } = connection
61
+
62
+ if (!authKey || !domain) {
63
+ throw new Error('some connection paramter missing.')
64
+ }
65
+
66
+ const domainOwner = await getRepository(User).findOne({
67
+ where: {
68
+ id: connection.domain.owner
69
+ }
70
+ })
71
+
72
+ this.context = {
73
+ domain: connection.domain,
74
+ user: domainOwner
75
+ /* TODO: domainOwner 대신 특정 유저를 지정할 수 있도록 개선해야함. 모든 커넥션에 유저를 지정하는 기능으로 일반화할 필요가 있는 지 고민해야함 */
76
+ }
77
+
78
+ const httpLink = createHttpLink({
79
+ uri: uri
80
+ })
81
+
82
+ /*
83
+ CHECKPOINT:
84
+ 1. GraphqQLWsLink를 사용하면 setContext를 통한 추가 헤더 설정이 무시됩니다.
85
+ 따라서, GraphQLWsLink를 사용하려면, connectionParams를 통해 헤더를 설정해야 합니다.
86
+
87
+ 2. 서버에서 실행시, webSocketImpl을 명시적으로 지정해야 합니다.
88
+ */
89
+ const wsLink = new GraphQLWsLink(
90
+ createClient({
91
+ url: uri.replace(/^http/, 'ws'),
92
+ keepAlive: 10_000,
93
+ retryAttempts: 1_000_000,
94
+ shouldRetry: e => true,
95
+ webSocketImpl: WebSocket,
96
+ connectionParams: {
97
+ headers: {
98
+ 'x-things-factory-domain': domain,
99
+ authorization: authKey ? `Bearer ${authKey}` : ''
100
+ }
101
+ }
102
+ })
103
+ )
104
+
105
+ const splitLink = split(
106
+ ({ query }) => {
107
+ const def = getMainDefinition(query)
108
+ return def.kind === 'OperationDefinition' && def.operation === 'subscription'
109
+ },
110
+ wsLink,
111
+ setContext((_, { headers }) => {
112
+ return {
113
+ headers: {
114
+ ...headers,
115
+ 'x-things-factory-domain': domain,
116
+ authorization: authKey ? `Bearer ${authKey}` : ''
117
+ }
118
+ }
119
+ }).concat(httpLink)
120
+ )
121
+
122
+ const cache = new InMemoryCache({
123
+ addTypename: false
124
+ })
125
+
126
+ const client = new ApolloClient({
127
+ defaultOptions,
128
+ cache,
129
+ link: splitLink
130
+ })
131
+
132
+ const subscriptions: SubscriberData[] = []
133
+ Object.keys(subscriptionHandlers).forEach(async tag => {
134
+ if (!tag || !subscriptionHandlers[tag]) return
135
+
136
+ const scenarioName = subscriptionHandlers[tag]
137
+
138
+ // fetch a scenario
139
+ const selectedScenario = await getRepository(Scenario).findOne({
140
+ where: {
141
+ name: scenarioName
142
+ },
143
+ relations: ['steps', 'domain']
144
+ })
145
+
146
+ const subscription = client.subscribe({
147
+ query: gql`
148
+ subscription {
149
+ data(tag: "${tag}") {
150
+ tag
151
+ data
152
+ }
153
+ }
154
+ `
155
+ })
156
+
157
+ const subscriptionObserver = subscription.subscribe({
158
+ next: async data => {
159
+ debug('received pubsub msg.:', data?.data)
160
+ await this.runScenario(subscriptions, data?.data?.data)
161
+ },
162
+ error: error => {
163
+ ConnectionManager.logger.error(`(${connection.name}:${connection.endpoint}) subscription error`, error)
164
+ },
165
+ complete: () => {
166
+ ConnectionManager.logger.info(`(${connection.name}:${connection.endpoint}) subscription complete`)
167
+ }
168
+ })
169
+
170
+ ConnectionManager.logger.info(
171
+ `(${connection.name}:${connection.endpoint}) subscription closed flag: ${subscriptionObserver.closed}`
172
+ )
173
+
174
+ subscriptions.push({
175
+ tag,
176
+ scenario: selectedScenario,
177
+ subscriptionObserver
178
+ })
179
+ ConnectionManager.logger.info(`(${tag}:${scenarioName}) subscription closed flag: ${subscriptionObserver.closed}`)
180
+ })
181
+
182
+ client['subscriptions'] = subscriptions
183
+ ConnectionManager.addConnectionInstance(connection, client)
184
+
185
+ ConnectionManager.logger.info(
186
+ `operato-connector connection(${connection.name}:${connection.endpoint}) is connected`
187
+ )
188
+ }
189
+
190
+ async disconnect(connection: InputConnection) {
191
+ const client = ConnectionManager.getConnectionInstance(connection)
192
+ const subscriptions: SubscriberData[] = client['subscriptions']
193
+ subscriptions.forEach(subscription => subscription.subscriptionObserver.unsubscribe())
194
+ client.stop()
195
+ ConnectionManager.removeConnectionInstance(connection)
196
+
197
+ ConnectionManager.logger.info(`operato-connector connection(${connection.name}) is disconnected`)
198
+ }
199
+
200
+ async runScenario(subscriptions: SubscriberData[], variables: any): Promise<ScenarioInstance> {
201
+ const { domain, user } = this.context
202
+ const { tag } = variables
203
+
204
+ if (!tag) {
205
+ throw new Error(`tag is invalid - ${tag}`)
206
+ }
207
+
208
+ const scenario = subscriptions.find(subscription => subscription.tag === tag)?.scenario
209
+ if (!scenario) {
210
+ throw new Error(`scenario is not found - ${tag}`)
211
+ }
212
+
213
+ if (!(await checkUserHasRole(scenario.roleId, domain, user))) {
214
+ throw new Error(`Unauthorized! ${scenario.name} doesn't have required role.`)
215
+ }
216
+
217
+ /* create a scenario instance */
218
+ const instanceName = scenario.name + '-' + String(Date.now())
219
+ const instance = new ScenarioInstance(instanceName, scenario, {
220
+ user,
221
+ domain,
222
+ variables,
223
+ client: GraphqlLocalClient.client
224
+ })
225
+
226
+ // run scenario
227
+ await instance.run()
228
+ return instance
229
+ }
230
+
231
+ get parameterSpec() {
232
+ return [
233
+ {
234
+ type: 'string',
235
+ name: 'authKey',
236
+ label: 'auth-key'
237
+ },
238
+ {
239
+ type: 'string',
240
+ name: 'domain',
241
+ label: 'domain'
242
+ },
243
+ {
244
+ type: 'tag-scenarios',
245
+ name: 'subscriptionHandlers',
246
+ label: 'subscription-handlers'
247
+ }
248
+ ]
249
+ }
250
+
251
+ get taskPrefixes() {
252
+ return ['graphql']
253
+ }
254
+
255
+ get help() {
256
+ return 'integration/connector/operato-connector'
257
+ }
258
+
259
+ get description() {
260
+ return 'Operato Graphql Connector'
261
+ }
262
+ }
263
+
264
+ ConnectionManager.registerConnector('operato-connector', new OperatoConnector())
@@ -0,0 +1,218 @@
1
+ import { logger } from '@things-factory/env'
2
+
3
+ import { ConnectionManager } from '../connection-manager'
4
+ import { Connector } from '../types'
5
+ import { InputConnection } from '../../service/connection/connection-type'
6
+
7
+ try {
8
+ var oracledb = require('oracledb')
9
+ } catch (err) {
10
+ logger.error('oracledb module loading failed', err)
11
+ }
12
+
13
+ export class OracleConnector implements Connector {
14
+ async ready(connectionConfigs: InputConnection[]) {
15
+ await Promise.all(connectionConfigs.map(this.connect))
16
+
17
+ ConnectionManager.logger.info('oracle-connector connections are ready')
18
+ }
19
+
20
+ async recreatePool(connection: InputConnection) {
21
+ const {
22
+ name,
23
+ endpoint,
24
+ params: { user, password, database, poolMin, poolMax, poolIncrement },
25
+ domain
26
+ } = connection
27
+
28
+ if (!oracledb) {
29
+ throw new Error('oracledb module loading failed')
30
+ }
31
+
32
+ const poolAlias = `${domain.name}-${name}`
33
+ await oracledb.getPool(poolAlias).close(10)
34
+ await oracledb.createPool({
35
+ user,
36
+ password,
37
+ // when oracle not using default port must add connection string with port like localhost:port
38
+ connectString: `${endpoint.trim()}/${database}`,
39
+ poolMin,
40
+ poolMax,
41
+ poolIncrement,
42
+ poolAlias
43
+ })
44
+
45
+ ConnectionManager.logger.info(`Oracle Database(${connection.name}:${database}) at ${endpoint} recreated.`)
46
+ }
47
+
48
+ async connect(connection: InputConnection) {
49
+ const {
50
+ name,
51
+ endpoint,
52
+ params: { user, password, database, poolMin, poolMax, poolIncrement },
53
+ domain
54
+ } = connection
55
+
56
+ if (!oracledb) {
57
+ throw new Error('oracledb module loading failed')
58
+ }
59
+
60
+ const poolAlias = `${domain.name}-${name}`
61
+
62
+ var enableStatistics = true
63
+ const pool = await oracledb.createPool({
64
+ user,
65
+ password,
66
+ // when oracle not using default port must add connection string with port like localhost:port
67
+ connectString: `${endpoint.trim()}/${database}`,
68
+ poolMin,
69
+ poolMax,
70
+ poolIncrement,
71
+ poolAlias,
72
+ enableStatistics
73
+ })
74
+
75
+ ConnectionManager.addConnectionInstance(connection, {
76
+ query: async (query, params) => {
77
+ let dbConnection: any = {}
78
+ let taskResult: any = {}
79
+ try {
80
+ dbConnection = await oracledb.getConnection(poolAlias)
81
+
82
+ taskResult = (
83
+ await dbConnection.execute(query, params, {
84
+ outFormat: oracledb.OBJECT
85
+ })
86
+ ).rows
87
+ } catch (e) {
88
+ if (e.name === 'Error' && e.message.includes('NJS-040')) {
89
+ await this.recreatePool(connection)
90
+ }
91
+ throw e
92
+ } finally {
93
+ await dbConnection.close()
94
+ }
95
+ return taskResult
96
+ },
97
+ execute: async (procedure, params) => {
98
+ let dbConnection: any = {}
99
+ let taskResult: any = {}
100
+ try {
101
+ // TODO: need to check if this is available when procedure string is a common query.
102
+ procedure = `BEGIN
103
+ ${procedure}
104
+ END;`
105
+ dbConnection = await oracledb.getConnection(poolAlias)
106
+
107
+ // console.log('\n************* stat after get ****************')
108
+ // await oracledb.getPool(poolAlias).logStatistics()
109
+
110
+ const result = await dbConnection.execute(procedure, params, {
111
+ outFormat: oracledb.OBJECT
112
+ })
113
+
114
+ let paramKeys = Object.keys(params)
115
+
116
+ for (const paramKey of paramKeys) {
117
+ if (params[paramKey].dir === oracledb?.BIND_OUT) {
118
+ if (params[paramKey].type === oracledb?.CURSOR) {
119
+ const resultSetTemp = result.outBinds[paramKey]
120
+ taskResult[paramKey] = await resultSetTemp.getRows()
121
+ await resultSetTemp.close()
122
+ } else {
123
+ taskResult[paramKey] = result.outBinds[paramKey]
124
+ }
125
+ }
126
+ }
127
+ } catch (e) {
128
+ if (e.name === 'Error' && e.message.includes('NJS-040')) {
129
+ await this.recreatePool(connection)
130
+ }
131
+ throw e
132
+ } finally {
133
+ await dbConnection.close()
134
+
135
+ // console.log('\n************* stat after close ****************')
136
+ // await oracledb.getPool(poolAlias).logStatistics()
137
+ }
138
+ return taskResult
139
+ },
140
+ close: async () => {
141
+ await oracledb.getPool(poolAlias).close(10)
142
+ }
143
+ })
144
+
145
+ ConnectionManager.logger.info(`Oracle Database(${connection.name}:${database}) at ${endpoint} connected.`)
146
+ }
147
+
148
+ async disconnect(connection: InputConnection) {
149
+ var client = ConnectionManager.getConnectionInstance(connection)
150
+
151
+ try {
152
+ await client.close()
153
+ ConnectionManager.logger.info(`Oracle Database(${connection.name}) closed.`)
154
+ } catch (e) {
155
+ ConnectionManager.logger.error(e)
156
+ }
157
+
158
+ ConnectionManager.removeConnectionInstance(connection)
159
+ }
160
+
161
+ get parameterSpec() {
162
+ return [
163
+ {
164
+ type: 'string',
165
+ name: 'user',
166
+ label: 'user'
167
+ },
168
+ {
169
+ type: 'password',
170
+ name: 'password',
171
+ label: 'password'
172
+ },
173
+ {
174
+ type: 'string',
175
+ name: 'database',
176
+ placeholder: 'SID',
177
+ label: 'database'
178
+ },
179
+ {
180
+ type: 'number',
181
+ name: 'poolMin',
182
+ placeholder: 'minimum connection-pool size',
183
+ label: 'pool-min',
184
+ value: 0
185
+ },
186
+ {
187
+ type: 'number',
188
+ name: 'poolMax',
189
+ placeholder: 'maximum connection-pool size',
190
+ label: 'pool-max',
191
+ value: 4
192
+ },
193
+ {
194
+ type: 'number',
195
+ name: 'poolIncrement',
196
+ placeholder: 'connection incremental size',
197
+ label: 'pool-increment',
198
+ value: 1
199
+ }
200
+ ]
201
+ }
202
+
203
+ get taskPrefixes() {
204
+ return ['database', 'oracle']
205
+ }
206
+
207
+ get help() {
208
+ return 'integration/connector/oracle-connector'
209
+ }
210
+ }
211
+
212
+ ConnectionManager.registerConnector('oracle-connector', new OracleConnector())
213
+
214
+ // need reference:
215
+ // https://download.oracle.com/otn_software/mac/instantclient/193000/instantclient-basiclite-macos.x64-19.3.0.0.0dbru.zip
216
+ // https://node-oracledb.readthedocs.io/en/latest/index.html
217
+
218
+ // docker pull store/oracle/database-instantclient:12.2.0.1
@@ -0,0 +1,152 @@
1
+ import { logger } from '@things-factory/env'
2
+
3
+ import { ConnectionManager } from '../connection-manager'
4
+ import { Connector } from '../types'
5
+ import { InputConnection } from '../../service/connection/connection-type'
6
+
7
+ try {
8
+ var { Client, Pool } = require('pg')
9
+ } catch (err) {
10
+ logger.error('postgresql module loading failed', err)
11
+ }
12
+
13
+ export class PostgresqlConnector implements Connector {
14
+ async ready(connectionConfigs: InputConnection[]) {
15
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)))
16
+
17
+ ConnectionManager.logger.info('postgresql-connector connections are ready')
18
+ }
19
+
20
+ async connect(connection: InputConnection) {
21
+ const {
22
+ endpoint,
23
+ params: { user, password, database, maxPoolConnection, idleTimeoutMillis, connectionTimeoutMillis }
24
+ } = connection
25
+ const [host, port = 5432] = endpoint.split(':')
26
+
27
+ if (!Pool) {
28
+ throw new Error('postgresql module loading failed')
29
+ }
30
+
31
+ // create a new pool
32
+ const pool = new Pool({
33
+ user,
34
+ host,
35
+ database,
36
+ password,
37
+ port: Number(port),
38
+ max: maxPoolConnection || 10,
39
+ idleTimeoutMillis: idleTimeoutMillis || 30000,
40
+ connectionTimeoutMillis: connectionTimeoutMillis || 2000
41
+ })
42
+
43
+ pool.on('error', (err, client) => {
44
+ logger.error('unexpected error on postgres pool.', err)
45
+ })
46
+
47
+ try {
48
+ // try to connect to database to check the connection at initial time
49
+ const entryConnection = await pool.connect().catch(async e => {
50
+ ConnectionManager.logger.error(e)
51
+ throw e
52
+ })
53
+ entryConnection.release()
54
+
55
+ ConnectionManager.addConnectionInstance(connection, {
56
+ query: async (query, params) => {
57
+ try {
58
+ // get a connection from the pool
59
+ var client = await pool.connect().catch(async e => {
60
+ ConnectionManager.logger.error(e)
61
+ throw e
62
+ })
63
+
64
+ client?.on('error', async err => {
65
+ logger.error('unexpected error on postgres client.', err)
66
+ ConnectionManager.logger.error(err)
67
+ })
68
+
69
+ // try to query
70
+ var result = (await client.query(query, params)).rows
71
+ } catch (e) {
72
+ ConnectionManager.logger.error(e)
73
+ throw e
74
+ } finally {
75
+ // release the connection back to the pool
76
+ client?.release()
77
+ }
78
+ return result
79
+ },
80
+ close: async () => {
81
+ // close the pool
82
+ await pool?.end()
83
+ }
84
+ })
85
+
86
+ ConnectionManager.logger.info(`PostgresSQL Database(${connection.name}:${database}) at ${endpoint} connected.`)
87
+ } catch (e) {
88
+ ConnectionManager.logger.error(
89
+ `PostgresSQL Database(${connection.name}:${database}) at ${endpoint} not connected.`,
90
+ e
91
+ )
92
+ }
93
+ }
94
+
95
+ async disconnect(connection: InputConnection) {
96
+ var connectionInstance = ConnectionManager.getConnectionInstance(connection)
97
+ try {
98
+ await connectionInstance.close()
99
+ ConnectionManager.logger.info(`PostgresSQL Database(${connection.name}) closed.`)
100
+ } catch (e) {
101
+ ConnectionManager.logger.error(e)
102
+ }
103
+ ConnectionManager.removeConnectionInstance(connection)
104
+ }
105
+
106
+ get parameterSpec() {
107
+ return [
108
+ {
109
+ type: 'string',
110
+ name: 'user',
111
+ label: 'user'
112
+ },
113
+ {
114
+ type: 'password',
115
+ name: 'password',
116
+ label: 'password'
117
+ },
118
+ {
119
+ type: 'string',
120
+ name: 'database',
121
+ label: 'database'
122
+ },
123
+ {
124
+ type: 'number',
125
+ name: 'maxPoolConnection',
126
+ label: 'maxPoolConnection'
127
+ },
128
+ {
129
+ type: 'number',
130
+ name: 'idleTimeoutMillis',
131
+ label: 'idleTimeoutMillis',
132
+ placeHolder: 'milli-seconds'
133
+ },
134
+ {
135
+ type: 'number',
136
+ name: 'connectionTimeoutMillis',
137
+ label: 'connectionTimeoutMillis',
138
+ placeHolder: 'milli-seconds'
139
+ }
140
+ ]
141
+ }
142
+
143
+ get taskPrefixes() {
144
+ return ['database']
145
+ }
146
+
147
+ get help() {
148
+ return 'integration/connector/postgresql-connector'
149
+ }
150
+ }
151
+
152
+ ConnectionManager.registerConnector('postgresql-connector', new PostgresqlConnector())
@@ -0,0 +1,53 @@
1
+ import { ConnectionManager } from '../connection-manager'
2
+ import { Connector } from '../types'
3
+ import { InputConnection } from '../../service/connection/connection-type'
4
+ import { connectConnections, disconnectConnections } from '../edge-client'
5
+
6
+ /**
7
+ * This connector is a proxy connector installed on the edge server to manage connections from the host.
8
+ * It interacts with connections established on the edge server and provides synchronization functionality.
9
+ */
10
+ export class ProxyConnector implements Connector {
11
+ public static instance = new ProxyConnector()
12
+
13
+ async ready(connectionConfigs: InputConnection[]) {
14
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)))
15
+
16
+ ConnectionManager.logger.info('proxy-connector connections are ready')
17
+ }
18
+
19
+ async connect(connection: InputConnection) {
20
+ // TODO 원래 커넥션과 에지설정을 참고하여, 에지 서버로 CONNECT/DISCONNECT 명령을 보낸다.
21
+ const proxy = {
22
+ disconnect: async () => {
23
+ ConnectionManager.logger.info('[proxy-connector] trying disconnect')
24
+ await disconnectConnections([connection])
25
+ }
26
+ }
27
+
28
+ await connectConnections([connection])
29
+
30
+ ConnectionManager.addConnectionInstance(connection, proxy)
31
+
32
+ ConnectionManager.logger.info(`proxy-connector connection(${connection.name}:${connection.endpoint}) is connected`)
33
+ }
34
+
35
+ async disconnect(connection: InputConnection) {
36
+ const proxy = ConnectionManager.removeConnectionInstance(connection)
37
+ await proxy.disconnect()
38
+
39
+ ConnectionManager.logger.info(`proxy-connector connection(${connection.name}) is disconnected`)
40
+ }
41
+
42
+ get parameterSpec() {
43
+ return []
44
+ }
45
+
46
+ get taskPrefixes() {
47
+ return []
48
+ }
49
+
50
+ get description() {
51
+ return 'Operato Proxy Connector'
52
+ }
53
+ }