@certik/skynet 0.12.0 → 0.13.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.1
4
+
5
+ - Added: s3 getFileSize (@certik/skynet/s3)
6
+
7
+ ## 0.13.0
8
+
9
+ - BREAKING: removed ably library (@certik/skynet/ably)
10
+ - BREAKING: removed metric library (@certik/skynet/metric)
11
+ - BREAKING: removed scan library (@certik/skynet/scan)
12
+ - BREAKING: redesigned slack library (@certik/skynet/slack)
13
+ - Upgraded: @aws-sdk to support new S3 directory bucket capability
14
+ - Deprecated: kafka / web3
15
+
3
16
  ## 0.12.0
4
17
 
5
18
  - BREAKING: removed token module (@certik/skynet/token)
package/bun.lockb CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@certik/skynet",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "Skynet Shared JS library",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -15,13 +15,12 @@
15
15
  "node": ">= 18"
16
16
  },
17
17
  "dependencies": {
18
- "@aws-sdk/client-dynamodb": "^3.449.0",
19
- "@aws-sdk/client-s3": "^3.449.0",
20
- "@aws-sdk/client-sqs": "^3.449.0",
21
- "@aws-sdk/lib-dynamodb": "^3.449.0",
18
+ "@aws-sdk/client-dynamodb": "^3.478.0",
19
+ "@aws-sdk/client-s3": "^3.478.0",
20
+ "@aws-sdk/client-sqs": "^3.478.0",
21
+ "@aws-sdk/lib-dynamodb": "^3.478.0",
22
22
  "@opensearch-project/opensearch": "^2.4.0",
23
- "@slack/web-api": "^6.9.1",
24
- "ably": "^1.2.47",
23
+ "@slack/web-api": "^6.11.0",
25
24
  "bottleneck": "^2.19.5",
26
25
  "chalk": "^5.3.0",
27
26
  "execa": "^8.0.1",
@@ -29,16 +28,16 @@
29
28
  "kafkajs": "^2.2.4",
30
29
  "md5": "^2.3.0",
31
30
  "meow": "^12.1.1",
32
- "snowflake-sdk": "^1.9.0",
33
- "web3": "^4.2.2",
31
+ "snowflake-sdk": "^1.9.2",
32
+ "web3": "^4.3.0",
34
33
  "which": "^4.0.0"
35
34
  },
36
35
  "devDependencies": {
37
- "ava": "^5.3.1",
38
- "eslint": "^8.49.0",
39
- "eslint-plugin-import": "^2.28.1",
40
- "prettier": "^3.0.3",
41
- "sinon": "^15.2.0"
36
+ "ava": "^6.0.1",
37
+ "eslint": "^8.56.0",
38
+ "eslint-plugin-import": "^2.29.1",
39
+ "prettier": "^3.1.1",
40
+ "sinon": "^17.0.1"
42
41
  },
43
42
  "license": "MIT",
44
43
  "publishConfig": {
package/s3.js CHANGED
@@ -46,6 +46,18 @@ async function hasFile(bucketName, key) {
46
46
  }
47
47
  }
48
48
 
