@metacall/protocol 0.1.28 → 0.1.29

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/dist/index.d.ts CHANGED
@@ -6,5 +6,5 @@ export * from './plan';
6
6
  export * from './protocol';
7
7
  export * from './signup';
8
8
  export * from './token';
9
- import metacallAPI from './protocol';
10
- export default metacallAPI;
9
+ import Protocol from './protocol';
10
+ export default Protocol;
package/dist/login.js CHANGED
@@ -1,24 +1,26 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const axios_1 = __importDefault(require("axios"));
7
3
  const url_1 = require("url");
8
- exports.default = (email, password, baseURL) => {
4
+ exports.default = async (email, password, baseURL) => {
9
5
  const request = {
10
6
  email,
11
7
  password
12
8
  };
13
- if (!baseURL.includes('localhost'))
14
- request['g-recaptcha-response'] = 'empty'; //TODO: Review the captcha
15
- return axios_1.default
16
- .post(baseURL + '/login', request, {
9
+ if (!baseURL.includes('localhost')) {
10
+ request['g-recaptcha-response'] = 'empty'; // TODO: Review the captcha
11
+ }
12
+ const res = await fetch(baseURL + '/login', {
13
+ method: 'POST',
17
14
  headers: {
18
15
  Accept: 'application/json, text/plain, */*',
19
16
  Host: new url_1.URL(baseURL).host,
20
- Origin: baseURL
21
- }
22
- })
23
- .then(res => res.data);
17
+ Origin: baseURL,
18
+ 'Content-Type': 'application/json'
19
+ },
20
+ body: JSON.stringify(request)
21
+ });
22
+ if (!res.ok) {
23
+ throw new Error(res.statusText);
24
+ }
25
+ return res.text();
24
26
  };
@@ -1,13 +1,16 @@
1
- import { AxiosError } from 'axios';
2
1
  import { Create, Deployment, LogType, MetaCallJSON } from './deployment';
3
2
  import { Plans } from './plan';
3
+ export declare class ProtocolError extends Error {
4
+ status?: number;
5
+ data?: unknown;
6
+ constructor(message: string, status?: number, data?: unknown);
7
+ }
4
8
  /**
5
- * Type guard for protocol-specific errors (Axios errors in this case).
9
+ * Type guard for protocol-specific errors.
6
10
  * @param err - The unknown error to check.
7
11
  * @returns True if the error is an ProtocolError, false otherwise.
8
12
  */
9
- export declare const isProtocolError: (err: unknown) => boolean;
10
- export { AxiosError as ProtocolError };
13
+ export declare const isProtocolError: (err: unknown) => err is ProtocolError;
11
14
  declare type SubscriptionMap = Record<string, number>;
12
15
  export interface SubscriptionDeploy {
13
16
  id: string;
@@ -19,7 +22,7 @@ export declare enum ResourceType {
19
22
  Package = "Package",
20
23
  Repository = "Repository"
21
24
  }
22
- export interface AddResponse {
25
+ export interface Resource {
23
26
  id: string;
24
27
  }
25
28
  export interface Branches {
@@ -66,8 +69,8 @@ export interface API {
66
69
  listSubscriptionsDeploys(): Promise<SubscriptionDeploy[]>;
67
70
  inspect(): Promise<Deployment[]>;
68
71
  inspectByName(suffix: string): Promise<Deployment>;
69
- upload(name: string, blob: unknown, jsons?: MetaCallJSON[], runners?: string[]): Promise<string>;
70
- add(url: string, branch: string, jsons: MetaCallJSON[]): Promise<AddResponse>;
72
+ upload(name: string, blob: unknown, jsons?: MetaCallJSON[], runners?: string[]): Promise<Resource>;
73
+ add(url: string, branch: string, jsons: MetaCallJSON[]): Promise<Resource>;
71
74
  deploy(name: string, env: {
72
75
  name: string;
73
76
  value: string;
@@ -82,8 +85,8 @@ export interface API {
82
85
  }
83
86
  declare const _default: (token: string, baseURL: string) => API;
84
87
  export default _default;
85
- export declare const MaxRetries = 30;
86
- export declare const MaxRetryInterval = 2000;
88
+ export declare const MaxRetries = 100;
89
+ export declare const MaxRetryInterval = 5000;
87
90
  export declare const MaxFuncLength = 64;
88
91
  /**
89
92
  * Executes an asynchronous function with automatic retry logic.
@@ -122,4 +125,4 @@ export declare const MaxFuncLength = 64;
122
125
  * );
123
126
  * ```
124
127
  */
125
- export declare const waitFor: <T>(fn: () => Promise<T>, maxRetries?: number, interval?: number) => Promise<T>;
128
+ export declare const waitFor: <T>(fn: (cancel: (message: string) => void) => Promise<T>, maxRetries?: number, interval?: number) => Promise<T>;
package/dist/protocol.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
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
3
+ This is just a client that implements all the rest API from the FaaS,
4
+ so each function it contains is an endpoint in the FaaS for deploying:
7
5
 
8
6
  refresh: updates the auth token
9
7
  validate: validates the auth token
@@ -18,41 +16,25 @@
18
16
  branchList: get the branches of a repository
19
17
  fileList: get files of a repository by branch
20
18
  */
21
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
22
- if (k2 === undefined) k2 = k;
23
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
24
- }) : (function(o, m, k, k2) {
25
- if (k2 === undefined) k2 = k;
26
- o[k2] = m[k];
27
- }));
28
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
- Object.defineProperty(o, "default", { enumerable: true, value: v });
30
- }) : function(o, v) {
31
- o["default"] = v;
32
- });
33
- var __importStar = (this && this.__importStar) || function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- var __importDefault = (this && this.__importDefault) || function (mod) {
41
- return (mod && mod.__esModule) ? mod : { "default": mod };
42
- };
43
19
  Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.waitFor = exports.MaxFuncLength = exports.MaxRetryInterval = exports.MaxRetries = exports.InvokeType = exports.ResourceType = exports.ProtocolError = exports.isProtocolError = void 0;
