@aloma.io/integration-sdk 3.0.0-10
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 +9 -0
- package/examples/hello-world/Containerfile +17 -0
- package/examples/hello-world/entrypoint.sh +5 -0
- package/examples/hello-world/package.json +11 -0
- package/examples/hello-world/src/controller/index.js +14 -0
- package/examples/hello-world/src/index.js +29 -0
- package/examples/kitchen-sink/01-hello-world.js +26 -0
- package/examples/kitchen-sink/02-config.js +54 -0
- package/examples/kitchen-sink/03-oauth.js +36 -0
- package/index.js +3 -0
- package/package.json +44 -0
- package/src/builder/index.mts +64 -0
- package/src/builder/runtime-context.mts +56 -0
- package/src/builder/transform/index.mts +87 -0
- package/src/controller/index.mts +58 -0
- package/src/index.mts +2 -0
- package/src/internal/dispatcher/index.cjs +189 -0
- package/src/internal/index.cjs +547 -0
- package/src/internal/util/jwe/cli.cjs +14 -0
- package/src/internal/util/jwe/index.cjs +69 -0
- package/src/internal/websocket/config.cjs +103 -0
- package/src/internal/websocket/connection/constants.cjs +25 -0
- package/src/internal/websocket/connection/index.cjs +70 -0
- package/src/internal/websocket/connection/registration.cjs +40 -0
- package/src/internal/websocket/index.cjs +46 -0
- package/src/internal/websocket/transport/durable.cjs +71 -0
- package/src/internal/websocket/transport/index.cjs +183 -0
- package/src/internal/websocket/transport/packet.cjs +54 -0
- package/src/internal/websocket/transport/processor.cjs +66 -0
- package/tsconfig.json +27 -0
package/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
FROM node:16-alpine3.18
|
2
|
+
|
3
|
+
ENV NODE_ENV production
|
4
|
+
|
5
|
+
WORKDIR /connector/
|
6
|
+
|
7
|
+
COPY ./package.json ./
|
8
|
+
COPY ./entrypoint.sh ./
|
9
|
+
COPY ./src ./src
|
10
|
+
|
11
|
+
RUN set -e; adduser -S -u 1111 connector
|
12
|
+
|
13
|
+
RUN set -e; apk add --no-cache git; yarn config set --home enableTelemetry 0; chmod 755 /connector/entrypoint.sh; cd /connector/; yarn install; rm -rf package.json; rm -rf yarn.lock;
|
14
|
+
|
15
|
+
USER connector
|
16
|
+
|
17
|
+
ENTRYPOINT ["/connector/entrypoint.sh"]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
const {Connector} = require('@aloma-io/integration-sdk');
|
2
|
+
const {Controller} = require('./controller');
|
3
|
+
|
4
|
+
const connector = new Connector({id: 'TODO your connector id', version: '1.0.0'});
|
5
|
+
const controller = new Controller();
|
6
|
+
|
7
|
+
connector.configure()
|
8
|
+
// types are the calls available in the step, these are typescript definitions
|
9
|
+
.types
|
10
|
+
(`
|
11
|
+
declare function hello(arg: {}): any;
|
12
|
+
`)
|
13
|
+
// resolvers hold the calls
|
14
|
+
.resolvers
|
15
|
+
({
|
16
|
+
hello: async (args) =>
|
17
|
+
{
|
18
|
+
return controller.hello(args);
|
19
|
+
},
|
20
|
+
})
|
21
|
+
// main will be called once the connector is connected
|
22
|
+
.main(({newTask, config}) =>
|
23
|
+
{
|
24
|
+
// the config might have changed, so the connector should be able to deal with multiple setConfigs over it's lifetime
|
25
|
+
controller.setConfig({newTask, config});
|
26
|
+
});
|
27
|
+
|
28
|
+
// start the connector
|
29
|
+
connector.run();
|
@@ -0,0 +1,26 @@
|
|
1
|
+
const {Connector} = require('@aloma-io/integration-sdk');
|
2
|
+
|
3
|
+
const connector = new Connector({id: 'TODO your connector id', version: '1.0.0'});
|
4
|
+
|
5
|
+
connector.configure()
|
6
|
+
// types are the calls available in the step, these are typescript definitions
|
7
|
+
.types
|
8
|
+
(`
|
9
|
+
declare function hello(arg: {}): any;
|
10
|
+
`)
|
11
|
+
// resolvers hold the calls
|
12
|
+
.resolvers
|
13
|
+
({
|
14
|
+
hello: async (args) =>
|
15
|
+
{
|
16
|
+
return "world";
|
17
|
+
},
|
18
|
+
})
|
19
|
+
// main will be called once the connector is connected
|
20
|
+
.main(({newTask, config}) =>
|
21
|
+
{
|
22
|
+
// the config might have changed, so the connector should be able to deal with multiple setConfigs over it's lifetime
|
23
|
+
});
|
24
|
+
|
25
|
+
// start the connector
|
26
|
+
connector.run();
|
@@ -0,0 +1,54 @@
|
|
1
|
+
const {Connector} = require('@aloma-io/integration-sdk');
|
2
|
+
|
3
|
+
const connector = new Connector({id: 'TODO your connector id', version: '1.0.0'});
|
4
|
+
|
5
|
+
connector.configure()
|
6
|
+
// these config fields can be configured from the aloma ui. these require a private and a public key which the connector will generate on start
|
7
|
+
.config({fields:
|
8
|
+
{
|
9
|
+
// this is then available from config.type
|
10
|
+
type: {
|
11
|
+
// name and placeholder for ui configuration
|
12
|
+
name: 'Database Type',
|
13
|
+
placeholder: 'e.g. mysql, pg, tedious',
|
14
|
+
// type
|
15
|
+
type: 'line',
|
16
|
+
// this is an unencrypted value, by default everything is encrypted
|
17
|
+
plain: true
|
18
|
+
},
|
19
|
+
user: {
|
20
|
+
name: 'User',
|
21
|
+
placeholder: 'e.g. john',
|
22
|
+
type: 'line',
|
23
|
+
plain: true,
|
24
|
+
// this is marked as optional in the ui, so does not have to be provided, everything else is mandatory
|
25
|
+
optional: true
|
26
|
+
},
|
27
|
+
// this is encrypted in the ui via public key, can only be decrypted by the connector having the private key
|
28
|
+
password: {
|
29
|
+
name: 'Password',
|
30
|
+
placeholder: 'e.g. x3gsadg',
|
31
|
+
type: 'line',
|
32
|
+
optional: true
|
33
|
+
},
|
34
|
+
|
35
|
+
}})
|
36
|
+
.types
|
37
|
+
(`
|
38
|
+
declare function hello(arg: {}): any;
|
39
|
+
`)
|
40
|
+
.resolvers
|
41
|
+
({
|
42
|
+
hello: async (args) =>
|
43
|
+
{
|
44
|
+
return "world";
|
45
|
+
},
|
46
|
+
})
|
47
|
+
.main(({newTask, config}) =>
|
48
|
+
{
|
49
|
+
console.log(config.type)
|
50
|
+
console.log(config.password)
|
51
|
+
console.log(config.user)
|
52
|
+
});
|
53
|
+
|
54
|
+
connector.run();
|
@@ -0,0 +1,36 @@
|
|
1
|
+
const {Connector} = require('@aloma-io/integration-sdk');
|
2
|
+
|
3
|
+
const connector = new Connector({id: 'TODO your connector id', version: '1.0.0'});
|
4
|
+
|
5
|
+
connector.configure()
|
6
|
+
// these require a private and a public key which the connector will generate on start
|
7
|
+
// after a connector is added to a workspace it can be connected from the aloma ui
|
8
|
+
.oauth
|
9
|
+
({
|
10
|
+
// the authorization url, the placeholders {{clientId}}, {{redirectURI}}, {{scope}} will be filled in by aloma
|
11
|
+
authorizationURL: 'https://github.com/login/oauth/authorize?client_id={{clientId}}&redirect_uri={{redirectURI}}&scope={{scope}}&allow_signup=false',
|
12
|
+
tokenURL: 'https://github.com/login/oauth/access_token',
|
13
|
+
clientId: 'clientId',
|
14
|
+
clientSecret: 'clientSecret',
|
15
|
+
scope: 'repo, user:email'
|
16
|
+
})
|
17
|
+
.types
|
18
|
+
(`
|
19
|
+
declare function hello(arg: {}): any;
|
20
|
+
`)
|
21
|
+
.resolvers
|
22
|
+
({
|
23
|
+
hello: async (args) =>
|
24
|
+
{
|
25
|
+
return "world";
|
26
|
+
},
|
27
|
+
})
|
28
|
+
.main(({newTask, config, oauth}) =>
|
29
|
+
{
|
30
|
+
// one can access oauth.accessToken()
|
31
|
+
// one can get a fetch client: oauth.getClient()
|
32
|
+
// the client supports retries and will also do an automatic token refresh if a refresh_token is provided
|
33
|
+
// oauth.getClient().fetch({url, options = {method: 'POST', headers: {}, body: ''})
|
34
|
+
});
|
35
|
+
|
36
|
+
connector.run();
|
package/index.js
ADDED
package/package.json
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
{
|
2
|
+
"name": "@aloma.io/integration-sdk",
|
3
|
+
"version": "3.0.0-10",
|
4
|
+
"description": "",
|
5
|
+
"author": "aloma.io",
|
6
|
+
"license": "Apache-2.0",
|
7
|
+
"type": "module",
|
8
|
+
"scripts": {
|
9
|
+
"dev": "./node_modules/typescript/bin/tsc --watch",
|
10
|
+
"build": "./node_modules/typescript/bin/tsc",
|
11
|
+
"test": "./node_modules/mocha/bin/_mocha --recursive",
|
12
|
+
"format": "yarn prettier --write src/"
|
13
|
+
},
|
14
|
+
"main": "./build/index.js",
|
15
|
+
"exports": {
|
16
|
+
".": {
|
17
|
+
"import": "./build/index.mjs",
|
18
|
+
"require": "./build/index.js"
|
19
|
+
},
|
20
|
+
"./build/*": "./build/*",
|
21
|
+
"./package": "./package.json",
|
22
|
+
"./package.json": "./package.json"
|
23
|
+
},
|
24
|
+
"dependencies": {
|
25
|
+
"@paralleldrive/cuid2": "^2",
|
26
|
+
"dotenv": "*",
|
27
|
+
"express": "^4",
|
28
|
+
"jose": "^4",
|
29
|
+
"node-fetch": "^2",
|
30
|
+
"prom-client": "^14",
|
31
|
+
"ws": "^8"
|
32
|
+
},
|
33
|
+
"optionalDependencies": {
|
34
|
+
"bufferutil": "^4",
|
35
|
+
"utf-8-validate": "^6"
|
36
|
+
},
|
37
|
+
"devDependencies": {
|
38
|
+
"@ts-ast-parser/core": "^0",
|
39
|
+
"@types/node": "^18",
|
40
|
+
"mocha": "^10",
|
41
|
+
"prettier": "^2",
|
42
|
+
"typescript": "^5"
|
43
|
+
}
|
44
|
+
}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
import fs from 'node:fs';
|
2
|
+
import parseTypes from './transform/index.mjs';
|
3
|
+
import RuntimeContext from './runtime-context.mjs';
|
4
|
+
import {fileURLToPath} from 'node:url';
|
5
|
+
import path from 'node:path';
|
6
|
+
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
8
|
+
|
9
|
+
const notEmpty = (what, name) => {
|
10
|
+
if (!what?.trim()) throw new Error(`${name} cannot be empty`);
|
11
|
+
|
12
|
+
return what;
|
13
|
+
};
|
14
|
+
|
15
|
+
export class Builder {
|
16
|
+
private data: any = {controller: './build/controller/.controller-for-types.mts'};
|
17
|
+
|
18
|
+
config(arg: any): Builder {
|
19
|
+
this.data.config = arg;
|
20
|
+
|
21
|
+
return this;
|
22
|
+
}
|
23
|
+
|
24
|
+
options(arg: any): Builder {
|
25
|
+
this.data.options = arg;
|
26
|
+
|
27
|
+
return this;
|
28
|
+
}
|
29
|
+
|
30
|
+
auth(arg: any): Builder {
|
31
|
+
this.data.auth = arg;
|
32
|
+
return this;
|
33
|
+
}
|
34
|
+
|
35
|
+
async build(): Promise<RuntimeContext> {
|
36
|
+
await this.parsePackageJson();
|
37
|
+
await this.discoverTypes();
|
38
|
+
|
39
|
+
// @ts-ignore
|
40
|
+
const Controller = (await import(__dirname + '/../../../../../build/controller/index.mjs')).default;
|
41
|
+
|
42
|
+
return new RuntimeContext(new Controller(), this.data);
|
43
|
+
}
|
44
|
+
|
45
|
+
private async parsePackageJson()
|
46
|
+
{
|
47
|
+
const data = this.data;
|
48
|
+
|
49
|
+
const packageJson = JSON.parse(fs.readFileSync(__dirname + '/../../../../../package.json', {encoding: 'utf-8'}));
|
50
|
+
|
51
|
+
notEmpty(data.id = packageJson.connectorId, 'id');
|
52
|
+
notEmpty(data.version= packageJson.version, 'version');
|
53
|
+
}
|
54
|
+
|
55
|
+
private async discoverTypes() {
|
56
|
+
notEmpty(this.data.controller, 'controller');
|
57
|
+
|
58
|
+
const content = fs.readFileSync(this.data.controller);
|
59
|
+
const {text, methods} = parseTypes(this.data.controller);
|
60
|
+
|
61
|
+
this.data.types = text;
|
62
|
+
this.data.methods = methods;
|
63
|
+
}
|
64
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import {AbstractController} from '../controller/index.mjs';
|
2
|
+
import {Connector} from '../internal/index.cjs';
|
3
|
+
|
4
|
+
export default class RuntimeContext {
|
5
|
+
constructor(private controller: AbstractController, private data: any) {}
|
6
|
+
|
7
|
+
async start(): Promise<void> {
|
8
|
+
const controller = this.controller;
|
9
|
+
|
10
|
+
if (!(controller instanceof AbstractController)) throw new Error('the controller needs to extend AbstractController');
|
11
|
+
const data:any = this.data;
|
12
|
+
|
13
|
+
const connector = new Connector({
|
14
|
+
id: data.id,
|
15
|
+
version: data.version,
|
16
|
+
name: `${data.id}/${data.version}`,
|
17
|
+
});
|
18
|
+
|
19
|
+
console.log('setting config', data.config)
|
20
|
+
|
21
|
+
const configuration = connector.configure().config(data.config || {});
|
22
|
+
|
23
|
+
const resolvers: any = {};
|
24
|
+
const methods: string[] = [...data.methods, '__endpoint', '__configQuery', '__default'];
|
25
|
+
|
26
|
+
methods.forEach((method) => {
|
27
|
+
resolvers[method] = async (args) => {
|
28
|
+
if (!methods.includes(method)) throw new Error(`${method} not found`);
|
29
|
+
|
30
|
+
return controller[method](args);
|
31
|
+
};
|
32
|
+
});
|
33
|
+
|
34
|
+
configuration
|
35
|
+
|
36
|
+
.types(data.types)
|
37
|
+
.resolvers(resolvers);
|
38
|
+
|
39
|
+
|
40
|
+
if (data.options?.endpoint?.enabled)
|
41
|
+
{
|
42
|
+
configuration.endpoint((arg) => controller.__endpoint(arg));
|
43
|
+
}
|
44
|
+
|
45
|
+
configuration.main(async ({newTask, updateTask, config, oauth, getClient}) => {
|
46
|
+
try {
|
47
|
+
await controller._doStop();
|
48
|
+
await controller._doStart(config, oauth, newTask, updateTask, getClient);
|
49
|
+
} catch (e) {
|
50
|
+
console.log(e);
|
51
|
+
}
|
52
|
+
});
|
53
|
+
|
54
|
+
connector.run();
|
55
|
+
}
|
56
|
+
}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import {parseFromFiles} from '@ts-ast-parser/core';
|
2
|
+
|
3
|
+
const transform = (meta: any) => {
|
4
|
+
if (!meta?.length) throw new Error('metadata is empty');
|
5
|
+
meta = meta[0];
|
6
|
+
|
7
|
+
if (meta.getDeclarations()?.length !== 1) {
|
8
|
+
throw new Error('connector file needs to export default class');
|
9
|
+
}
|
10
|
+
|
11
|
+
const methods = {};
|
12
|
+
const decl = meta.getDeclarations()[0];
|
13
|
+
|
14
|
+
const members = decl.getMethods().filter((member: any) => {
|
15
|
+
return !(
|
16
|
+
member.isStatic() ||
|
17
|
+
member.isInherited() ||
|
18
|
+
member.getKind() !== 'Method' ||
|
19
|
+
member.getModifier() !== 'public' ||
|
20
|
+
member.getName().startsWith('_')
|
21
|
+
);
|
22
|
+
});
|
23
|
+
|
24
|
+
const text = members
|
25
|
+
.map((member: any) => {
|
26
|
+
methods[member.getName()] = true;
|
27
|
+
|
28
|
+
return member
|
29
|
+
.getSignatures()
|
30
|
+
.map((sig: any) => {
|
31
|
+
const docs = sig.getJSDoc().serialize() || [];
|
32
|
+
const desc = docs.find((what: any) => what.kind === 'description')?.value;
|
33
|
+
|
34
|
+
const paramDocs =
|
35
|
+
docs
|
36
|
+
.filter((what: any) => what.kind === 'param')
|
37
|
+
.map((what: any) => {
|
38
|
+
return ` * @param {${what.value.type}} ${what.value.name} - ${what.value.description || ''}`;
|
39
|
+
})
|
40
|
+
.join('\n') || ' *';
|
41
|
+
|
42
|
+
const params = sig
|
43
|
+
.getParameters()
|
44
|
+
.map((param: any) => {
|
45
|
+
const serialized = param.serialize();
|
46
|
+
|
47
|
+
switch (!!param.isNamed()) {
|
48
|
+
case true:
|
49
|
+
const tmp = param
|
50
|
+
.getNamedElements()
|
51
|
+
.map((p) => {
|
52
|
+
const defaultVal = p.default != null ? ' = ' + p.default : '';
|
53
|
+
|
54
|
+
return `${p.name}${defaultVal}`;
|
55
|
+
})
|
56
|
+
.join('; ');
|
57
|
+
return `{${tmp}}: ${param.getType().text}`;
|
58
|
+
case false:
|
59
|
+
return `${param.getName()}: ${param.getType().text}`;
|
60
|
+
}
|
61
|
+
})
|
62
|
+
.join(', ');
|
63
|
+
|
64
|
+
const retVal = sig
|
65
|
+
.getReturnType()
|
66
|
+
.type.text.replace(/^Promise</, '')
|
67
|
+
.replace(/>$/, '');
|
68
|
+
|
69
|
+
return `
|
70
|
+
/**
|
71
|
+
* ${desc || ''}
|
72
|
+
*
|
73
|
+
${paramDocs}
|
74
|
+
**/
|
75
|
+
declare function ${member.getName()}(${params}): ${retVal};
|
76
|
+
`;
|
77
|
+
})
|
78
|
+
.join('\n');
|
79
|
+
})
|
80
|
+
.join('');
|
81
|
+
|
82
|
+
return {text, methods: Object.keys(methods)};
|
83
|
+
};
|
84
|
+
|
85
|
+
export default (path: string) => {
|
86
|
+
return transform(parseFromFiles([path]));
|
87
|
+
};
|
@@ -0,0 +1,58 @@
|
|
1
|
+
export abstract class AbstractController {
|
2
|
+
protected config;
|
3
|
+
protected client;
|
4
|
+
|
5
|
+
protected async start(): Promise<void> {}
|
6
|
+
|
7
|
+
protected async stop(): Promise<void> {}
|
8
|
+
|
9
|
+
protected configQuery(arg: any): Promise<any> {
|
10
|
+
return Promise.resolve({});
|
11
|
+
}
|
12
|
+
|
13
|
+
protected fallback(arg: any): Promise<any> {
|
14
|
+
throw new Error('method not found');
|
15
|
+
}
|
16
|
+
|
17
|
+
protected endpoint(arg: any): Promise<any> {
|
18
|
+
throw new Error('method not found');
|
19
|
+
}
|
20
|
+
|
21
|
+
protected async newTask(name: string, data: any): Promise<string> {
|
22
|
+
throw new Error('not implemented');
|
23
|
+
}
|
24
|
+
|
25
|
+
protected getClient({baseUrl}: {baseUrl: string}): Promise<any> {
|
26
|
+
throw new Error('not implemented');
|
27
|
+
}
|
28
|
+
|
29
|
+
protected async updateTask(name: string, data: any): Promise<string> {
|
30
|
+
throw new Error('not implemented');
|
31
|
+
}
|
32
|
+
|
33
|
+
async __endpoint(arg: any): Promise<any | null> {
|
34
|
+
return this.endpoint(arg);
|
35
|
+
}
|
36
|
+
|
37
|
+
async __configQuery(arg: any): Promise<any | null> {
|
38
|
+
return this.configQuery(arg);
|
39
|
+
}
|
40
|
+
|
41
|
+
async __default(arg: any): Promise<any | null> {
|
42
|
+
return this.fallback(arg);
|
43
|
+
}
|
44
|
+
|
45
|
+
async _doStart(config: any, client: any, newTask: any, updateTask: any, getClient: any): Promise<void> {
|
46
|
+
this.config = config;
|
47
|
+
this.client = client;
|
48
|
+
this.newTask = newTask;
|
49
|
+
this.updateTask = updateTask;
|
50
|
+
this.getClient = getClient;
|
51
|
+
|
52
|
+
await this.start();
|
53
|
+
}
|
54
|
+
|
55
|
+
async _doStop(): Promise<void> {
|
56
|
+
await this.stop();
|
57
|
+
}
|
58
|
+
}
|
package/src/index.mts
ADDED