49
+ async function getFileSize(bucketName, key) {
50
+ const s3 = getS3();
51
+
52
+ const result = await s3.send(
53
+ new HeadObjectCommand({
54
+ Bucket: bucketName,
55
+ Key: key,
56
+ }),
57
+ );
58
+ return result.ContentLength;
59
+ }
60
+
49
61
  async function writeFile(bucketName, key, body, options = {}) {
50
62
  const s3 = getS3();
51
63
 
@@ -116,4 +128,4 @@ async function listKeys(bucketname, prefix, continuationToken) {
116
128
  return res;
117
129
  }
118
130
 
119
- export { getS3, hasFile, readFile, writeFile, deleteFile, listKeys };
131
+ export { getS3, hasFile, readFile, writeFile, deleteFile, getFileSize, listKeys };
package/slack.js CHANGED
@@ -1,71 +1,16 @@
1
1
  import { WebClient } from "@slack/web-api";
2
2
 
3
- function getToken() {
4
- return process.env.SKYNET_SLACK_TOKEN;
5
- }
6
-
7
- function getClient(t) {
8
- const token = t || getToken();
9
-
10
- if (!token) {
11
- throw new Error("Cannot communicate with slack due to missing slack token: process.env.SKYNET_SLACK_TOKEN");
12
- }
13
-
3
+ function getClient(token) {
14
4
  const client = new WebClient(token);
15
5
 
16
6
  return client;
17
7
  }
18
8
 
19
- async function findConversation(client, name) {
20
- const { conversations } = client;
21
-
22
- let channels = [];
23
-
24
- let result = await conversations.list({
25
- types: "public_channel,private_channel",
26
- limit: 1000,
27
- });
28
-
29
- channels = channels.concat(result.channels);
30
-
31
- while (result.response_metadata.next_cursor) {
32
- result = await conversations.list({
33
- types: "public_channel,private_channel",
34
- cursor: result.response_metadata.next_cursor,
35
- limit: 1000,
36
- });
37
-
38
- channels = channels.concat(result.channels);
39
- }
40
-
41
- for (const channel of channels) {
42
- if (channel.name === name) {
43
- const conversationId = channel.id;
44
-
45
- return conversationId;
46
- }
47
- }
48
-
49
- return null;
50
- }
51
-
52
- function isString(s) {
53
- return typeof s === "string" || s instanceof String;
54
- }
55
-
56
- async function postMessage(channel, message, verbose) {
9
+ // you can identify conversation ID at the bottom of the channel settings page
10
+ async function postMessageToConversation({ conversationId, message, token, verbose }) {
57
11
  try {
58
- const token = isString(channel) ? null : channel.token;
59
12
  const client = getClient(token);
60
13
 
61
- let conversationId = isString(channel) ? await findConversation(client, channel) : channel.id;
62
-
63
- if (!conversationId) {
64
- throw new Error(
65
- `cannot find slack public/private channel: ${channel}, you may have to invite the @CertiK Skynet bot to the channel`
66
- );
67
- }
68
-
69
14
  let post = {};
70
15
 
71
16
  if (typeof message === "string") {
@@ -75,7 +20,7 @@ async function postMessage(channel, message, verbose) {
75
20
  }
76
21
 
77
22
  if (verbose) {
78
- console.log(`posting to slack channel ${channel} (${conversationId}):`, JSON.stringify(post, null, 2));
23
+ console.log(`posting to slack conversation ${conversationId}:`, JSON.stringify(post, null, 2));
79
24
  }
80
25
 
81
26
  await client.chat.postMessage({
@@ -89,4 +34,4 @@ async function postMessage(channel, message, verbose) {
89
34
  }
90
35
  }
91
36
 
92
- export { getClient, findConversation, postMessage };
37
+ export { postMessageToConversation };
package/ably.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import Bottleneck from "bottleneck";
2
-
3
- export function publishMessage(channel: string, messageName: string, messageData: any): Promise<void>;
4
- export const rateLimitedPublishMessage: Bottleneck;
package/ably.js DELETED
@@ -1,35 +0,0 @@
1
- import Ably from "ably";
2
- import Bottleneck from "bottleneck/es5";
3
- import { ensureAndGet } from "./env.js";
4
-
5
- let ABLY;
6
-
7
- async function getAbly() {
8
- if (!ABLY) {
9
- ABLY = await new Promise((resolve) => {
10
- const ably = new Ably.Realtime(ensureAndGet("ABLY_ROOT_API_KEY"));
11
- ably.connection.on("connected", () => {
12
- console.log("ably connection successful");
13
- resolve(ably);
14
- });
15
- });
16
- }
17
- return ABLY;
18
- }
19
-
20
- export async function publishMessage(channel, messageName, messageData) {
21
- const ably = await getAbly();
22
- const channelObj = ably.channels.get(channel);
23
-
24
- const messageDataJson = JSON.stringify(messageData);
25
-
26
- try {
27
- await channelObj.publish(messageName, messageDataJson);
28
- } catch (error) {
29
- console.error("error publishing to ably: ", error);
30
- }
31
- }
32
-
33
- export const rateLimitedPublishMessage = new Bottleneck({
34
- minTime: 1000 / 40,
35
- }).wrap(publishMessage);
package/metric.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export function getMetricAt(
2
- tableName: string,
3
- name: string,
4
- timestamp: number
5
- ): Promise<any>;
6
- export function getMetricPreviousValue(
7
- tableName: string,
8
- name: string,
9
- timestamp: number
10
- ): Promise<any>;
package/metric.js DELETED
@@ -1,60 +0,0 @@
1
- import { getDocClient } from "./dynamodb.js";
2
-
3
- /* Assume table has name/timestamp/value fields */
4
-
5
- export async function getMetricAt(tableName, name, timestamp) {
6
- const docClient = getDocClient();
7
-
8
- const query = await docClient
9
- .query({
10
- TableName: tableName,
11
- KeyConditionExpression: "#name = :name and #timestamp <= :timestamp",
12
- ExpressionAttributeNames: {
13
- "#name": "name",
14
- "#timestamp": "timestamp"
15
- },
16
- ExpressionAttributeValues: {
17
- ":name": name,
18
- ":timestamp": timestamp
19
- },
20
- Limit: 1,
21
- ScanIndexForward: false
22
- })
23
- .promise();
24
-
25
- if (query.Count > 0) {
26
- const { value } = query.Items[0];
27
-
28
- return value;
29
- } else {
30
- return null;
31
- }
32
- }
33
-
34
- export async function getMetricPreviousValue(tableName, name, timestamp) {
35
- const docClient = getDocClient();
36
-
37
- const query = await docClient
38
- .query({
39
- TableName: tableName,
40
- KeyConditionExpression: "#name = :name and #timestamp < :timestamp",
41
- ExpressionAttributeNames: {
42
- "#timestamp": "timestamp",
43
- "#name": "name"
44
- },
45
- ExpressionAttributeValues: {
46
- ":timestamp": timestamp,
47
- ":name": name
48
- },
49
- Limit: 1,
50
- ScanIndexForward: false
51
- })
52
- .promise();
53
-
54
- if (query.Count === 0) {
55
- return 0;
56
- } else {
57
- const { value } = query.Items[0];
58
- return value;
59
- }
60
- }
package/scan.d.ts DELETED
@@ -1,47 +0,0 @@
1
- type Transaction = {
2
- blockNumber: string,
3
- timeStamp: string,
4
- hash: string,
5
- nonce: string,
6
- blockHash: string,
7
- transactionIndex: string,
8
- from: string,
9
- to: string,
10
- value: string | undefined,
11
- gas: string,
12
- gasPrice: string,
13
- isError: string,
14
- txreceipt_status: string,
15
- input: string,
16
- contractAddress: string,
17
- cumulativeGasUsed: string,
18
- gasUsed: string,
19
- confirmations: string,
20
- methodId: string,
21
- functionName: string
22
- }
23
-
24
- export function streamTxs(
25
- address: string,
26
- since: number | undefined,
27
- to: number | undefined,
28
- callback: (tx: Transaction) => void,
29
- verbose: boolean
30
- ): Promise<void>;
31
-
32
- export function fetchTxs(
33
- protocol: string,
34
- addr: string,
35
- since: number | undefined,
36
- to: number | undefined,
37
- offset: number | undefined,
38
- ): Promise<Transaction[] | null>;
39
-
40
- export function fetchTxsWithRetry(
41
- protocol: string,
42
- addr: string,
43
- since: number | undefined,
44
- to: number | undefined,
45
- offset: number | undefined,
46
- verbose: boolean
47
- ): Promise<Transaction[] | null>
package/scan.js DELETED
@@ -1,74 +0,0 @@
1
- import { exponentialRetry, wait } from "./availability.js";
2
- import { PROTOCOLS } from "./const.js";
3
-
4
- const BATCH_SIZE = 10_000; // Max number of transactions fetched from scan api at once
5
-
6
- function getScanApiEndpoint(protocol, addr, startBlock = 0, endBlock = null, offset=BATCH_SIZE) {
7
- const { endpoint, key } = PROTOCOLS[protocol].scanApi;
8
- let url = `${endpoint}?module=account&action=txlist&address=${addr}&apikey=${key}&sort=asc&startblock=${startBlock}&page=1&offset=${offset}`;
9
-
10
- if (endBlock) {
11
- url += `&endblock=${endBlock}`;
12
- }
13
-
14
- return url;
15
- }
16
-
17
- async function fetchTxs(protocol, addr, since, to, offset) {
18
- const url = getScanApiEndpoint(protocol, addr, since, to, offset);
19
-
20
- try {
21
- const res = await fetch(url);
22
- const scanJSON = await res.json();
23
- return scanJSON.result;
24
- } catch (err) {
25
- console.log(`error fetching txs: ${err}`)
26
- return null;
27
- }
28
- }
29
-
30
- async function fetchTxsWithRetry(protocol, addr, since, to, offset, verbose) {
31
- const txs = await exponentialRetry(
32
- async () => fetchTxs(protocol, addr, since, to, offset),
33
- { maxRetry: 6, test: Array.isArray, verbose }
34
- )
35
-
36
- return txs;
37
- }
38
-
39
- async function streamTxs(address, since, to, callback, verbose) {
40
- let startBlock = since;
41
- let hasMorePage = true;
42
- let lastTxHash = null;
43
-
44
- const [protocol, addr] = address.split(":");
45
-
46
- while (hasMorePage) {
47
- const txs = await fetchTxs(protocol, addr, startBlock, to, BATCH_SIZE, verbose);
48
- console.log(`start block=${since}, total items=${txs.length}`);
49
-
50
- // This will only ever happen if the total number of txs is a multiple of our batch size
51
- if (txs.length === 0) {
52
- break;
53
- }
54
-
55
- // If our start block overlaps with the last request's end block, then we may have some overlapping txs
56
- // To avoid adding those txs to the db twice, we filter them out
57
- const newTxsStartIdx = txs.indexOf(({ hash }) => hash === lastTxHash);
58
-
59
- const newTxs = txs.slice(newTxsStartIdx !== -1 ? newTxsStartIdx : 0);
60
-
61
- for (const tx of newTxs) {
62
- callback(tx);
63
- }
64
-
65
- hasMorePage = txs.length === BATCH_SIZE;
66
-
67
- startBlock = parseInt(txs[txs.length - 1].blockNumber, 10) + 1;
68
- lastTxHash = txs[txs.length - 1].hash;
69
-
70
- await wait(2000);
71
- }
72
- }
73
-
74
- export { streamTxs, fetchTxs, fetchTxsWithRetry };