45
- const axios_1 = __importStar(require("axios"));
46
- Object.defineProperty(exports, "ProtocolError", { enumerable: true, get: function () { return axios_1.AxiosError; } });
47
- const form_data_1 = __importDefault(require("form-data"));
20
+ exports.waitFor = exports.MaxFuncLength = exports.MaxRetryInterval = exports.MaxRetries = exports.InvokeType = exports.ResourceType = exports.isProtocolError = exports.ProtocolError = void 0;
48
21
  const url_1 = require("url");
49
22
  const deployment_1 = require("./deployment");
23
+ class ProtocolError extends Error {
24
+ constructor(message, status, data) {
25
+ super(message);
26
+ this.name = 'ProtocolError';
27
+ this.status = status;
28
+ this.data = data;
29
+ }
30
+ }
31
+ exports.ProtocolError = ProtocolError;
50
32
  /**
51
- * Type guard for protocol-specific errors (Axios errors in this case).
33
+ * Type guard for protocol-specific errors.
52
34
  * @param err - The unknown error to check.
53
35
  * @returns True if the error is an ProtocolError, false otherwise.
54
36
  */
55
- const isProtocolError = (err) => axios_1.default.isAxiosError(err);
37
+ const isProtocolError = (err) => err instanceof ProtocolError;
56
38
  exports.isProtocolError = isProtocolError;
57
39
  var ResourceType;
58
40
  (function (ResourceType) {
@@ -64,33 +46,90 @@ var InvokeType;
64
46
  InvokeType["Call"] = "call";
65
47
  InvokeType["Await"] = "await";
66
48
  })(InvokeType = exports.InvokeType || (exports.InvokeType = {}));
