@metacall/protocol 0.1.5

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.
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ /*
3
+
4
+ * About File:
5
+
6
+ this is just a client that implements all the rest API from the FaaS, so each function it contains is an endpoint in the FaaS for deploying and similar
7
+
8
+ refresh: updates the auth token
9
+ validate: validates the auth token
10
+ deployEnabled: checks if you're able to deploy
11
+ listSubscriptions: gives you a list of the subscription available
12
+ inspect: gives you are deploys with it's endpoints
13
+ upload: uploads a zip (package) into the faas
14
+ deploy: deploys the previously uploaded zip into the faas
15
+ deployDelete: deletes the deploy and the zip
16
+
17
+ */
18
+ var __importDefault = (this && this.__importDefault) || function (mod) {
19
+ return (mod && mod.__esModule) ? mod : { "default": mod };
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.isProtocolError = void 0;
23
+ const axios_1 = __importDefault(require("axios"));
24
+ const form_data_1 = __importDefault(require("form-data"));
25
+ const deployment_1 = require("./deployment");
26
+ const isProtocolError = (err) => axios_1.default.isAxiosError(err);
27
+ exports.isProtocolError = isProtocolError;
28
+ exports.default = (token, baseURL) => {
29
+ const api = {
30
+ refresh: () => axios_1.default
31
+ .get(baseURL + '/api/account/refresh-token', {
32
+ headers: { Authorization: 'jwt ' + token }
33
+ })
34
+ .then(res => res.data),
35
+ validate: () => axios_1.default
36
+ .get(baseURL + '/validate', {
37
+ headers: { Authorization: 'jwt ' + token }
38
+ })
39
+ .then(res => res.data),
40
+ deployEnabled: () => axios_1.default
41
+ .get(baseURL + '/api/account/deploy-enabled', {
42
+ headers: { Authorization: 'jwt ' + token }
43
+ })
44
+ .then(res => res.data),
45
+ listSubscriptions: async () => {
46
+ const res = await axios_1.default.get(baseURL + '/api/billing/list-subscriptions', {
47
+ headers: { Authorization: 'jwt ' + token }
48
+ });
49
+ const subscriptions = {};
50
+ for (const id of res.data) {
51
+ if (subscriptions[id] === undefined) {
52
+ subscriptions[id] = 1;
53
+ }
54
+ else {
55
+ ++subscriptions[id];
56
+ }
57
+ }
58
+ return subscriptions;
59
+ },
60
+ inspect: async () => axios_1.default
61
+ .get(baseURL + '/api/inspect', {
62
+ headers: { Authorization: 'jwt ' + token }
63
+ })
64
+ .then(res => res.data),
65
+ upload: async (name, blob, jsons = [], runners = []) => {
66
+ const fd = new form_data_1.default();
67
+ fd.append('id', name);
68
+ fd.append('type', 'application/x-zip-compressed');
69
+ fd.append('jsons', JSON.stringify(jsons));
70
+ fd.append('runners', JSON.stringify(runners));
71
+ fd.append('raw', blob, {
72
+ filename: 'blob',
73
+ contentType: 'application/x-zip-compressed'
74
+ });
75
+ const res = await axios_1.default.post(baseURL + '/api/package/create', fd, {
76
+ headers: {
77
+ Authorization: 'jwt ' + token,
78
+ ...fd.getHeaders()
79
+ }
80
+ });
81
+ return res.data;
82
+ },
83
+ add: (url, branch, jsons = []) => axios_1.default
84
+ .post(baseURL + '/api/repository/add', {
85
+ url,
86
+ branch,
87
+ jsons
88
+ }, {
89
+ headers: { Authorization: 'jwt ' + token }
90
+ })
91
+ .then((res) => res.data),
92
+ branchList: (url) => axios_1.default
93
+ .post(baseURL + '/api/repository/branchlist', {
94
+ url
95
+ }, {
96
+ headers: { Authorization: 'jwt ' + token }
97
+ })
98
+ .then((res) => res.data),
99
+ deploy: (name, env, plan, resourceType, release = Date.now().toString(16), version = 'v1') => axios_1.default
100
+ .post(baseURL + '/api/deploy/create', {
101
+ resourceType,
102
+ suffix: name,
103
+ release,
104
+ env,
105
+ plan,
106
+ version
107
+ }, {
108
+ headers: { Authorization: 'jwt ' + token }
109
+ })
110
+ .then(res => res.data),
111
+ deployDelete: (prefix, suffix, version = 'v1') => axios_1.default
112
+ .post(baseURL + '/api/deploy/delete', {
113
+ prefix,
114
+ suffix,
115
+ version
116
+ }, {
117
+ headers: { Authorization: 'jwt ' + token }
118
+ })
119
+ .then(res => res.data),
120
+ logs: (container, type = deployment_1.LogType.Deploy, suffix, prefix, version = 'v1') => axios_1.default
121
+ .post(baseURL + '/api/deploy/logs', {
122
+ container,
123
+ type,
124
+ suffix,
125
+ prefix,
126
+ version
127
+ }, {
128
+ headers: { Authorization: 'jwt ' + token }
129
+ })
130
+ .then(res => res.data),
131
+ fileList: (url, branch) => axios_1.default
132
+ .post(baseURL + '/api/repository/filelist', {
133
+ url,
134
+ branch
135
+ }, {
136
+ headers: { Authorization: 'jwt ' + token }
137
+ })
138
+ .then(res => res.data['files'])
139
+ };
140
+ return api;
141
+ };
@@ -0,0 +1 @@
1
+ export declare const expiresIn: (token: string) => number;
package/dist/token.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.expiresIn = void 0;
7
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
8
+ const expiresIn = (token) => {
9
+ const decoded = jsonwebtoken_1.default.decode(token);
10
+ if (typeof decoded === 'string') {
11
+ return 0;
12
+ }
13
+ const now = Date.now() / 1000;
14
+ return new Date(((decoded === null || decoded === void 0 ? void 0 : decoded['exp']) || now) * 1000).getTime() - now * 1000;
15
+ };
16
+ exports.expiresIn = expiresIn;
package/package.json ADDED
@@ -0,0 +1,110 @@
1
+ {
2
+ "name": "@metacall/protocol",
3
+ "version": "0.1.5",
4
+ "description": "Tool for deploying into MetaCall FaaS platform.",
5
+ "exports": {
6
+ "./*": "./dist/*.js",
7
+ "./package.json": "./package.json"
8
+ },
9
+ "typesVersions": {
10
+ "*": {
11
+ "*": [
12
+ "dist/*.d.ts"
13
+ ]
14
+ }
15
+ },
16
+ "scripts": {
17
+ "test": "npm run --silent build && mocha dist/test",
18
+ "unit": "npm run --silent test -- --ignore **/integration**",
19
+ "prepublishOnly": "npm run --silent build",
20
+ "build": "npm run --silent lint && tsc",
21
+ "postinstall": "npm run build",
22
+ "lint": "eslint . --ignore-pattern dist",
23
+ "fix": "eslint . --ignore-pattern dist --fix",
24
+ "doc": "node dist/doc/index.js"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/metacall/protocol.git"
29
+ },
30
+ "keywords": [
31
+ "MetaCall",
32
+ "FaaS",
33
+ "protocol"
34
+ ],
35
+ "author": "Thomas Rory Gummerson <thomas@gummerson.no> (https://trgwii.no/)",
36
+ "contributors": [
37
+ "Vicente Eduardo Ferrer Garcia <vic798@gmail.com> (https://metacall.io/)"
38
+ ],
39
+ "license": "Apache-2.0",
40
+ "bugs": {
41
+ "url": "https://github.com/metacall/protocol/issues"
42
+ },
43
+ "homepage": "https://github.com/metacall/protocol#readme",
44
+ "prettier": {
45
+ "tabWidth": 4,
46
+ "useTabs": true,
47
+ "singleQuote": true,
48
+ "trailingComma": "none",
49
+ "arrowParens": "avoid"
50
+ },
51
+ "eslintConfig": {
52
+ "env": {
53
+ "es6": true,
54
+ "node": true
55
+ },
56
+ "plugins": [
57
+ "@typescript-eslint",
58
+ "eslint-plugin-tsdoc"
59
+ ],
60
+ "extends": [
61
+ "eslint:recommended",
62
+ "prettier",
63
+ "plugin:@typescript-eslint/eslint-recommended",
64
+ "plugin:@typescript-eslint/recommended",
65
+ "plugin:@typescript-eslint/recommended-requiring-type-checking",
66
+ "prettier/@typescript-eslint",
67
+ "plugin:prettier/recommended"
68
+ ],
69
+ "globals": {
70
+ "Atomics": "readonly",
71
+ "SharedArrayBuffer": "readonly"
72
+ },
73
+ "parser": "@typescript-eslint/parser",
74
+ "parserOptions": {
75
+ "ecmaVersion": 2020,
76
+ "sourceType": "module",
77
+ "project": "./tsconfig.json"
78
+ },
79
+ "rules": {
80
+ "tsdoc/syntax": "warn"
81
+ }
82
+ },
83
+ "dependencies": {
84
+ "@types/ignore-walk": "^4.0.0",
85
+ "@types/jsonwebtoken": "^8.5.8",
86
+ "axios": "^0.21.0",
87
+ "form-data": "^3.0.0",
88
+ "ignore-walk": "^3.0.4",
89
+ "jsonwebtoken": "^8.5.1"
90
+ },
91
+ "devDependencies": {
92
+ "@types/express": "^4.17.13",
93
+ "@types/mocha": "^8.2.2",
94
+ "@types/node": "^14.14.7",
95
+ "@types/swagger-ui-express": "^4.1.3",
96
+ "@types/yamljs": "^0.2.31",
97
+ "@typescript-eslint/eslint-plugin": "^4.7.0",
98
+ "@typescript-eslint/parser": "^4.7.0",
99
+ "eslint": "^7.13.0",
100
+ "eslint-config-prettier": "^6.15.0",
101
+ "eslint-plugin-prettier": "^3.1.4",
102
+ "eslint-plugin-tsdoc": "^0.2.7",
103
+ "express": "^4.17.2",
104
+ "mocha": "^9.2.0",
105
+ "prettier": "^2.1.2",
106
+ "swagger-ui-express": "^4.3.0",
107
+ "typescript": "4.3.2",
108
+ "yamljs": "^0.3.0"
109
+ }
110
+ }
@@ -0,0 +1,118 @@
1
+ /*
2
+
3
+ * About File:
4
+ it defines the structure of the deployments (when we do inspect for example), it also defines the structure of metacall.json
5
+
6
+ */
7
+
8
+ export type DeployStatus = 'create' | 'ready' | 'fail';
9
+
10
+ export enum LogType {
11
+ Job = 'job',
12
+ Deploy = 'deploy'
13
+ }
14
+
15
+ export type LanguageId =
16
+ | 'node'
17
+ | 'ts'
18
+ | 'rb'
19
+ | 'py'
20
+ | 'cs'
21
+ // | 'wasm'
22
+ // | 'java'
23
+ // | 'c'
24
+ | 'cob'
25
+ | 'file'
26
+ | 'rpc';
27
+
28
+ enum ValueId {
29
+ METACALL_BOOL = 0,
30
+ METACALL_CHAR = 1,
31
+ METACALL_SHORT = 2,
32
+ METACALL_INT = 3,
33
+ METACALL_LONG = 4,
34
+ METACALL_FLOAT = 5,
35
+ METACALL_DOUBLE = 6,
36
+ METACALL_STRING = 7,
37
+ METACALL_BUFFER = 8,
38
+ METACALL_ARRAY = 9,
39
+ METACALL_MAP = 10,
40
+ METACALL_PTR = 11,
41
+ METACALL_FUTURE = 12,
42
+ METACALL_FUNCTION = 13,
43
+ METACALL_NULL = 14,
44
+ METACALL_CLASS = 15,
45
+ METACALL_OBJECT = 16,
46
+
47
+ METACALL_SIZE,
48
+ METACALL_INVALID
49
+ }
50
+
51
+ interface Type {
52
+ name: string;
53
+ id: ValueId;
54
+ }
55
+
56
+ interface Return {
57
+ type: Type;
58
+ }
59
+
60
+ interface Argument {
61
+ name: string;
62
+ type: Type;
63
+ }
64
+
65
+ interface Signature {
66
+ ret: Return;
67
+ args: Argument[];
68
+ }
69
+
70
+ interface Func {
71
+ name: string;
72
+ signature: Signature;
73
+ async: boolean;
74
+ }
75
+
76
+ // TODO
77
+ /*
78
+ interface Class {
79
+
80
+ }
81
+
82
+ interface Object {
83
+
84
+ }
85
+ */
86
+
87
+ interface Scope {
88
+ name: string;
89
+ funcs: Func[];
90
+ classes: string[]; // TODO: Class[];
91
+ objects: string[]; // TODO: Object[];
92
+ }
93
+
94
+ interface Handle {
95
+ name: string;
96
+ scope: Scope;
97
+ }
98
+
99
+ export interface Deployment {
100
+ status: DeployStatus;
101
+ prefix: string;
102
+ suffix: string;
103
+ version: string;
104
+ packages: Record<LanguageId, Handle[]>;
105
+ ports: number[];
106
+ }
107
+
108
+ export interface Create {
109
+ suffix: string;
110
+ prefix: string;
111
+ version: string;
112
+ }
113
+
114
+ export type MetaCallJSON = {
115
+ language_id: LanguageId;
116
+ path: string;
117
+ scripts: string[];
118
+ };
@@ -0,0 +1,94 @@
1
+ /*
2
+
3
+ * About File:
4
+ this is already documented but it defines the languages supported, extensions, runners, coloring, etc
5
+
6
+ */
7
+
8
+ import { LanguageId } from './deployment';
9
+
10
+ interface Language {
11
+ tag: string; // Tag which corresponds to language_id in metacall.json
12
+ displayName: string; // Name for displaying the language
13
+ hexColor: string; // Color for displaying the language related things
14
+ fileExtRegex: RegExp; // Regex for selecting the metacall.json scripts field
15
+ runnerName?: string; // Id of the runner
16
+ runnerFilesRegexes: RegExp[]; // Regex for generating the runners list
17
+ }
18
+
19
+ export const Languages: Record<LanguageId, Language> = {
20
+ cs: {
21
+ tag: 'cs',
22
+ displayName: 'C#',
23
+ hexColor: '#953dac',
24
+ fileExtRegex: /^cs$/,
25
+ runnerName: 'csharp',
26
+ runnerFilesRegexes: [/^project\.json$/, /\.csproj$/]
27
+ },
28
+ py: {
29
+ tag: 'py',
30
+ displayName: 'Python',
31
+ hexColor: '#ffd43b',
32
+ fileExtRegex: /^py$/,
33
+ runnerName: 'python',
34
+ runnerFilesRegexes: [/^requirements\.txt$/]
35
+ },
36
+ rb: {
37
+ tag: 'rb',
38
+ displayName: 'Ruby',
39
+ hexColor: '#e53935',
40
+ fileExtRegex: /^rb$/,
41
+ runnerName: 'ruby',
42
+ runnerFilesRegexes: [/^Gemfile$/]
43
+ },
44
+ node: {
45
+ tag: 'node',
46
+ displayName: 'NodeJS',
47
+ hexColor: '#3c873a',
48
+ fileExtRegex: /^js$/,
49
+ runnerName: 'nodejs',
50
+ runnerFilesRegexes: [/^package\.json$/]
51
+ },
52
+ ts: {
53
+ tag: 'ts',
54
+ displayName: 'TypeScript',
55
+ hexColor: '#007acc',
56
+ fileExtRegex: /^(ts|tsx)$/,
57
+ runnerName: 'nodejs',
58
+ runnerFilesRegexes: [/^package\.json$/] // TODO: Use tsconfig instead?
59
+ },
60
+ file: {
61
+ tag: 'file',
62
+ displayName: 'Static Files',
63
+ hexColor: '#de5500',
64
+ fileExtRegex: /^\w+$/,
65
+ runnerName: undefined, // File has no runner (yet?)
66
+ runnerFilesRegexes: [] // File has no runner files (yet?)
67
+ },
68
+ cob: {
69
+ tag: 'cob',
70
+ displayName: 'Cobol',
71
+ hexColor: '#01325a',
72
+ fileExtRegex: /^(cob|cbl|cbl)$/,
73
+ runnerName: undefined, // Cobol has no runner (yet?)
74
+ runnerFilesRegexes: [] // Cobol has no runner files (yet?)
75
+ },
76
+ rpc: {
77
+ tag: 'rpc',
78
+ displayName: 'RPC',
79
+ hexColor: '#0f564d',
80
+ fileExtRegex: /^rpc$/,
81
+ runnerName: undefined, // RPC has no runner (yet?)
82
+ runnerFilesRegexes: [] // RPC has no runner files (yet?)
83
+ }
84
+ };
85
+
86
+ export const DisplayNameToLanguageId: Record<string, LanguageId> = Object.keys(
87
+ Languages
88
+ ).reduce(
89
+ (obj, lang) =>
90
+ Object.assign(obj, {
91
+ [Languages[lang as LanguageId].displayName]: lang
92
+ }),
93
+ {}
94
+ );
package/src/login.ts ADDED
@@ -0,0 +1,31 @@
1
+ import axios from 'axios';
2
+
3
+ interface Request {
4
+ email: string;
5
+ password: string;
6
+ 'g-recaptcha-response'?: string;
7
+ }
8
+
9
+ export default (
10
+ email: string,
11
+ password: string,
12
+ baseURL: string
13
+ ): Promise<string> => {
14
+ const request: Request = {
15
+ email,
16
+ password
17
+ };
18
+
19
+ if (!baseURL.includes('localhost'))
20
+ request['g-recaptcha-response'] = 'empty'; //TODO: Review the captcha
21
+
22
+ return axios
23
+ .post<string>(baseURL + '/login', request, {
24
+ headers: {
25
+ Accept: 'application/json, text/plain, */*',
26
+ Host: baseURL.split('//')[1],
27
+ Origin: baseURL
28
+ }
29
+ })
30
+ .then(res => res.data);
31
+ };
package/src/package.ts ADDED
@@ -0,0 +1,132 @@
1
+ /*
2
+
3
+ * About File:
4
+
5
+ defines a package and its routines, a package is just a metacall.json with some extra details, for example, the runners needed to build a it (for example, if we find a requirements.txt then it means we need to run the python installer pip, so the python runner is needed)
6
+
7
+ it includes ignore files (like .gitignore), it is able to list all files in a path and classify them depending on what loader is the correct for each file extension
8
+
9
+ generatePackage is an exported function that given a path it generates all the information needed, for example, what runners are needed, what metacall-*.json are generated (depending on file extension) and the list of files that will be in that package (excluding the ones in gitignore)
10
+
11
+ generateJsonsFromFiles is similar but it is more fine grained, it uses a list of files and returns what are the metacall-*.json generated from them
12
+
13
+ */
14
+
15
+ import walk from 'ignore-walk';
16
+ import { basename, extname } from 'path';
17
+ import { LanguageId, MetaCallJSON } from './deployment';
18
+ import { Languages } from './language';
19
+
20
+ export const findFilesPath = async (
21
+ path: string = process.cwd(),
22
+ ignoreFiles: string[] = ['.gitignore']
23
+ ): Promise<string[]> =>
24
+ (
25
+ await walk({
26
+ path,
27
+ ignoreFiles,
28
+ includeEmpty: false,
29
+ follow: true
30
+ })
31
+ ).filter(x => !x.startsWith('.git'));
32
+
33
+ const pathIsMetaCallJson = (path: string): boolean =>
34
+ !!/^metacall(-.+)?\.json$/.exec(basename(path));
35
+
36
+ export const findMetaCallJsons = (files: string[]): string[] =>
37
+ files.filter(pathIsMetaCallJson);
38
+
39
+ export const findRunners = (files: string[]): Set<string> => {
40
+ const runners: Set<string> = new Set<string>();
41
+
42
+ for (const file of files) {
43
+ const fileName = basename(file);
44
+ for (const langId of Object.keys(Languages)) {
45
+ const lang = Languages[langId as LanguageId];
46
+ for (const re of lang.runnerFilesRegexes) {
47
+ if (re.exec(fileName) && lang.runnerName) {
48
+ runners.add(lang.runnerName);
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ return runners;
55
+ };
56
+
57
+ export enum PackageError {
58
+ Empty = 'No files found in the current folder',
59
+ JsonNotFound = 'No metacall.json found in the current folder',
60
+ None = 'Package correctly generated'
61
+ }
62
+
63
+ interface PackageDescriptor {
64
+ error: PackageError;
65
+ files: string[];
66
+ jsons: string[];
67
+ runners: string[];
68
+ }
69
+
70
+ const NullPackage: PackageDescriptor = {
71
+ error: PackageError.None,
72
+ files: [],
73
+ jsons: [],
74
+ runners: []
75
+ };
76
+
77
+ export const generatePackage = async (
78
+ path: string = process.cwd()
79
+ ): Promise<PackageDescriptor> => {
80
+ const files = await findFilesPath(path);
81
+
82
+ if (files.length === 0) {
83
+ return { ...NullPackage, error: PackageError.Empty };
84
+ }
85
+
86
+ const jsons = findMetaCallJsons(files);
87
+
88
+ return {
89
+ error:
90
+ jsons.length === 0 ? PackageError.JsonNotFound : PackageError.None,
91
+ files,
92
+ jsons,
93
+ runners: Array.from(findRunners(files))
94
+ };
95
+ };
96
+
97
+ const getExtension = (file: string) => {
98
+ const ext = extname(file || '').split('.');
99
+ return ext[ext.length - 1];
100
+ };
101
+
102
+ const matchFilesByLanguage = (lang: LanguageId, files: string[]): string[] =>
103
+ files.reduce(
104
+ (arr: string[], file: string) =>
105
+ Languages[lang].fileExtRegex.exec(
106
+ getExtension(file) || basename(file)
107
+ )
108
+ ? [...arr, file]
109
+ : arr,
110
+ []
111
+ );
112
+
113
+ export const generateJsonsFromFiles = (files: string[]): MetaCallJSON[] =>
114
+ (Object.keys(Languages) as LanguageId[]).reduce<MetaCallJSON[]>(
115
+ (jsons, lang) => {
116
+ const scripts = matchFilesByLanguage(lang, files);
117
+
118
+ if (scripts.length === 0) {
119
+ return jsons;
120
+ } else {
121
+ return [
122
+ {
123
+ language_id: lang,
124
+ path: '.',
125
+ scripts
126
+ } as MetaCallJSON,
127
+ ...jsons
128
+ ];
129
+ }
130
+ },
131
+ []
132
+ );
package/src/plan.ts ADDED
@@ -0,0 +1,5 @@
1
+ export enum Plans {
2
+ Essential = 'Essential',
3
+ Standard = 'Standard',
4
+ Premium = 'Premium'
5
+ }