@things-factory/edge-client 8.0.0-beta.9 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/edge-client",
3
- "version": "8.0.0-beta.9",
3
+ "version": "8.0.0",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "license": "MIT",
@@ -22,10 +22,10 @@
22
22
  "clean": "npm run clean:server"
23
23
  },
24
24
  "dependencies": {
25
- "@things-factory/auth-base": "^8.0.0-beta.9",
26
- "@things-factory/integration-base": "^8.0.0-beta.9",
27
- "@things-factory/lock-client": "^8.0.0-beta.9",
28
- "@things-factory/shell": "^8.0.0-beta.9"
25
+ "@things-factory/auth-base": "^8.0.0",
26
+ "@things-factory/integration-base": "^8.0.0",
27
+ "@things-factory/lock-client": "^8.0.0",
28
+ "@things-factory/shell": "^8.0.0"
29
29
  },
30
- "gitHead": "86b1dfa26292926a2d5447fdc23bfa5a9e983245"
30
+ "gitHead": "07ef27d272dd9a067a9648ac7013748510556a18"
31
31
  }
@@ -0,0 +1,6 @@
1
+ import { pubsub } from '@things-factory/shell'
2
+ import { Connection } from '@things-factory/integration-base'
3
+
4
+ export async function connectConnections(connections: Connection[], context: ResolverContext): Promise<any> {
5
+ pubsub.publish('connect-connections', connections)
6
+ }
@@ -0,0 +1,6 @@
1
+ import { pubsub } from '@things-factory/shell'
2
+ import { Connection } from '@things-factory/integration-base'
3
+
4
+ export async function disconnectConnections(connections: Connection[], context: ResolverContext): Promise<any> {
5
+ pubsub.publish('disconnect-connections', connections)
6
+ }
@@ -0,0 +1,2 @@
1
+ export * from './run-task'
2
+ export * from './sync-connections'
@@ -0,0 +1,33 @@
1
+ import { pubsub } from '@things-factory/shell'
2
+ import { Context, Step, TaskHandler } from '@things-factory/integration-base'
3
+ import { requestLock, tryLock } from '@things-factory/lock-client'
4
+
5
+ export const runTask: TaskHandler = async (step: Step, scenarioContext: Context) => {
6
+ const { logger }: { logger: any } = scenarioContext
7
+
8
+ try {
9
+ const id = await requestLock()
10
+
11
+ pubsub.publish('run-task', {
12
+ id,
13
+ step,
14
+ context: scenarioContext
15
+ })
16
+
17
+ const result = await tryLock({ id })
18
+ const { out, logs, error } = result || {}
19
+
20
+ if (logger.transports?.length > 0) {
21
+ /* 강제로 시나리오 인스턴스가 종료된 경우에는 logger도 닫힌 상태가 된다. 이 경우에는 transports가 설정되지 않게된다. */
22
+ logs && logs.map(log => logger.log(log))
23
+ }
24
+
25
+ if (error) {
26
+ throw new Error(error)
27
+ }
28
+
29
+ return out
30
+ } catch (err) {
31
+ throw new Error(err)
32
+ }
33
+ }
@@ -0,0 +1,6 @@
1
+ import { pubsub } from '@things-factory/shell'
2
+ import { Connection } from '@things-factory/integration-base'
3
+
4
+ export async function syncConnections(connections: Connection[], context: ResolverContext): Promise<any> {
5
+ pubsub.publish('sync-connections', connections)
6
+ }
@@ -0,0 +1,45 @@
1
+ import { Connector, ConnectionManager, InputConnection } from '@things-factory/integration-base'
2
+
3
+ /**
4
+ * This connector only serves the role of a tag specifying that a particular step of the task will be performed on the Edge Server,
5
+ * and therefore, contains no special logic.
6
+ */
7
+ export class EdgeConnector implements Connector {
8
+ async ready(connectionConfigs: InputConnection[]) {
9
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)))
10
+
11
+ ConnectionManager.logger.info('edge-connector connections are ready')
12
+ }
13
+
14
+ async connect(connection: InputConnection) {
15
+ const edge = {}
16
+
17
+ ConnectionManager.addConnectionInstance(connection, edge)
18
+
19
+ ConnectionManager.logger.info(`edge-connector connection(${connection.name}:${connection.endpoint}) is connected`)
20
+ }
21
+
22
+ async disconnect(connection: InputConnection) {
23
+ ConnectionManager.removeConnectionInstance(connection)
24
+
25
+ ConnectionManager.logger.info(`edge-connector connection(${connection.name}) is disconnected`)
26
+ }
27
+
28
+ get parameterSpec() {
29
+ return []
30
+ }
31
+
32
+ get taskPrefixes() {
33
+ return [] // intentionally empty
34
+ }
35
+
36
+ get help() {
37
+ return 'integration/connector/edge-connector'
38
+ }
39
+
40
+ get description() {
41
+ return 'Operato Edge Connector'
42
+ }
43
+ }
44
+
45
+ ConnectionManager.registerConnector('edge-connector', new EdgeConnector())
@@ -0,0 +1 @@
1
+ import './connector/edge-connector'
@@ -0,0 +1,17 @@
1
+ export * from './service'
2
+
3
+ import { setEdgeClient } from '@things-factory/integration-base'
4
+ import { runTask } from './controllers/run-task'
5
+ import { syncConnections } from './controllers/sync-connections'
6
+ import { connectConnections } from './controllers/connect-connections'
7
+ import { disconnectConnections } from './controllers/disconnect-connections'
8
+ import './engine' // for registering edge-connector
9
+
10
+ process.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {
11
+ setEdgeClient({
12
+ handler: runTask,
13
+ syncConnections,
14
+ connectConnections,
15
+ disconnectConnections
16
+ })
17
+ })
@@ -0,0 +1,18 @@
1
+ import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
2
+
3
+ import { Appliance } from '@things-factory/auth-base'
4
+ import { releaseLock } from '@things-factory/lock-client'
5
+
6
+ import { RunTaskCallbackInput } from './edge-client-type'
7
+
8
+ @Resolver(Appliance)
9
+ export class EdgeClientMutation {
10
+ @Mutation(returns => Boolean, { description: 'To receive the result of runTask' })
11
+ async runTaskCallback(@Arg('result', type => RunTaskCallbackInput) result: RunTaskCallbackInput, @Ctx() context: ResolverContext): Promise<boolean> {
12
+ const { id, out, logs, error } = result
13
+
14
+ await releaseLock({ id, payload: { out, logs, error } })
15
+
16
+ return true
17
+ }
18
+ }
@@ -0,0 +1,241 @@
1
+ import { filter, pipe } from 'graphql-yoga'
2
+ import { Resolver, Subscription, Root, Arg } from 'type-graphql'
3
+
4
+ import { pubsub, getRepository, Domain } from '@things-factory/shell'
5
+ import { Appliance, User } from '@things-factory/auth-base'
6
+ import { ConnectionManager, Connection, Step } from '@things-factory/integration-base'
7
+
8
+ import { EdgeContext, RunTaskPayload } from './edge-client-type'
9
+
10
+ @Resolver(Appliance)
11
+ export class EdgeClientSubscription {
12
+ @Subscription(type => [Connection], {
13
+ subscribe: async ({ args, context, info }) => {
14
+ const { domain, user }: { domain: Domain; user: User } = context.state
15
+ const subdomain = domain?.subdomain
16
+
17
+ if (!domain) {
18
+ throw new Error('domain required')
19
+ }
20
+
21
+ if (!user.domains?.find(d => d.subdomain === subdomain) && !process.superUserGranted(domain, user)) {
22
+ throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)
23
+ }
24
+
25
+ const appliance =
26
+ user.reference &&
27
+ (await getRepository(Appliance).findOne({
28
+ where: {
29
+ id: user.reference
30
+ }
31
+ }))
32
+
33
+ if (!appliance) {
34
+ throw new Error('Appliance not found')
35
+ }
36
+
37
+ process.nextTick(async () => {
38
+ const connections = Object.values(ConnectionManager.getConnectionInstanceEntities(domain)).filter(
39
+ ({ edgeId }) => edgeId == appliance.id
40
+ )
41
+
42
+ if (connections.length > 0) {
43
+ pubsub.publish('sync-connections', connections)
44
+ }
45
+ })
46
+
47
+ return pipe(
48
+ pubsub.subscribe('sync-connections'),
49
+ filter(async (payload: Connection[]) => {
50
+ const connections = payload
51
+ const { edgeId } = connections[0]
52
+
53
+ if (connections.find(connection => connection.domain?.subdomain !== subdomain)) {
54
+ return false
55
+ }
56
+
57
+ if (!edgeId || appliance?.id !== edgeId) {
58
+ return false
59
+ }
60
+
61
+ return true
62
+ })
63
+ )
64
+ }
65
+ })
66
+ syncConnections(@Root() payload: Connection[]): Connection[] {
67
+ return payload.map(connection => {
68
+ return {
69
+ ...connection,
70
+ params: JSON.stringify(connection.params)
71
+ }
72
+ }) as any
73
+ }
74
+
75
+ @Subscription(type => [Connection], {
76
+ subscribe: async ({ args, context, info }) => {
77
+ const { domain, user }: { domain: Domain; user: User } = context.state
78
+ const subdomain = domain?.subdomain
79
+
80
+ if (!domain) {
81
+ throw new Error('domain required')
82
+ }
83
+
84
+ if (!user.domains?.find(d => d.subdomain === subdomain) && !process.superUserGranted(domain, user)) {
85
+ throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)
86
+ }
87
+
88
+ const appliance =
89
+ user.reference &&
90
+ (await getRepository(Appliance).findOne({
91
+ where: {
92
+ id: user.reference
93
+ }
94
+ }))
95
+
96
+ if (!appliance) {
97
+ throw new Error('Appliance not found')
98
+ }
99
+
100
+ return pipe(
101
+ pubsub.subscribe('connect-connections'),
102
+ filter(async (payload: Connection[]) => {
103
+ const connections = payload
104
+ const { edgeId } = connections[0]
105
+
106
+ if (connections.find(connection => connection.domain?.subdomain !== subdomain)) {
107
+ return false
108
+ }
109
+
110
+ if (!edgeId || appliance?.id !== edgeId) {
111
+ return false
112
+ }
113
+
114
+ return true
115
+ })
116
+ )
117
+ }
118
+ })
119
+ connectConnections(@Root() payload: Connection[]): Connection[] {
120
+ return payload.map(connection => {
121
+ return {
122
+ ...connection,
123
+ params: JSON.stringify(connection.params)
124
+ }
125
+ }) as any
126
+ }
127
+
128
+ @Subscription(type => [Connection], {
129
+ subscribe: async ({ args, context, info }) => {
130
+ const { domain, user }: { domain: Domain; user: User } = context.state
131
+ const subdomain = domain?.subdomain
132
+
133
+ if (!domain) {
134
+ throw new Error('domain required')
135
+ }
136
+
137
+ if (!user.domains?.find(d => d.subdomain === subdomain) && !process.superUserGranted(domain, user)) {
138
+ throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)
139
+ }
140
+
141
+ const appliance =
142
+ user.reference &&
143
+ (await getRepository(Appliance).findOne({
144
+ where: {
145
+ id: user.reference
146
+ }
147
+ }))
148
+
149
+ if (!appliance) {
150
+ throw new Error('Appliance not found')
151
+ }
152
+
153
+ return pipe(
154
+ pubsub.subscribe('disconnect-connections'),
155
+ filter(async (payload: Connection[]) => {
156
+ const connections = payload
157
+ const { edgeId } = connections[0]
158
+
159
+ if (connections.find(connection => connection.domain?.subdomain !== subdomain)) {
160
+ return false
161
+ }
162
+
163
+ if (!edgeId || appliance?.id !== edgeId) {
164
+ return false
165
+ }
166
+
167
+ return true
168
+ })
169
+ )
170
+ }
171
+ })
172
+ disconnectConnections(@Root() payload: Connection[]): Connection[] {
173
+ return payload.map(connection => {
174
+ return {
175
+ ...connection,
176
+ params: JSON.stringify(connection.params)
177
+ }
178
+ }) as any
179
+ }
180
+
181
+ @Subscription({
182
+ subscribe: async ({ args, context, info }) => {
183
+ const { domain, user }: { domain: Domain; user: User } = context.state
184
+
185
+ const subdomain = domain?.subdomain
186
+ if (!domain) {
187
+ throw new Error('domain required')
188
+ }
189
+
190
+ if (!user.domains?.find(d => d.subdomain === subdomain) && !process.superUserGranted(domain, user)) {
191
+ throw new Error(`domain(${subdomain}) is not working for user(${user.email}).`)
192
+ }
193
+
194
+ const appliance =
195
+ user.reference &&
196
+ (await getRepository(Appliance).findOne({
197
+ where: {
198
+ id: user.reference
199
+ }
200
+ }))
201
+
202
+ if (!appliance) {
203
+ throw new Error('Appliance not found')
204
+ }
205
+ return pipe(
206
+ pubsub.subscribe('run-task'),
207
+ filter(async (payload: { id: string; step: Step; context: EdgeContext }) => {
208
+ const { domainId, connection } = payload.step
209
+
210
+ if (domainId !== domain.id) {
211
+ return false
212
+ }
213
+
214
+ const { edgeId } = connection && ConnectionManager.getConnectionInstanceEntityByName(domain, connection)
215
+
216
+ if (!edgeId || appliance?.id !== edgeId) {
217
+ return false
218
+ }
219
+
220
+ return true
221
+ })
222
+ )
223
+ }
224
+ })
225
+ runTask(@Root() payload: RunTaskPayload): RunTaskPayload {
226
+ const { id, step, context } = payload
227
+ const { name, task, description, connection, params } = step
228
+
229
+ return {
230
+ id, // requestId
231
+ step: {
232
+ name,
233
+ task,
234
+ description,
235
+ connection,
236
+ params: JSON.stringify(params)
237
+ },
238
+ context
239
+ }
240
+ }
241
+ }
@@ -0,0 +1,46 @@
1
+ import { InputType, ObjectType, Field } from 'type-graphql'
2
+
3
+ import { Domain, ScalarObject } from '@things-factory/shell'
4
+ import { Step } from '@things-factory/integration-base'
5
+
6
+ @ObjectType()
7
+ export class EdgeContext {
8
+ @Field(type => Domain, { nullable: true })
9
+ domain: any
10
+
11
+ @Field(type => ScalarObject, { nullable: true })
12
+ data: any
13
+
14
+ @Field(type => ScalarObject, { nullable: true })
15
+ variables: any
16
+
17
+ @Field({ nullable: true })
18
+ lng: string
19
+ }
20
+
21
+ @ObjectType()
22
+ export class RunTaskPayload {
23
+ @Field()
24
+ id: string
25
+
26
+ @Field()
27
+ step: Step
28
+
29
+ @Field()
30
+ context: EdgeContext
31
+ }
32
+
33
+ @InputType()
34
+ export class RunTaskCallbackInput {
35
+ @Field({ nullable: true })
36
+ id: string
37
+
38
+ @Field(type => ScalarObject, { nullable: true })
39
+ out: any
40
+
41
+ @Field(type => ScalarObject, { nullable: true })
42
+ logs: any
43
+
44
+ @Field(type => ScalarObject, { nullable: true })
45
+ error: any
46
+ }
@@ -0,0 +1,6 @@
1
+ import { EdgeClientMutation } from './edge-client-mutation'
2
+ import { EdgeClientSubscription } from './edge-client-subscription'
3
+
4
+ export const entities = []
5
+ export const resolvers = [EdgeClientMutation, EdgeClientSubscription]
6
+ export const subscribers = []
@@ -0,0 +1,21 @@
1
+ /* EXPORT ENTITY TYPES */
2
+
3
+ /* IMPORT ENTITIES AND RESOLVERS */
4
+ import { entities as EdgeClientEntities, resolvers as EdgeClientResolvers, subscribers as EdgeClientSubscribers } from './appliance'
5
+
6
+ export const entities = [
7
+ /* ENTITIES */
8
+ ...EdgeClientEntities
9
+ ]
10
+
11
+ export const subscribers = [
12
+ /* SUBSCRIBERS */
13
+ ...EdgeClientSubscribers
14
+ ]
15
+
16
+ export const schema = {
17
+ resolverClasses: [
18
+ /* RESOLVER CLASSES */
19
+ ...EdgeClientResolvers
20
+ ]
21
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig-base.json",
3
+ "compilerOptions": {
4
+ "strict": false,
5
+ "module": "commonjs",
6
+ "outDir": "../dist-server",
7
+ "baseUrl": "./"
8
+ },
9
+ "include": ["./**/*"]
10
+ }