67
- exports.default = (token, baseURL) => {
68
- const getURL = (path) => new url_1.URL(path, baseURL).toString();
69
- const getConfig = (headers = {}) => {
70
- return {
71
- headers: {
72
- Authorization: 'jwt ' + token,
73
- ...headers
74
- }
49
+ class Request {
50
+ constructor(token, baseURL) {
51
+ this.token = token;
52
+ this.baseURL = baseURL;
53
+ this.impl = {
54
+ url: '',
55
+ headers: new Headers({
56
+ Authorization: 'jwt ' + this.token
57
+ }),
58
+ method: 'GET',
59
+ body: undefined
75
60
  };
76
- };
61
+ }
62
+ url(path) {
63
+ this.impl.url = new url_1.URL(path, this.baseURL).toString();
64
+ return this;
65
+ }
66
+ headers(headers = {}) {
67
+ this.impl.headers = new Headers({
68
+ Authorization: 'jwt ' + this.token,
69
+ ...headers
70
+ });
71
+ return this;
72
+ }
73
+ method(method) {
74
+ this.impl.method = method;
75
+ return this;
76
+ }
77
+ blob(body) {
78
+ this.impl.body = body;
79
+ return this;
80
+ }
81
+ body(body) {
82
+ this.impl.body = JSON.stringify(body);
83
+ this.impl.headers.set('Content-Type', 'application/json');
84
+ return this;
85
+ }
86
+ async execute() {
87
+ const config = {
88
+ method: this.impl.method,
89
+ headers: this.impl.headers
90
+ };
91
+ if (this.impl.body !== undefined) {
92
+ config.body = this.impl.body;
93
+ }
94
+ const res = await fetch(this.impl.url, config);
95
+ if (!res.ok) {
96
+ const data = await res.text().catch(() => null);
97
+ throw new Error(`HTTP ${res.status}: ${res.statusText}${data ? ` - ${data}` : ''}`);
98
+ }
99
+ return res;
100
+ }
101
+ async asJson() {
102
+ const res = await this.execute();
103
+ return res.json();
104
+ }
105
+ async asText() {
106
+ const res = await this.execute();
107
+ return res.text();
108
+ }
109
+ async asStatus() {
110
+ const res = await this.execute();
111
+ return res.status;
112
+ }
113
+ async asResponse() {
114
+ return await this.execute();
115
+ }
116
+ }
117
+ exports.default = (token, baseURL) => {
118
+ const request = (url = baseURL) => new Request(token, url);
77
119
  const api = {
78
- refresh: () => axios_1.default
79
- .get(getURL('/api/account/refresh-token'), getConfig())
80
- .then(res => res.data),
81
- ready: () => axios_1.default
82
- .get(getURL('/api/readiness'), getConfig())
83
- .then(res => res.status == 200),
84
- validate: () => axios_1.default
85
- .get(getURL('/validate'), getConfig())
86
- .then(res => res.data),
87
- deployEnabled: () => axios_1.default
88
- .get(getURL('/api/account/deploy-enabled'), getConfig())
89
- .then(res => res.data),
120
+ refresh: () => request().url('/api/account/refresh-token').asText(),
121
+ ready: () => request()
122
+ .url('/api/readiness')
123
+ .asStatus()
124
+ .then(status => status === 200),
125
+ validate: () => request().url('/validate').asJson(),
126
+ deployEnabled: () => request().url('/api/account/deploy-enabled').asJson(),
90
127
  listSubscriptions: async () => {
91
- const res = await axios_1.default.get(getURL('/api/billing/list-subscriptions'), getConfig());
128
+ const subscriptionsList = await request()
129
+ .url('/api/billing/list-subscriptions')
130
+ .asJson();
92
131
  const subscriptions = {};
93
- for (const id of res.data) {
132
+ for (const id of subscriptionsList) {
94
133
  if (subscriptions[id] === undefined) {
95
134
  subscriptions[id] = 1;
96
135
  }
@@ -100,12 +139,10 @@ exports.default = (token, baseURL) => {
100
139
  }
101
140
  return subscriptions;
102
141
  },
103
- listSubscriptionsDeploys: () => axios_1.default
104
- .get(getURL('/api/billing/list-subscriptions-deploys'), getConfig())
105
- .then(res => res.data),
106
- inspect: () => axios_1.default
107
- .get(getURL('/api/inspect'), getConfig())
108
- .then(res => res.data),
142
+ listSubscriptionsDeploys: () => request()
143
+ .url('/api/billing/list-subscriptions')
144
+ .asJson(),
145
+ inspect: () => request().url('/api/inspect').asJson(),
109
146
  inspectByName: async (suffix) => {
110
147
  const deployments = await api.inspect();
111
148
  const deploy = deployments.find(deploy => deploy.suffix == suffix);
@@ -115,78 +152,97 @@ exports.default = (token, baseURL) => {
115
152
  return deploy;
116
153
  },
117
154
  upload: async (name, blob, jsons = [], runners = []) => {
118
- const fd = new form_data_1.default();
155
+ const fd = new FormData();
119
156
  fd.append('id', name);
120
157
  fd.append('type', 'application/x-zip-compressed');
121
158
  fd.append('jsons', JSON.stringify(jsons));
122
159
  fd.append('runners', JSON.stringify(runners));
123
- fd.append('raw', blob, {
124
- filename: 'blob',
125
- contentType: 'application/x-zip-compressed'
126
- });
127
- const res = await axios_1.default.post(getURL('/api/package/create'), fd, getConfig() // Axios automatically sets multipart headers
128
- );
129
- return res.data;
160
+ fd.append('raw', blob, 'blob');
161
+ return await request()
162
+ .url('/api/package/create')
163
+ .method('POST')
164
+ .blob(fd)
165
+ .asJson();
130
166
  },
131
- add: (url, branch, jsons = []) => axios_1.default
132
- .post(getURL('/api/repository/add'), {
167
+ add: (url, branch, jsons = []) => request()
168
+ .url('/api/repository/add')
169
+ .method('POST')
170
+ .body({
133
171
  url,
134
172
  branch,
135
173
  jsons
136
- }, getConfig())
137
- .then(res => res.data),
138
- branchList: (url) => axios_1.default
139
- .post(getURL('/api/repository/branchlist'), {
174
+ })
175
+ .asJson(),
176
+ branchList: (url) => request()
177
+ .url('/api/repository/branchlist')
178
+ .method('POST')
179
+ .body({
140
180
  url
141
- }, getConfig())
142
- .then(res => res.data),
143
- deploy: (name, env, plan, resourceType, release = Date.now().toString(16), version = 'v1') => axios_1.default
144
- .post(getURL('/api/deploy/create'), {
181
+ })
182
+ .asJson(),
183
+ deploy: (name, env, plan, resourceType, release = Date.now().toString(16), version = 'v1') => request()
184
+ .url('/api/deploy/create')
185
+ .method('POST')
186
+ .body({
145
187
  resourceType,
146
188
  suffix: name,
147
189
  release,
148
190
  env,
149
191
  plan,
150
192
  version
151
- }, getConfig())
152
- .then(res => res.data),
153
- deployDelete: (prefix, suffix, version = 'v1') => axios_1.default
154
- .post(getURL('/api/deploy/delete'), {
193
+ })
194
+ .asJson(),
195
+ deployDelete: (prefix, suffix, version = 'v1') => request()
196
+ .url('/api/deploy/delete')
197
+ .method('POST')
198
+ .body({
155
199
  prefix,
156
200
  suffix,
157
201
  version
158
- }, getConfig())
159
- .then(res => res.data),
160
- logs: (container, type = deployment_1.LogType.Deploy, suffix, prefix, version = 'v1') => axios_1.default
161
- .post(getURL('/api/deploy/logs'), {
202
+ })
203
+ .asJson(),
204
+ logs: (container, type = deployment_1.LogType.Deploy, suffix, prefix, version = 'v1') => request()
205
+ .url('/api/deploy/logs')
206
+ .method('POST')
207
+ .body({
162
208
  container,
163
209
  type,
164
210
  suffix,
165
211
  prefix,
166
212
  version
167
- }, getConfig())
168
- .then(res => res.data),
169
- fileList: (url, branch) => axios_1.default
170
- .post(getURL('/api/repository/filelist'), {
213
+ })
214
+ .asJson(),
215
+ fileList: (url, branch) => request()
216
+ .url('/api/repository/filelist')
217
+ .method('POST')
218
+ .body({
171
219
  url,
172
220
  branch
173
- }, getConfig())
174
- .then(res => res.data['files']),
175
- invoke: (type, prefix, suffix, version = 'v1', name, args) => {
176
- const url = getURL(`/${prefix}/${suffix}/${version}/${type}/${name}`);
177
- const config = getConfig();
178
- const req = args === undefined
179
- ? axios_1.default.get(url, config)
180
- : axios_1.default.post(url, args, config);
181
- return req.then(res => res.data);
221
+ })
222
+ .asJson()
223
+ .then(res => res['files']),
224
+ invoke: async (type, prefix, suffix, version = 'v1', name, args) => {
225
+ // Old API
226
+ // const req = request('https://api.metacall.io').url(
227
+ // `/${prefix}/${suffix}/${version}/${type}/${name}`
228
+ // );
229
+ // New API
230
+ const req = request(`https://${version}-${suffix}-${prefix}.api.metacall.io`).url(`/${type}/${name}`);
231
+ if (args === undefined) {
232
+ req.method('GET');
233
+ }
234
+ else {
235
+ req.method('POST').body(args);
236
+ }
237
+ return await req.asJson();
182
238
  },
183
239
  call: (prefix, suffix, version = 'v1', name, args) => api.invoke(InvokeType.Call, prefix, suffix, version, name, args),
184
240
  await: (prefix, suffix, version = 'v1', name, args) => api.invoke(InvokeType.Await, prefix, suffix, version, name, args)
185
241
  };
186
242
  return api;
187
243
  };
188
- exports.MaxRetries = 30;
189
- exports.MaxRetryInterval = 2000;
244
+ exports.MaxRetries = 100;
245
+ exports.MaxRetryInterval = 5000;
190
246
  exports.MaxFuncLength = 64;
191
247
  /**
192
248
  * Executes an asynchronous function with automatic retry logic.
@@ -227,9 +283,14 @@ exports.MaxFuncLength = 64;
227
283
  */
228
284
  const waitFor = async (fn, maxRetries = exports.MaxRetries, interval = exports.MaxRetryInterval) => {
229
285
  let retry = 0;
286
+ let cancellation = undefined;
287
+ const cancel = (message) => {
288
+ retry = exports.MaxRetries;
289
+ cancellation = `Operation cancelled with message: ${message}`;
290
+ };
230
291
  for (;;) {
231
292
  try {
232
- return await fn();
293
+ return await fn(cancel);
233
294
  }
234
295
  catch (error) {
235
296
  retry++;
@@ -239,11 +300,13 @@ const waitFor = async (fn, maxRetries = exports.MaxRetries, interval = exports.M
239
300
  (fnStr.length > exports.MaxFuncLength
240
301
  ? fnStr.slice(0, exports.MaxFuncLength) + '...'
241
302
  : fnStr);
242
- const message = exports.isProtocolError(error)
243
- ? error.message
244
- : error instanceof Error
303
+ const message = cancellation !== undefined
304
+ ? cancellation
305
+ : exports.isProtocolError(error)
245
306
  ? error.message
246
- : String(error);
307
+ : error instanceof Error
308
+ ? error.message
309
+ : String(error);
247
310
  throw new Error(`Failed to execute '${func}' after ${maxRetries} retries: ${message}`);
248
311
  }
249
312
  await new Promise(r => setTimeout(r, interval));
package/dist/signup.js CHANGED
@@ -1,25 +1,27 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const axios_1 = __importDefault(require("axios"));
7
3
  const url_1 = require("url");
8
- exports.default = (email, password, alias, baseURL) => {
4
+ exports.default = async (email, password, alias, baseURL) => {
9
5
  const request = {
10
6
  email,
11
7
  password,
12
8
  alias
13
9
  };
14
- if (!baseURL.includes('localhost'))
15
- request['g-recaptcha-response'] = 'empty';
16
- return axios_1.default
17
- .post(baseURL + '/signup', request, {
10
+ if (!baseURL.includes('localhost')) {
11
+ request['g-recaptcha-response'] = 'empty'; // TODO: Review the captcha
12
+ }
13
+ const res = await fetch(baseURL + '/signup', {
14
+ method: 'POST',
18
15
  headers: {
19
16
  Accept: 'application/json, text/plain, */*',
20
17
  Host: new url_1.URL(baseURL).host,
21
- Origin: baseURL
22
- }
23
- })
24
- .then(res => res.data);
18
+ Origin: baseURL,
19
+ 'Content-Type': 'application/json'
20
+ },
21
+ body: JSON.stringify(request)
22
+ });
23
+ if (!res.ok) {
24
+ throw new Error(res.statusText);
25
+ }
26
+ return res.text();
25
27
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metacall/protocol",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Tool for deploying into MetaCall FaaS platform.",
5
5
  "exports": {
6
6
  "./*": "./dist/*.js",
@@ -18,8 +18,9 @@
18
18
  }
19
19
  },
20
20
  "scripts": {
21
+ "pretest": "npm --prefix ./src/test/resources/integration/api install",
21
22
  "test": "npm run --silent build && mocha dist/test",
22
- "unit": "npm run --silent test -- --ignore **/integration**",
23
+ "unit": "npm run --silent test -- --ignore **/**.integration.**",
23
24
  "prepublishOnly": "npm run --silent build",
24
25
  "postinstall": "node -e \"require('fs').existsSync('githooks') && require('./githooks/configure.js').configure()\"",
25
26
  "build": "npm run --silent lint && tsc",
@@ -85,8 +86,6 @@
85
86
  }
86
87
  },
87
88
  "dependencies": {
88
- "axios": "^1.13.5",
89
- "form-data": "^3.0.0",
90
89
  "ignore-walk": "^3.0.4",
91
90
  "jsonwebtoken": "^9.0.0"
92
91
  },
package/src/index.ts CHANGED
@@ -7,5 +7,5 @@ export * from './protocol';
7
7
  export * from './signup';
8
8
  export * from './token';
9
9
 
10
- import metacallAPI from './protocol';
11
- export default metacallAPI;
10
+ import Protocol from './protocol';
11
+ export default Protocol;
package/src/login.ts CHANGED
@@ -1,4 +1,3 @@
1
- import axios from 'axios';
2
1
  import { URL } from 'url';
3
2
 
4
3
  interface Request {
@@ -7,7 +6,7 @@ interface Request {
7
6
  'g-recaptcha-response'?: string;
8
7
  }
9
8
 
10
- export default (
9
+ export default async (
11
10
  email: string,
12
11
  password: string,
13
12
  baseURL: string
@@ -17,16 +16,24 @@ export default (
17
16
  password
18
17
  };
19
18
 
20
- if (!baseURL.includes('localhost'))
21
- request['g-recaptcha-response'] = 'empty'; //TODO: Review the captcha
19
+ if (!baseURL.includes('localhost')) {
20
+ request['g-recaptcha-response'] = 'empty'; // TODO: Review the captcha
21
+ }
22
22
 
23
- return axios
24
- .post<string>(baseURL + '/login', request, {
25
- headers: {
26
- Accept: 'application/json, text/plain, */*',
27
- Host: new URL(baseURL).host,
28
- Origin: baseURL
29
- }
30
- })
31
- .then(res => res.data);
23
+ const res = await fetch(baseURL + '/login', {
24
+ method: 'POST',
25
+ headers: {
26
+ Accept: 'application/json, text/plain, */*',
27
+ Host: new URL(baseURL).host,
28
+ Origin: baseURL,
29
+ 'Content-Type': 'application/json'
30
+ },
31
+ body: JSON.stringify(request)
32
+ });
33
+
34
+ if (!res.ok) {
35
+ throw new Error(res.statusText);
36
+ }
37
+
38
+ return res.text();
32
39
  };
package/src/protocol.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  /*
2
-
3
- * About File:
4
-
5
- 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
2
+ This is just a client that implements all the rest API from the FaaS,
3
+ so each function it contains is an endpoint in the FaaS for deploying:
6
4
 
7
5
  refresh: updates the auth token
8
6
  validate: validates the auth token
@@ -18,22 +16,29 @@
18
16
  fileList: get files of a repository by branch
19
17
  */
20
18
 
21
- import axios, { AxiosError, AxiosRequestConfig } from 'axios';
22
- import FormData from 'form-data';
23
19
  import { URL } from 'url';
24
20
  import { Create, Deployment, LogType, MetaCallJSON } from './deployment';
25
21
  import { Plans } from './plan';
26
- import { ProtocolError } from './protocol';
22
+
23
+ export class ProtocolError extends Error {
24
+ status?: number;
25
+ data?: unknown;
26
+
27
+ constructor(message: string, status?: number, data?: unknown) {
28
+ super(message);
29
+ this.name = 'ProtocolError';
30
+ this.status = status;
31
+ this.data = data;
32
+ }
33
+ }
27
34
 
28
35
  /**
29
- * Type guard for protocol-specific errors (Axios errors in this case).
36
+ * Type guard for protocol-specific errors.
30
37
  * @param err - The unknown error to check.
31
38
  * @returns True if the error is an ProtocolError, false otherwise.
32
39
  */
33
- export const isProtocolError = (err: unknown): boolean =>
34
- axios.isAxiosError(err);
35
-
36
- export { AxiosError as ProtocolError };
40
+ export const isProtocolError = (err: unknown): err is ProtocolError =>
41
+ err instanceof ProtocolError;
37
42
 
38
43
  type SubscriptionMap = Record<string, number>;
39
44
 
@@ -49,7 +54,7 @@ export enum ResourceType {
49
54
  Repository = 'Repository'
50
55
  }
51
56
 
52
- export interface AddResponse {
57
+ export interface Resource {
53
58
  id: string;
54
59
  }
55
60
 
@@ -106,12 +111,8 @@ export interface API {
106
111
  blob: unknown,
107
112
  jsons?: MetaCallJSON[],
108
113
  runners?: string[]
109
- ): Promise<string>;
110
- add(
111
- url: string,
112
- branch: string,
113
- jsons: MetaCallJSON[]
114
- ): Promise<AddResponse>;
114
+ ): Promise<Resource>;
115
+ add(url: string, branch: string, jsons: MetaCallJSON[]): Promise<Resource>;
115
116
  deploy(
116
117
  name: string,
117
118
  env: { name: string; value: string }[],
@@ -158,50 +159,131 @@ export interface API {
158
159
  ): Promise<Result>;
159
160
  }
160
161
 
161
- export default (token: string, baseURL: string): API => {
162
- const getURL = (path: string): string => new URL(path, baseURL).toString();
163
- const getConfig = (headers = {}): AxiosRequestConfig => {
164
- return {
165
- headers: {
166
- Authorization: 'jwt ' + token,
167
- ...headers
168
- }
162
+ interface RequestImpl {
163
+ url: string;
164
+ headers: Headers;
165
+ method: string;
166
+ body?: BodyInit;
167
+ }
168
+
169
+ class Request {
170
+ private token: string;
171
+ private baseURL: string;
172
+ private impl: RequestImpl;
173
+
174
+ constructor(token: string, baseURL: string) {
175
+ this.token = token;
176
+ this.baseURL = baseURL;
177
+ this.impl = {
178
+ url: '',
179
+ headers: new Headers({
180
+ Authorization: 'jwt ' + this.token
181
+ }),
182
+ method: 'GET',
183
+ body: undefined
169
184
  };
170
- };
185
+ }
186
+
187
+ url(path: string): Request {
188
+ this.impl.url = new URL(path, this.baseURL).toString();
189
+ return this;
190
+ }
191
+
192
+ headers(headers = {}): Request {
193
+ this.impl.headers = new Headers({
194
+ Authorization: 'jwt ' + this.token,
195
+ ...headers
196
+ });
197
+ return this;
198
+ }
199
+
200
+ method(method: string): Request {
201
+ this.impl.method = method;
202
+ return this;
203
+ }
204
+
205
+ blob(body: BodyInit): Request {
206
+ this.impl.body = body;
207
+ return this;
208
+ }
209
+
210
+ body(body: unknown): Request {
211
+ this.impl.body = JSON.stringify(body);
212
+ this.impl.headers.set('Content-Type', 'application/json');
213
+ return this;
214
+ }
215
+
216
+ private async execute(): Promise<Response> {
217
+ const config: RequestInit = {
218
+ method: this.impl.method,
219
+ headers: this.impl.headers
220
+ };
221
+
222
+ if (this.impl.body !== undefined) {
223
+ config.body = this.impl.body;
224
+ }
225
+
226
+ const res = await fetch(this.impl.url, config);
227
+
228
+ if (!res.ok) {
229
+ const data = await res.text().catch(() => null);
230
+ throw new Error(
231
+ `HTTP ${res.status}: ${res.statusText}${
232
+ data ? ` - ${data}` : ''
233
+ }`
234
+ );
235
+ }
236
+
237
+ return res;
238
+ }
239
+
240
+ async asJson<T>(): Promise<T> {
241
+ const res = await this.execute();
242
+ return res.json() as Promise<T>;
243
+ }
244
+
245
+ async asText(): Promise<string> {
246
+ const res = await this.execute();
247
+ return res.text();
248
+ }
249
+
250
+ async asStatus(): Promise<number> {
251
+ const res = await this.execute();
252
+ return res.status;
253
+ }
254
+
255
+ async asResponse(): Promise<Response> {
256
+ return await this.execute();
257
+ }
258
+ }
259
+
260
+ export default (token: string, baseURL: string): API => {
261
+ const request = (url = baseURL) => new Request(token, url);
171
262
 
172
263
  const api: API = {
173
264
  refresh: (): Promise<string> =>
174
- axios
175
- .get<string>(getURL('/api/account/refresh-token'), getConfig())
176
- .then(res => res.data),
265
+ request().url('/api/account/refresh-token').asText(),
177
266
 
178
267
  ready: (): Promise<boolean> =>
179
- axios
180
- .get<boolean>(getURL('/api/readiness'), getConfig())
181
- .then(res => res.status == 200),
268
+ request()
269
+ .url('/api/readiness')
270
+ .asStatus()
271
+ .then(status => status === 200),
182
272
 
183
273
  validate: (): Promise<boolean> =>
184
- axios
185
- .get<boolean>(getURL('/validate'), getConfig())
186
- .then(res => res.data),
274
+ request().url('/validate').asJson<boolean>(),
187
275
 
188
276
  deployEnabled: (): Promise<boolean> =>
189
- axios
190
- .get<boolean>(
191
- getURL('/api/account/deploy-enabled'),
192
- getConfig()
193
- )
194
- .then(res => res.data),
277
+ request().url('/api/account/deploy-enabled').asJson<boolean>(),
195
278
 
196
279
  listSubscriptions: async (): Promise<SubscriptionMap> => {
197
- const res = await axios.get<string[]>(
198
- getURL('/api/billing/list-subscriptions'),
199
- getConfig()
200
- );
280
+ const subscriptionsList = await request()
281
+ .url('/api/billing/list-subscriptions')
282
+ .asJson<string[]>();
201
283
 
202
284
  const subscriptions: SubscriptionMap = {};
203
285
 
204
- for (const id of res.data) {
286
+ for (const id of subscriptionsList) {
205
287
  if (subscriptions[id] === undefined) {
206
288
  subscriptions[id] = 1;
207
289
  } else {
@@ -213,17 +295,12 @@ export default (token: string, baseURL: string): API => {
213
295
  },
214
296
 
215
297
  listSubscriptionsDeploys: (): Promise<SubscriptionDeploy[]> =>
216
- axios
217
- .get<SubscriptionDeploy[]>(
218
- getURL('/api/billing/list-subscriptions-deploys'),
219
- getConfig()
220
- )
221
- .then(res => res.data),
298
+ request()
299
+ .url('/api/billing/list-subscriptions')
300
+ .asJson<SubscriptionDeploy[]>(),
222
301
 
223
302
  inspect: (): Promise<Deployment[]> =>
224
- axios
225
- .get<Deployment[]>(getURL('/api/inspect'), getConfig())
226
- .then(res => res.data),
303
+ request().url('/api/inspect').asJson<Deployment[]>(),
227
304
 
228
305
  inspectByName: async (suffix: string): Promise<Deployment> => {
229
306
  const deployments = await api.inspect();
@@ -239,52 +316,48 @@ export default (token: string, baseURL: string): API => {
239
316
 
240
317
  upload: async (
241
318
  name: string,
242
- blob: unknown,
319
+ blob: Blob,
243
320
  jsons: MetaCallJSON[] = [],
244
321
  runners: string[] = []
245
- ): Promise<string> => {
322
+ ): Promise<Resource> => {
246
323
  const fd = new FormData();
324
+
247
325
  fd.append('id', name);
248
326
  fd.append('type', 'application/x-zip-compressed');
249
327
  fd.append('jsons', JSON.stringify(jsons));
250
328
  fd.append('runners', JSON.stringify(runners));
251
- fd.append('raw', blob, {
252
- filename: 'blob',
253
- contentType: 'application/x-zip-compressed'
254
- });
255
- const res = await axios.post<string>(
256
- getURL('/api/package/create'),
257
- fd,
258
- getConfig() // Axios automatically sets multipart headers
259
- );
260
- return res.data;
329
+ fd.append('raw', blob, 'blob');
330
+
331
+ return await request()
332
+ .url('/api/package/create')
333
+ .method('POST')
334
+ .blob(fd)
335
+ .asJson<Resource>();
261
336
  },
337
+
262
338
  add: (
263
339
  url: string,
264
340
  branch: string,
265
341
  jsons: MetaCallJSON[] = []
266
- ): Promise<AddResponse> =>
267
- axios
268
- .post<AddResponse>(
269
- getURL('/api/repository/add'),
270
- {
271
- url,
272
- branch,
273
- jsons
274
- },
275
- getConfig()
276
- )
277
- .then(res => res.data),
342
+ ): Promise<Resource> =>
343
+ request()
344
+ .url('/api/repository/add')
345
+ .method('POST')
346
+ .body({
347
+ url,
348
+ branch,
349
+ jsons
350
+ })
351
+ .asJson<Resource>(),
352
+
278
353
  branchList: (url: string): Promise<Branches> =>
279
- axios
280
- .post<Branches>(
281
- getURL('/api/repository/branchlist'),
282
- {
283
- url
284
- },
285
- getConfig()
286
- )
287
- .then(res => res.data),
354
+ request()
355
+ .url('/api/repository/branchlist')
356
+ .method('POST')
357
+ .body({
358
+ url
359
+ })
360
+ .asJson<Branches>(),
288
361
 
289
362
  deploy: (
290
363
  name: string,
@@ -294,37 +367,33 @@ export default (token: string, baseURL: string): API => {
294
367
  release: string = Date.now().toString(16),
295
368
  version = 'v1'
296
369
  ): Promise<Create> =>
297
- axios
298
- .post<Create>(
299
- getURL('/api/deploy/create'),
300
- {
301
- resourceType,
302
- suffix: name,
303
- release,
304
- env,
305
- plan,
306
- version
307
- },
308
- getConfig()
309
- )
310
- .then(res => res.data),
370
+ request()
371
+ .url('/api/deploy/create')
372
+ .method('POST')
373
+ .body({
374
+ resourceType,
375
+ suffix: name,
376
+ release,
377
+ env,
378
+ plan,
379
+ version
380
+ })
381
+ .asJson<Create>(),
311
382
 
312
383
  deployDelete: (
313
384
  prefix: string,
314
385
  suffix: string,
315
386
  version = 'v1'
316
387
  ): Promise<string> =>
317
- axios
318
- .post<string>(
319
- getURL('/api/deploy/delete'),
320
- {
321
- prefix,
322
- suffix,
323
- version
324
- },
325
- getConfig()
326
- )
327
- .then(res => res.data),
388
+ request()
389
+ .url('/api/deploy/delete')
390
+ .method('POST')
391
+ .body({
392
+ prefix,
393
+ suffix,
394
+ version
395
+ })
396
+ .asJson<string>(),
328
397
 
329
398
  logs: (
330
399
  container: string,
@@ -333,33 +402,30 @@ export default (token: string, baseURL: string): API => {
333
402
  prefix: string,
334
403
  version = 'v1'
335
404
  ): Promise<string> =>
336
- axios
337
- .post<string>(
338
- getURL('/api/deploy/logs'),
339
- {
340
- container,
341
- type,
342
- suffix,
343
- prefix,
344
- version
345
- },
346
- getConfig()
347
- )
348
- .then(res => res.data),
405
+ request()
406
+ .url('/api/deploy/logs')
407
+ .method('POST')
408
+ .body({
409
+ container,
410
+ type,
411
+ suffix,
412
+ prefix,
413
+ version
414
+ })
415
+ .asJson<string>(),
349
416
 
350
417
  fileList: (url: string, branch: string): Promise<string[]> =>
351
- axios
352
- .post<{ [k: string]: string[] }>(
353
- getURL('/api/repository/filelist'),
354
- {
355
- url,
356
- branch
357
- },
358
- getConfig()
359
- )
360
- .then(res => res.data['files']),
361
-
362
- invoke: <Result, Args = unknown>(
418
+ request()
419
+ .url('/api/repository/filelist')
420
+ .method('POST')
421
+ .body({
422
+ url,
423
+ branch
424
+ })
425
+ .asJson<{ [k: string]: string[] }>()
426
+ .then(res => res['files']),
427
+
428
+ invoke: async <Result, Args = unknown>(
363
429
  type: InvokeType,
364
430
  prefix: string,
365
431
  suffix: string,
@@ -367,17 +433,23 @@ export default (token: string, baseURL: string): API => {
367
433
  name: string,
368
434
  args?: Args
369
435
  ): Promise<Result> => {
370
- const url = getURL(
371
- `/${prefix}/${suffix}/${version}/${type}/${name}`
372
- );
373
- const config = getConfig();
374
-
375
- const req =
376
- args === undefined
377
- ? axios.get<Result>(url, config)
378
- : axios.post<Result>(url, args, config);
436
+ // Old API
437
+ // const req = request('https://api.metacall.io').url(
438
+ // `/${prefix}/${suffix}/${version}/${type}/${name}`
439
+ // );
440
+
441
+ // New API
442
+ const req = request(
443
+ `https://${version}-${suffix}-${prefix}.api.metacall.io`
444
+ ).url(`/${type}/${name}`);
445
+
446
+ if (args === undefined) {
447
+ req.method('GET');
448
+ } else {
449
+ req.method('POST').body(args);
450
+ }
379
451
 
380
- return req.then(res => res.data);
452
+ return await req.asJson<Result>();
381
453
  },
382
454
 
383
455
  call: <Result, Args = unknown>(
@@ -402,8 +474,8 @@ export default (token: string, baseURL: string): API => {
402
474
  return api;
403
475
  };
404
476
 
405
- export const MaxRetries = 30;
406
- export const MaxRetryInterval = 2000;
477
+ export const MaxRetries = 100;
478
+ export const MaxRetryInterval = 5000;
407
479
  export const MaxFuncLength = 64;
408
480
 
409
481
  /**
@@ -444,15 +516,21 @@ export const MaxFuncLength = 64;
444
516
  * ```
445
517
  */
446
518
  export const waitFor = async <T>(
447
- fn: () => Promise<T>,
519
+ fn: (cancel: (message: string) => void) => Promise<T>,
448
520
  maxRetries: number = MaxRetries,
449
521
  interval: number = MaxRetryInterval
450
522
  ): Promise<T> => {
451
523
  let retry = 0;
524
+ let cancellation = undefined;
525
+
526
+ const cancel = (message: string) => {
527
+ retry = MaxRetries;
528
+ cancellation = `Operation cancelled with message: ${message}`;
529
+ };
452
530
 
453
531
  for (;;) {
454
532
  try {
455
- return await fn();
533
+ return await fn(cancel);
456
534
  } catch (error) {
457
535
  retry++;
458
536
  if (retry >= maxRetries) {
@@ -462,11 +540,14 @@ export const waitFor = async <T>(
462
540
  (fnStr.length > MaxFuncLength
463
541
  ? fnStr.slice(0, MaxFuncLength) + '...'
464
542
  : fnStr);
465
- const message = isProtocolError(error)
466
- ? (error as ProtocolError).message
467
- : error instanceof Error
468
- ? error.message
469
- : String(error);
543
+ const message =
544
+ cancellation !== undefined
545
+ ? cancellation
546
+ : isProtocolError(error)
547
+ ? error.message
548
+ : error instanceof Error
549
+ ? error.message
550
+ : String(error);
470
551
 
471
552
  throw new Error(
472
553
  `Failed to execute '${func}' after ${maxRetries} retries: ${message}`
package/src/signup.ts CHANGED
@@ -1,4 +1,3 @@
1
- import axios from 'axios';
2
1
  import { URL } from 'url';
3
2
  interface Request {
4
3
  email: string;
@@ -7,7 +6,7 @@ interface Request {
7
6
  'g-recaptcha-response'?: string;
8
7
  }
9
8
 
10
- export default (
9
+ export default async (
11
10
  email: string,
12
11
  password: string,
13
12
  alias: string,
@@ -18,15 +17,25 @@ export default (
18
17
  password,
19
18
  alias
20
19
  };
21
- if (!baseURL.includes('localhost'))
22
- request['g-recaptcha-response'] = 'empty';
23
- return axios
24
- .post<string>(baseURL + '/signup', request, {
25
- headers: {
26
- Accept: 'application/json, text/plain, */*',
27
- Host: new URL(baseURL).host,
28
- Origin: baseURL
29
- }
30
- })
31
- .then(res => res.data);
20
+
21
+ if (!baseURL.includes('localhost')) {
22
+ request['g-recaptcha-response'] = 'empty'; // TODO: Review the captcha
23
+ }
24
+
25
+ const res = await fetch(baseURL + '/signup', {
26
+ method: 'POST',
27
+ headers: {
28
+ Accept: 'application/json, text/plain, */*',
29
+ Host: new URL(baseURL).host,
30
+ Origin: baseURL,
31
+ 'Content-Type': 'application/json'
32
+ },
33
+ body: JSON.stringify(request)
34
+ });
35
+
36
+ if (!res.ok) {
37
+ throw new Error(res.statusText);
38
+ }
39
+
40
+ return res.text();
32
41
  };
package/tsconfig.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "strict": true,
4
- "lib": ["es2020", "dom"],
4
+ "lib": ["es2020", "DOM"],
5
5
  "moduleResolution": "node",
6
6
  "target": "es2020",
7
7
  "module": "CommonJS",