@certik/skynet 0.18.8 → 0.19.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/.vscode/settings.json +5 -0
- package/CHANGELOG.md +13 -0
- package/README.md +2 -2
- package/{abi.js → abi.ts} +5 -132
- package/{address.js → address.ts} +4 -4
- package/{api.js → api.ts} +79 -19
- package/{app.js → app.ts} +138 -25
- package/{availability.js → availability.ts} +18 -21
- package/bun.lockb +0 -0
- package/{cli.js → cli.ts} +1 -1
- package/{const.js → const.ts} +42 -3
- package/{databricks.js → databricks.ts} +24 -23
- package/{date.js → date.ts} +5 -5
- package/{deploy.js → deploy.ts} +157 -44
- package/{dynamodb.js → dynamodb.ts} +159 -186
- package/env.ts +25 -0
- package/eslint.config.mjs +7 -0
- package/examples/{api.js → api.ts} +7 -6
- package/examples/{indexer.js → indexer.ts} +9 -10
- package/examples/{mode-indexer.js → mode-indexer.ts} +18 -10
- package/graphql.ts +43 -0
- package/{indexer.js → indexer.ts} +228 -109
- package/{log.js → log.ts} +6 -6
- package/{opsgenie.js → opsgenie.ts} +29 -6
- package/package.json +18 -11
- package/{s3.js → s3.ts} +29 -19
- package/search.ts +37 -0
- package/selector.ts +72 -0
- package/{slack.js → slack.ts} +19 -12
- package/snowflake.ts +51 -0
- package/tsconfig.json +26 -0
- package/util.ts +35 -0
- package/web3.ts +41 -0
- package/.eslintignore +0 -1
- package/.eslintrc.json +0 -20
- package/abi.d.ts +0 -20
- package/address.d.ts +0 -2
- package/api.d.ts +0 -12
- package/app.d.ts +0 -118
- package/availability.d.ts +0 -18
- package/cli.d.ts +0 -4
- package/const.d.ts +0 -42
- package/deploy.d.ts +0 -51
- package/dns.d.ts +0 -1
- package/dns.js +0 -27
- package/dynamodb.d.ts +0 -63
- package/env.d.ts +0 -6
- package/env.js +0 -42
- package/examples/consumer.js +0 -30
- package/examples/producer.js +0 -63
- package/graphql.d.ts +0 -3
- package/graphql.js +0 -22
- package/indexer.d.ts +0 -32
- package/log.d.ts +0 -8
- package/opsgenie.d.ts +0 -8
- package/proxy.d.ts +0 -9
- package/proxy.js +0 -154
- package/s3.d.ts +0 -36
- package/search.d.ts +0 -5
- package/search.js +0 -29
- package/selector.d.ts +0 -19
- package/selector.js +0 -71
- package/slack.d.ts +0 -10
- package/snowflake.d.ts +0 -4
- package/snowflake.js +0 -33
- package/sqs.d.ts +0 -3
- package/sqs.js +0 -5
- package/util.d.ts +0 -5
- package/util.js +0 -61
- package/web3.d.ts +0 -25
- package/web3.js +0 -113
|
@@ -1,17 +1,40 @@
|
|
|
1
1
|
import md5 from "md5";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
type OpsgenieResponse = {
|
|
4
|
+
data: {
|
|
5
|
+
success: boolean;
|
|
6
|
+
action: string;
|
|
7
|
+
processedAt: string;
|
|
8
|
+
integrationId: string;
|
|
9
|
+
isSuccess: boolean;
|
|
10
|
+
status: string;
|
|
11
|
+
alertId: string;
|
|
12
|
+
alias: string;
|
|
13
|
+
};
|
|
14
|
+
took: number;
|
|
15
|
+
requestId: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function getGenieKey(key?: string) {
|
|
19
|
+
return key || process.env["SKYNET_OPSGENIE_API_KEY"];
|
|
5
20
|
}
|
|
6
21
|
|
|
7
22
|
function getGenieEndPoint() {
|
|
8
|
-
return process.env
|
|
23
|
+
return process.env["SKYNET_OPSGENIE_END_POINT"] || "https://api.opsgenie.com/v2/alerts";
|
|
9
24
|
}
|
|
10
25
|
|
|
11
|
-
export async function postGenieMessage(
|
|
26
|
+
export async function postGenieMessage(
|
|
27
|
+
body: {
|
|
28
|
+
alias?: string;
|
|
29
|
+
message: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
},
|
|
32
|
+
apiKey?: string,
|
|
33
|
+
verbose?: boolean,
|
|
34
|
+
) {
|
|
12
35
|
try {
|
|
13
36
|
const genieKey = apiKey || getGenieKey();
|
|
14
|
-
const genieEndPoint = getGenieEndPoint(
|
|
37
|
+
const genieEndPoint = getGenieEndPoint();
|
|
15
38
|
|
|
16
39
|
// Prevents duplicate alerts (See Opsgenie doc about alias)
|
|
17
40
|
if (!body.alias) {
|
|
@@ -32,7 +55,7 @@ export async function postGenieMessage(body, apiKey, verbose) {
|
|
|
32
55
|
body: JSON.stringify(body),
|
|
33
56
|
});
|
|
34
57
|
|
|
35
|
-
const result = await response.json();
|
|
58
|
+
const result: OpsgenieResponse = await response.json();
|
|
36
59
|
if (verbose) {
|
|
37
60
|
console.log(`Result of API call to Opsgenie... ${result}`);
|
|
38
61
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@certik/skynet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Skynet Shared JS library",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "index.
|
|
6
|
+
"main": "index.ts",
|
|
7
7
|
"author": "CertiK Engineering",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"lint": "eslint *.
|
|
10
|
-
"test": "doppler run --
|
|
11
|
-
"format": "prettier --write '*.
|
|
9
|
+
"lint": "eslint '*.ts' test/*.ts",
|
|
10
|
+
"test": "doppler run -- bun test",
|
|
11
|
+
"format": "prettier --write '*.ts' '**/*.ts'",
|
|
12
12
|
"pub": "npm publish --access public"
|
|
13
13
|
},
|
|
14
14
|
"engines": {
|
|
@@ -22,26 +22,33 @@
|
|
|
22
22
|
"@databricks/sql": "^1.9.0",
|
|
23
23
|
"@elastic/elasticsearch": "^8.14.0",
|
|
24
24
|
"@slack/web-api": "^6.11.0",
|
|
25
|
-
"bottleneck": "^2.19.5",
|
|
26
25
|
"chalk": "^5.3.0",
|
|
27
26
|
"execa": "^9.3.0",
|
|
28
|
-
"express": "^4.
|
|
27
|
+
"express": "^4.21.2",
|
|
29
28
|
"md5": "^2.3.0",
|
|
30
29
|
"meow": "^13.2.0",
|
|
31
30
|
"snowflake-sdk": "^1.11.0",
|
|
31
|
+
"type-fest": "^4.30.2",
|
|
32
32
|
"web3": "^4.11.0",
|
|
33
33
|
"which": "^4.0.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
36
|
+
"@eslint/js": "^9.17.0",
|
|
37
|
+
"@types/bun": "^1.1.15",
|
|
38
|
+
"@types/express": "^5.0.0",
|
|
39
|
+
"@types/md5": "^2.3.5",
|
|
40
|
+
"@types/which": "^3.0.4",
|
|
41
|
+
"eslint": "^9.17.0",
|
|
38
42
|
"eslint-plugin-import": "^2.29.1",
|
|
39
43
|
"eslint-plugin-md": "^1.0.19",
|
|
44
|
+
"eslint-plugin-prettier": "^5.0.0",
|
|
40
45
|
"prettier": "^3.3.3",
|
|
41
|
-
"sinon": "^18.0.0"
|
|
46
|
+
"sinon": "^18.0.0",
|
|
47
|
+
"typescript": "^5.7.2",
|
|
48
|
+
"typescript-eslint": "^8.18.1"
|
|
42
49
|
},
|
|
43
50
|
"license": "MIT",
|
|
44
51
|
"publishConfig": {
|
|
45
52
|
"access": "public"
|
|
46
53
|
}
|
|
47
|
-
}
|
|
54
|
+
}
|
package/{s3.js → s3.ts}
RENAMED
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
ListObjectsV2Command,
|
|
8
8
|
NotFound,
|
|
9
9
|
NoSuchKey,
|
|
10
|
+
ObjectCannedACL,
|
|
10
11
|
} from "@aws-sdk/client-s3";
|
|
12
|
+
import type { PutObjectCommandInput, ListObjectsV2CommandInput, ListObjectsV2CommandOutput } from "@aws-sdk/client-s3";
|
|
11
13
|
|
|
12
|
-
let _s3Client;
|
|
14
|
+
let _s3Client: S3Client;
|
|
13
15
|
|
|
14
16
|
function getS3(forceNew = false) {
|
|
15
17
|
if (!_s3Client || forceNew) {
|
|
@@ -18,14 +20,14 @@ function getS3(forceNew = false) {
|
|
|
18
20
|
return _s3Client;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
async function readFile(bucketName, key, verbose = false) {
|
|
23
|
+
async function readFile(bucketName: string, key: string, verbose = false) {
|
|
22
24
|
const s3 = getS3();
|
|
23
25
|
|
|
24
26
|
const params = { Bucket: bucketName, Key: key };
|
|
25
27
|
|
|
26
28
|
try {
|
|
27
29
|
const result = await s3.send(new GetObjectCommand(params));
|
|
28
|
-
return result.Body
|
|
30
|
+
return result.Body?.transformToString();
|
|
29
31
|
} catch (error) {
|
|
30
32
|
if (error instanceof NoSuchKey) {
|
|
31
33
|
if (verbose) {
|
|
@@ -39,7 +41,7 @@ async function readFile(bucketName, key, verbose = false) {
|
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
async function hasFile(bucketName, key) {
|
|
44
|
+
async function hasFile(bucketName: string, key: string) {
|
|
43
45
|
const s3 = getS3();
|
|
44
46
|
|
|
45
47
|
try {
|
|
@@ -55,7 +57,7 @@ async function hasFile(bucketName, key) {
|
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
async function getFileSize(bucketName, key) {
|
|
60
|
+
async function getFileSize(bucketName: string, key: string) {
|
|
59
61
|
const s3 = getS3();
|
|
60
62
|
|
|
61
63
|
const result = await s3.send(
|
|
@@ -67,7 +69,17 @@ async function getFileSize(bucketName, key) {
|
|
|
67
69
|
return result.ContentLength;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
async function writeFile(
|
|
72
|
+
async function writeFile(
|
|
73
|
+
bucketName: string,
|
|
74
|
+
key: string,
|
|
75
|
+
body: string,
|
|
76
|
+
options: {
|
|
77
|
+
verbose?: boolean;
|
|
78
|
+
skipIfExists?: boolean;
|
|
79
|
+
acl?: ObjectCannedACL;
|
|
80
|
+
contentType?: string;
|
|
81
|
+
} = {},
|
|
82
|
+
) {
|
|
71
83
|
const s3 = getS3();
|
|
72
84
|
|
|
73
85
|
if (options.skipIfExists) {
|
|
@@ -78,7 +90,7 @@ async function writeFile(bucketName, key, body, options = {}) {
|
|
|
78
90
|
|
|
79
91
|
const verbose = options.verbose || false;
|
|
80
92
|
|
|
81
|
-
const params = { Bucket: bucketName, Key: key, Body: body };
|
|
93
|
+
const params: PutObjectCommandInput = { Bucket: bucketName, Key: key, Body: body };
|
|
82
94
|
|
|
83
95
|
if (options.acl) {
|
|
84
96
|
params.ACL = options.acl;
|
|
@@ -95,7 +107,7 @@ async function writeFile(bucketName, key, body, options = {}) {
|
|
|
95
107
|
await s3.send(new PutObjectCommand(params));
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
async function deleteFile(bucketName, key, verbose = false) {
|
|
110
|
+
async function deleteFile(bucketName: string, key: string, verbose = false) {
|
|
99
111
|
const s3 = getS3();
|
|
100
112
|
|
|
101
113
|
const params = { Bucket: bucketName, Key: key };
|
|
@@ -115,20 +127,20 @@ async function deleteFile(bucketName, key, verbose = false) {
|
|
|
115
127
|
}
|
|
116
128
|
}
|
|
117
129
|
|
|
118
|
-
async function listKeys(
|
|
130
|
+
async function listKeys(bucketName: string, prefix?: string, continuationToken?: string) {
|
|
119
131
|
const s3 = getS3();
|
|
120
132
|
|
|
121
|
-
const params = {
|
|
122
|
-
Bucket:
|
|
133
|
+
const params: ListObjectsV2CommandInput = {
|
|
134
|
+
Bucket: bucketName,
|
|
123
135
|
Prefix: prefix,
|
|
124
136
|
ContinuationToken: continuationToken,
|
|
125
137
|
};
|
|
126
138
|
|
|
127
|
-
let data
|
|
139
|
+
let data: ListObjectsV2CommandOutput;
|
|
128
140
|
try {
|
|
129
141
|
data = await s3.send(new ListObjectsV2Command(params));
|
|
130
142
|
} catch (err) {
|
|
131
|
-
if (err
|
|
143
|
+
if (err instanceof Error && "statusCode" in err && err.statusCode === 400) {
|
|
132
144
|
return null;
|
|
133
145
|
}
|
|
134
146
|
|
|
@@ -139,12 +151,10 @@ async function listKeys(bucketname, prefix, continuationToken) {
|
|
|
139
151
|
return { keys: [] };
|
|
140
152
|
}
|
|
141
153
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return res;
|
|
154
|
+
return {
|
|
155
|
+
keys: data.Contents.map(({ Key }) => Key),
|
|
156
|
+
continuationToken: data.IsTruncated ? data.NextContinuationToken : undefined,
|
|
157
|
+
};
|
|
148
158
|
}
|
|
149
159
|
|
|
150
160
|
export { getS3, hasFile, readFile, writeFile, deleteFile, getFileSize, listKeys };
|
package/search.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isProduction } from "./env.ts";
|
|
2
|
+
import { inline } from "./log.ts";
|
|
3
|
+
import { Client } from "@elastic/elasticsearch";
|
|
4
|
+
import osModule from "os";
|
|
5
|
+
|
|
6
|
+
type ElasticSearchRecord = {
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function sendToSearch(indexPrefix: string, record: ElasticSearchRecord, throws = false) {
|
|
11
|
+
if (!process.env["SKYNET_ELASTICSEARCH_CLOUD_ID"] || !process.env["SKYNET_ELASTICSEARCH_API_KEY"]) {
|
|
12
|
+
throw new Error("SKYNET_ELASTICSEARCH_CLOUD_ID or SKYNET_ELASTICSEARCH_API_KEY is not set");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const client = new Client({
|
|
16
|
+
cloud: { id: process.env["SKYNET_ELASTICSEARCH_CLOUD_ID"] },
|
|
17
|
+
auth: { apiKey: process.env["SKYNET_ELASTICSEARCH_API_KEY"] },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const now = new Date();
|
|
21
|
+
const indexName = [indexPrefix, isProduction() ? "prod" : "dev", now.toISOString().slice(0, 7)].join("-");
|
|
22
|
+
|
|
23
|
+
// best as possible delivery
|
|
24
|
+
// by default not throw
|
|
25
|
+
try {
|
|
26
|
+
await client.index({
|
|
27
|
+
index: indexName,
|
|
28
|
+
document: { instance: osModule.hostname(), timestamp: now, record },
|
|
29
|
+
});
|
|
30
|
+
} catch (err) {
|
|
31
|
+
inline.error(err);
|
|
32
|
+
|
|
33
|
+
if (throws) {
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/selector.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Flag, Result } from "meow";
|
|
2
|
+
|
|
3
|
+
type StringFlag = Flag<"string", string> | Flag<"string", string[], true>;
|
|
4
|
+
type BooleanFlag = Flag<"boolean", boolean> | Flag<"boolean", boolean[], true>;
|
|
5
|
+
type NumberFlag = Flag<"number", number> | Flag<"number", number[], true>;
|
|
6
|
+
type AnyFlag = StringFlag | BooleanFlag | NumberFlag;
|
|
7
|
+
|
|
8
|
+
export type Selector = Record<
|
|
9
|
+
string,
|
|
10
|
+
AnyFlag & { desc?: string; description?: string; optional?: boolean; aliases?: string[] }
|
|
11
|
+
>;
|
|
12
|
+
export type SelectorFlags<TSelector extends Selector> = Result<TSelector>["flags"];
|
|
13
|
+
|
|
14
|
+
function getSelectorDesc(selector: Selector) {
|
|
15
|
+
return Object.keys(selector)
|
|
16
|
+
.map((name) => {
|
|
17
|
+
return ` --${name.padEnd(14)}${selector[name].desc || selector[name].description || ""}`;
|
|
18
|
+
})
|
|
19
|
+
.join("\n");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// check https://github.com/sindresorhus/meow for selector config options
|
|
23
|
+
function getSelectorFlags<TSelector extends Selector>(selector: Selector): TSelector {
|
|
24
|
+
return Object.keys(selector).reduce((acc, name) => {
|
|
25
|
+
const flag = {
|
|
26
|
+
type: selector[name].type || "string",
|
|
27
|
+
...selector[name],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// by default to be required
|
|
31
|
+
if (!selector[name].optional && selector[name].isRequired !== false) {
|
|
32
|
+
flag.isRequired = true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { ...acc, [name]: flag };
|
|
36
|
+
}, {} as TSelector);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function toSelectorString<TSelector extends Selector>(selectorFlags: SelectorFlags<TSelector>, delim = ",") {
|
|
40
|
+
return Object.keys(selectorFlags)
|
|
41
|
+
.sort() // deterministic
|
|
42
|
+
.map((flag) => {
|
|
43
|
+
return `${flag}=${selectorFlags[flag]}`;
|
|
44
|
+
})
|
|
45
|
+
.join(delim);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeSelectorValue(v: string) {
|
|
49
|
+
return v.replace(/[^A-Za-z0-9]+/g, "-");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getJobName<TSelector extends Selector>(name: string, selectorFlags: SelectorFlags<TSelector>, mode?: string) {
|
|
53
|
+
const selectorNamePart = Object.keys(selectorFlags)
|
|
54
|
+
.sort()
|
|
55
|
+
.map((name) => selectorFlags[name])
|
|
56
|
+
.join("-");
|
|
57
|
+
|
|
58
|
+
let jobName = name;
|
|
59
|
+
|
|
60
|
+
if (mode) {
|
|
61
|
+
jobName += `-${mode}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (selectorNamePart.length > 0) {
|
|
65
|
+
// handle special case
|
|
66
|
+
jobName += `-${normalizeSelectorValue(selectorNamePart)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return jobName;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { getJobName, getSelectorDesc, getSelectorFlags, toSelectorString };
|
package/{slack.js → slack.ts}
RENAMED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
import { WebClient } from "@slack/web-api";
|
|
2
|
+
import type { ChatPostMessageArguments } from "@slack/web-api";
|
|
2
3
|
|
|
3
|
-
function getClient(token) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return client;
|
|
4
|
+
function getClient(token?: string) {
|
|
5
|
+
return new WebClient(token);
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
// you can identify conversation ID at the bottom of the channel settings page
|
|
10
|
-
async function postMessageToConversation({
|
|
9
|
+
async function postMessageToConversation({
|
|
10
|
+
conversationId,
|
|
11
|
+
message,
|
|
12
|
+
token,
|
|
13
|
+
verbose,
|
|
14
|
+
}: {
|
|
15
|
+
conversationId: string;
|
|
16
|
+
message: string | Partial<ChatPostMessageArguments>;
|
|
17
|
+
token?: string;
|
|
18
|
+
verbose?: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
if (!token) {
|
|
21
|
+
throw new Error("missing slack token");
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
try {
|
|
12
25
|
const client = getClient(token);
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (typeof message === "string") {
|
|
17
|
-
post.text = message;
|
|
18
|
-
} else {
|
|
19
|
-
post = message;
|
|
20
|
-
}
|
|
27
|
+
const post = typeof message === "string" ? { text: message } : message;
|
|
21
28
|
|
|
22
29
|
if (verbose) {
|
|
23
30
|
console.log(`posting to slack conversation ${conversationId}:`, JSON.stringify(post, null, 2));
|
package/snowflake.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import snowflake from "snowflake-sdk";
|
|
2
|
+
import type { ConnectionOptions, Connection, Binds } from "snowflake-sdk";
|
|
3
|
+
import { ensureAndGet } from "./env.js";
|
|
4
|
+
|
|
5
|
+
async function getConnection(options: Partial<ConnectionOptions>) {
|
|
6
|
+
const account = ensureAndGet("SKYNET_SNOWFLAKE_ACCOUNT");
|
|
7
|
+
const username = ensureAndGet("SKYNET_SNOWFLAKE_USERNAME");
|
|
8
|
+
const password = ensureAndGet("SKYNET_SNOWFLAKE_PASSWORD");
|
|
9
|
+
|
|
10
|
+
if (!account || !username || !password) {
|
|
11
|
+
throw new Error("missing snowflake credentials");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const connection = snowflake.createConnection({
|
|
15
|
+
account,
|
|
16
|
+
username,
|
|
17
|
+
password,
|
|
18
|
+
...options,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return new Promise<Connection>((resolve, reject) => {
|
|
22
|
+
connection.connect((err, conn) => {
|
|
23
|
+
if (err) {
|
|
24
|
+
reject(err);
|
|
25
|
+
} else {
|
|
26
|
+
resolve(conn);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function executeSql<T>(options: ConnectionOptions, sql: string, binds?: Binds) {
|
|
33
|
+
const connection = await getConnection(options);
|
|
34
|
+
|
|
35
|
+
return await new Promise<T[] | undefined>((resolve, reject) => {
|
|
36
|
+
connection.execute({
|
|
37
|
+
sqlText: sql,
|
|
38
|
+
binds: binds,
|
|
39
|
+
complete: (err, statement, rows) => {
|
|
40
|
+
// console.log(statement.getSqlText());
|
|
41
|
+
if (err) {
|
|
42
|
+
reject(err);
|
|
43
|
+
} else {
|
|
44
|
+
resolve(rows);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { getConnection, executeSql };
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Enable latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
|
|
10
|
+
// Bundler mode
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
// Best practices
|
|
17
|
+
"strict": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
|
|
21
|
+
// Some stricter flags
|
|
22
|
+
"noUnusedLocals": true,
|
|
23
|
+
"noUnusedParameters": true,
|
|
24
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
25
|
+
},
|
|
26
|
+
}
|
package/util.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Inclusive range
|
|
2
|
+
function range(startAt: number, endAt: number, step: number) {
|
|
3
|
+
const arr: [number, number][] = [];
|
|
4
|
+
|
|
5
|
+
for (let i = startAt; i <= endAt; i += step) {
|
|
6
|
+
arr.push([i, Math.min(endAt, i + step - 1)]);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return arr;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function arrayGroup<T>(array: T[], groupSize: number) {
|
|
13
|
+
const groups: T[][] = [];
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < array.length; i += groupSize) {
|
|
16
|
+
groups.push(array.slice(i, i + groupSize));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return groups;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// return an array with numbers of given inclusive range
|
|
23
|
+
// given 1, 5
|
|
24
|
+
// return [1, 2, 3, 4, 5]
|
|
25
|
+
function fillRange(start: number, end: number) {
|
|
26
|
+
const result: number[] = [];
|
|
27
|
+
|
|
28
|
+
for (let i = start; i <= end; i++) {
|
|
29
|
+
result.push(i);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { arrayGroup, range, fillRange };
|
package/web3.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Web3 } from "web3";
|
|
2
|
+
import type { AbiFunctionFragment } from "web3";
|
|
3
|
+
import { PROTOCOLS } from "./const.ts";
|
|
4
|
+
|
|
5
|
+
function newWeb3ByProtocol(protocol: string) {
|
|
6
|
+
if (!Object.keys(PROTOCOLS).includes(protocol)) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return new Web3(PROTOCOLS[protocol].endpoint);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeCallParams<T>(params?: T) {
|
|
14
|
+
if (params === undefined) {
|
|
15
|
+
return [] as T[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(params)) {
|
|
19
|
+
return params as T[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return [params];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function singleCall<TData, TParams>(protocol: string, abi: AbiFunctionFragment, target: string, params: TParams) {
|
|
26
|
+
const web3 = newWeb3ByProtocol(protocol);
|
|
27
|
+
|
|
28
|
+
if (!web3) {
|
|
29
|
+
throw new Error(`unsupported protocol`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const contract = new web3.eth.Contract([abi], target);
|
|
33
|
+
const functionSignature = web3.eth.abi.encodeFunctionSignature(abi);
|
|
34
|
+
const method = contract.methods[functionSignature];
|
|
35
|
+
|
|
36
|
+
const result = await method(...normalizeCallParams(params)).call<TData>();
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { singleCall };
|
package/.eslintignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*.d.ts
|
package/.eslintrc.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": ["eslint:recommended", "plugin:import/recommended", "plugin:md/prettier"],
|
|
3
|
-
"parserOptions": {
|
|
4
|
-
"ecmaVersion": 2023
|
|
5
|
-
},
|
|
6
|
-
"env": {
|
|
7
|
-
"node": true,
|
|
8
|
-
"browser": false,
|
|
9
|
-
"es2023": true
|
|
10
|
-
},
|
|
11
|
-
"settings": {
|
|
12
|
-
"import/core-modules": ["ava", "meow"],
|
|
13
|
-
"import/resolver": {
|
|
14
|
-
"node": {
|
|
15
|
-
"extensions": [".js", ".jsx", ".ts", ".tsx"]
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"rules": {}
|
|
20
|
-
}
|
package/abi.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
type ContractIO = {
|
|
2
|
-
name: string;
|
|
3
|
-
type: string;
|
|
4
|
-
internalType: string | undefined;
|
|
5
|
-
components: ContractIO[];
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
type ABI = {
|
|
9
|
-
constant: boolean;
|
|
10
|
-
inputs: ContractIO[];
|
|
11
|
-
name: string;
|
|
12
|
-
outputs: ContractIO[];
|
|
13
|
-
payable: boolean;
|
|
14
|
-
stateMutability: string;
|
|
15
|
-
type: string;
|
|
16
|
-
}[];
|
|
17
|
-
|
|
18
|
-
export const ERC20: ABI;
|
|
19
|
-
export const MULTICALL: ABI;
|
|
20
|
-
export const BEP20: ABI;
|
package/address.d.ts
DELETED
package/api.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Selector } from "./selector";
|
|
2
|
-
import { Route, Serve } from "./app";
|
|
3
|
-
import express from "express";
|
|
4
|
-
|
|
5
|
-
export function startApiApp(params: {
|
|
6
|
-
binaryName: string;
|
|
7
|
-
name: string;
|
|
8
|
-
selector?: Selector;
|
|
9
|
-
routes: Route[];
|
|
10
|
-
serve: Serve;
|
|
11
|
-
beforeListen: (args: { app: ReturnType<typeof express> }) => void;
|
|
12
|
-
}): void;
|