@certik/skynet 0.9.5 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0
4
+
5
+ - Added a new application type `api` to the `app` library
6
+
7
+ ## 0.9.7
8
+
9
+ - Added Polygon multicall support
10
+
11
+ ## 0.9.6
12
+
13
+ - Updated `inline` method in `log` library to print a timestamp in front of each log line
14
+
3
15
  ## 0.9.5
4
16
 
5
17
  - Fixed deploy issue when selector value contains special characters
package/api.js ADDED
@@ -0,0 +1,105 @@
1
+ const express = require("express");
2
+ const meow = require("meow");
3
+ const { getSelectorFlags, getSelectorDesc } = require("./selector");
4
+ const { isProduction } = require("./env");
5
+
6
+ const apiKeyMiddleware = (key) => {
7
+ async function requireAPIKey(req, res, next) {
8
+ try {
9
+ const apiKey = req.get("x-api-key");
10
+
11
+ if (!apiKey) {
12
+ console.log("request without api key");
13
+
14
+ res.status(400).send("require x-api-key header");
15
+
16
+ return;
17
+ }
18
+
19
+ if (apiKey !== key) {
20
+ console.log("request has an invalid api key");
21
+
22
+ res.status(400).send("invalid api key");
23
+
24
+ return;
25
+ }
26
+
27
+ next();
28
+ } catch (err) {
29
+ console.log("check api key error", err);
30
+
31
+ res.status(500).send("internal error");
32
+ }
33
+ }
34
+
35
+ return requireAPIKey;
36
+ };
37
+
38
+ function startApiApp({ binaryName, name, selector = {}, routes, serve }) {
39
+ const app = express();
40
+ app.use(express.json());
41
+
42
+ const cli = meow(
43
+ `
44
+ Usage
45
+ $ ${binaryName} <options>
46
+
47
+ Options
48
+ ${getSelectorDesc(selector)}
49
+ --verbose Output debug messages
50
+ `,
51
+ {
52
+ description: false,
53
+ version: false,
54
+ flags: {
55
+ ...getSelectorFlags(selector),
56
+ verbose: {
57
+ type: "boolean",
58
+ default: false,
59
+ },
60
+ },
61
+ }
62
+ );
63
+
64
+ const { verbose, ...selectorFlags } = cli.flags;
65
+
66
+ // for health check
67
+ app.get("/", (req, res) => {
68
+ res.send("ok");
69
+ });
70
+
71
+ for (const route of routes) {
72
+ const method = route.method ? route.method.toLowerCase() : "get";
73
+ const middlewares = route.middlewares || [];
74
+
75
+ if (route.protected) {
76
+ middlewares.push(apiKeyMiddleware(serve.apiKey));
77
+ }
78
+
79
+ if (app[method]) {
80
+ if (verbose) {
81
+ console.log(`registering ${method} ${route.path}`);
82
+ }
83
+
84
+ app[method](route.path, ...middlewares, async (req, res) => {
85
+ try {
86
+ await route.handler({ req, res, ...selectorFlags });
87
+ } catch (routeErr) {
88
+ console.log("caught route err", routeErr);
89
+
90
+ res.status(500).send(`internal server error: ${routeErr.message}`);
91
+ }
92
+ });
93
+ }
94
+ }
95
+
96
+ app.listen(serve.port, () => {
97
+ if (isProduction()) {
98
+ console.log(`${name} listening at https://api.certik-skynet.com/${serve.prefix}`);
99
+ } else {
100
+ console.log(`${name} listening at http://localhost:${serve.port}`);
101
+ }
102
+ });
103
+ }
104
+
105
+ module.exports = { startApiApp };
package/app.js CHANGED
@@ -4,6 +4,7 @@ const { SKYNET_API_PREFIX } = require("./const");
4
4
  const { createIndexerApp, createModeIndexerApp } = require("./indexer");
5
5
  const { createDeploy, createModeDeploy } = require("./deploy");
6
6
  const { createProducerApp, createConsumerApp } = require("./kafka");
7
+ const { startApiApp } = require("./api");
7
8
  const { createMonitor, ERROR_LEVEL } = require("./monitor");
8
9
  const { getBinaryName, detectBin, detectWorkingDirectory } = require("./cli");
9
10
 
@@ -202,7 +203,7 @@ function indexer({ name, selector, build, check, env = {}, region = "us-east-1"
202
203
  bin: `${bin} check`,
203
204
  schedule: check.schedule,
204
205
  cpu: check.cpu || 100,
205
- mem: check.mem || 100
206
+ mem: check.mem || 100,
206
207
  },
207
208
  });
208
209
 
@@ -215,7 +216,7 @@ function indexer({ name, selector, build, check, env = {}, region = "us-east-1"
215
216
  type: "stateless",
216
217
  selector,
217
218
  check: check.func,
218
- maxRetry: check.maxRetry
219
+ maxRetry: check.maxRetry,
219
220
  });
220
221
 
221
222
  monitor();
@@ -315,7 +316,7 @@ function modeIndexer({ name, selector, state, build, validate, check, env = {},
315
316
  bin: `${bin} check`,
316
317
  schedule: check.schedule,
317
318
  cpu: check.cpu || 100,
318
- mem: check.mem || 100
319
+ mem: check.mem || 100,
319
320
  },
320
321
  });
321
322
 
@@ -329,7 +330,7 @@ function modeIndexer({ name, selector, state, build, validate, check, env = {},
329
330
  selector,
330
331
  mode: true,
331
332
  check: check.func,
332
- maxRetry: check.maxRetry
333
+ maxRetry: check.maxRetry,
333
334
  });
334
335
 
335
336
  monitor();
@@ -417,7 +418,7 @@ function producer({ name, selector, produce, check, state, env = {}, region = "u
417
418
  bin: `${bin} check`,
418
419
  schedule: check.schedule,
419
420
  cpu: check.cpu || 100,
420
- mem: check.mem || 100
421
+ mem: check.mem || 100,
421
422
  },
422
423
  });
423
424
 
@@ -430,7 +431,7 @@ function producer({ name, selector, produce, check, state, env = {}, region = "u
430
431
  type: "stateful",
431
432
  selector,
432
433
  check: check.func,
433
- maxRetry: check.maxRetry
434
+ maxRetry: check.maxRetry,
434
435
  });
435
436
 
436
437
  monitor();
@@ -509,7 +510,7 @@ function consumer({ name, selector, consume, check, env = {}, region = "us-east-
509
510
  bin: `${bin} check`,
510
511
  schedule: check.schedule,
511
512
  cpu: check.cpu || 100,
512
- mem: check.mem || 100
513
+ mem: check.mem || 100,
513
514
  },
514
515
  });
515
516
 
@@ -522,7 +523,147 @@ function consumer({ name, selector, consume, check, env = {}, region = "us-east-
522
523
  type: "stateless",
523
524
  selector,
524
525
  check: check.func,
525
- maxRetry: check.maxRetry
526
+ maxRetry: check.maxRetry,
527
+ });
528
+
529
+ monitor();
530
+ },
531
+ });
532
+ }
533
+
534
+ function checkApiServeParameter(serve, routes) {
535
+ const errors = [];
536
+
537
+ if (!serve?.prefix) {
538
+ errors.push("must define serve.prefix");
539
+ } else if (!serve.prefix.startsWith("/")) {
540
+ errors.push("server.prefix must start with /, e.g. /my-api");
541
+ }
542
+
543
+ if (!serve?.port) {
544
+ errors.push("must define serve.port");
545
+ }
546
+
547
+ if (!serve?.cpu) {
548
+ errors.push("must define serve.cpu");
549
+ }
550
+
551
+ if (!serve?.mem) {
552
+ errors.push("must define serve.mem");
553
+ }
554
+
555
+ if (routes.some((r) => r.protected) && !serve.apiKey) {
556
+ errors.push("must define serve.apiKey since some routes are protected");
557
+ }
558
+
559
+ return errors;
560
+ }
561
+
562
+ function checkApiRoutesParameter(routes) {
563
+ const errors = [];
564
+
565
+ if (!Array.isArray(routes)) {
566
+ errors.push("routes must be an array");
567
+ } else {
568
+ for (let i = 0; i < routes.length; i++) {
569
+ const route = routes[i];
570
+
571
+ if (!route.path) {
572
+ errors.push(`routes[${i}] must define path`);
573
+ }
574
+
575
+ if (!route.handler) {
576
+ errors.push(`routes[${i}] must define handler`);
577
+ }
578
+
579
+ if (route.middlewares && !Array.isArray(route.middlewares)) {
580
+ errors.push(`routes[${i}].middlewares must be an array`);
581
+ }
582
+ }
583
+ }
584
+
585
+ return errors;
586
+ }
587
+
588
+ function api({ name, routes, serve, env = {}, region = "us-east-1" }) {
589
+ // do not support selector for now
590
+ const selector = {};
591
+
592
+ // hard code a simple health check for now
593
+ const check = {
594
+ func: async () => {
595
+ const errors = [];
596
+
597
+ const url = `${serve.prefix}/`;
598
+ const res = await fetch(url);
599
+
600
+ if (!res.ok) {
601
+ errors.push({
602
+ type: ERROR_LEVEL.ERROR,
603
+ message: `service ${name} is not healthy`,
604
+ });
605
+ }
606
+
607
+ return errors;
608
+ },
609
+ schedule: every(5).minutes,
610
+ maxRetry: 0,
611
+ };
612
+
613
+ return createApp({
614
+ parameterErrors: [
615
+ ...checkApiRoutesParameter(routes),
616
+ ...checkApiServeParameter(serve, routes),
617
+ ...checkCheckParameter(check),
618
+ ...checkEnvParameter(env),
619
+ ],
620
+ env,
621
+ check,
622
+ onRun: () => {
623
+ startApiApp({
624
+ binaryName: `${getBinaryName()} run`,
625
+ name,
626
+ selector,
627
+ routes,
628
+ serve,
629
+ });
630
+ },
631
+ onDeploy: () => {
632
+ const bin = detectBin();
633
+
634
+ const { deploy } = createDeploy({
635
+ binaryName: `${getBinaryName()} deploy`,
636
+ name,
637
+ workingDirectory: detectWorkingDirectory(),
638
+ bin: `${bin} run`,
639
+ selector,
640
+ region,
641
+ env,
642
+ schedule: "@minutely",
643
+ cpu: serve.cpu,
644
+ mem: serve.mem,
645
+ service: {
646
+ prefix: serve.prefix,
647
+ port: serve.port,
648
+ },
649
+ check: check && {
650
+ bin: `${bin} check`,
651
+ schedule: check.schedule,
652
+ cpu: check.cpu || 100,
653
+ mem: check.mem || 100,
654
+ },
655
+ });
656
+
657
+ deploy();
658
+ },
659
+ onCheck: () => {
660
+ const { monitor } = createMonitor({
661
+ binaryName: `${getBinaryName()} check`,
662
+ name,
663
+ type: "stateless",
664
+ selector,
665
+ check: check.func,
666
+ maxRetry: check.maxRetry,
526
667
  });
527
668
 
528
669
  monitor();
@@ -551,4 +692,4 @@ const every = (n = 1) => {
551
692
  };
552
693
  };
553
694
 
554
- module.exports = { indexer, modeIndexer, producer, consumer, every, SENSITIVE_VALUE, ERROR_LEVEL };
695
+ module.exports = { indexer, modeIndexer, producer, consumer, api, every, SENSITIVE_VALUE, ERROR_LEVEL };
package/const.js CHANGED
@@ -59,7 +59,7 @@ const PROTOCOLS = {
59
59
  endpoint: "https://api.polygonscan.com/api",
60
60
  key: getPolygonScanApiKey(),
61
61
  },
62
- multiCallProvider: "", // TODO
62
+ multiCallProvider: "0x8eC86392e0aDB57d00fDffbA39b8870e107c0757",
63
63
  scanUrl: "https://polygonscan.com/",
64
64
  },
65
65
  };
package/deploy.js CHANGED
@@ -49,6 +49,7 @@ const genConfig = ({
49
49
  restart,
50
50
  cpu,
51
51
  mem,
52
+ service,
52
53
  additionalEnv = [],
53
54
  region = "us-east-1",
54
55
  isProduction,
@@ -123,6 +124,34 @@ const genConfig = ({
123
124
  ]
124
125
  }
125
126
 
127
+ ${
128
+ service
129
+ ? `# Setup API Routes
130
+ network {
131
+ port "http" {
132
+ static = ${service.port}
133
+ }
134
+ }
135
+
136
+ service {
137
+ name = "${jobName}"
138
+ port = "http"
139
+
140
+ tags = [
141
+ "urlprefix-${service.prefix} strip=${service.prefix}",
142
+ ]
143
+
144
+ check {
145
+ type = "http"
146
+ path = "/"
147
+ interval = "10s"
148
+ timeout = "2s"
149
+ }
150
+ }
151
+ `
152
+ : ""
153
+ }
154
+
126
155
  template {
127
156
  change_mode = "restart"
128
157
  destination = "secrets/context.env"
@@ -507,6 +536,7 @@ function createDeploy({
507
536
  restart,
508
537
  cpu,
509
538
  mem,
539
+ service,
510
540
  }) {
511
541
  async function deployModeless({ production, stop, dryRun, verbose, schedule: cmdSchedule, ...selectorFlags }) {
512
542
  const jobName = getJobName(name, selectorFlags, null);
@@ -538,6 +568,7 @@ function createDeploy({
538
568
  cmd: `${bin} ${args}`,
539
569
  cpu,
540
570
  mem,
571
+ service,
541
572
  isProduction: production,
542
573
  });
543
574
 
package/examples/api ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+
3
+ // this file is executable and is for kafka producer testing
4
+ // a few test commands to try on
5
+ // $ examples/api run --verbose
6
+ // $ examples/api run
7
+ // $ examples/api check
8
+ // $ examples/api deploy
9
+ // $ examples/api --help
10
+
11
+ const { api, every, SENSITIVE_VALUE, ERROR_LEVEL } = require("../app");
12
+
13
+ // an example middleware
14
+ async function logRequest(req, res, next) {
15
+ const start = new Date();
16
+ next();
17
+ const end = new Date();
18
+ const logInfo = { start, end, elapsed: `${end - start}ms` };
19
+ console.log(logInfo);
20
+ }
21
+
22
+ async function getProjectsHandler({ req, res, verbose }) {
23
+ console.log("receieved call", req.query, verbose);
24
+
25
+ const type = req.query.type;
26
+
27
+ if (!type) {
28
+ res.status(400).end("must provide type parameter");
29
+ return;
30
+ }
31
+
32
+ res.json([{ id: "proj-1", name: "Test Project 1" }]);
33
+ }
34
+
35
+ async function createProjectHandler({ req, res, verbose }) {
36
+ console.log("receieved call", req.body, verbose);
37
+
38
+ res.json({ success: true });
39
+ }
40
+
41
+ const app = api({
42
+ name: "example-api",
43
+
44
+ env: {
45
+ EXAMPLE_API_KEY: SENSITIVE_VALUE,
46
+ },
47
+
48
+ routes: [
49
+ {
50
+ method: "GET",
51
+ path: "/projects",
52
+ handler: getProjectsHandler,
53
+ middlewares: [logRequest],
54
+ },
55
+ {
56
+ method: "POST",
57
+ path: "/projects",
58
+ handler: createProjectHandler,
59
+ protected: true,
60
+ middlewares: [logRequest],
61
+ },
62
+ ],
63
+
64
+ serve: {
65
+ prefix: "/example-api", // deployed to a sub path
66
+ port: 12345,
67
+ apiKey: process.env.EXAMPLE_API_KEY,
68
+ cpu: 600,
69
+ mem: 200,
70
+ },
71
+ });
72
+
73
+ app();
package/examples/consumer CHANGED
@@ -25,7 +25,7 @@ async function check({ protocol, state, verbose }) {
25
25
  }
26
26
 
27
27
  const app = consumer({
28
- name: "LibSkynetExampleConsumer",
28
+ name: "example-consumer",
29
29
  selector: { protocol: { type: "string", description: "from which chain to consume data" } },
30
30
 
31
31
  consume: {
package/examples/indexer CHANGED
@@ -35,7 +35,7 @@ async function check({ protocol, state, verbose }) {
35
35
  }
36
36
 
37
37
  const app = indexer({
38
- name: "LibSkynetExampleIndexer",
38
+ name: "example-indexer",
39
39
 
40
40
  selector: {
41
41
  // for more flags check meow documentation at https://github.com/sindresorhus/meow
@@ -32,7 +32,7 @@ async function check({ mode, protocol, state, verbose }) {
32
32
  }
33
33
 
34
34
  const app = modeIndexer({
35
- name: "LibSkynetExampleModeIndexer",
35
+ name: "example-mode-indexer",
36
36
 
37
37
  selector: {
38
38
  // for more flags check meow documentation at https://github.com/sindresorhus/meow
package/examples/producer CHANGED
@@ -32,7 +32,7 @@ async function check({ protocol, state, verbose }) {
32
32
  }
33
33
 
34
34
  const app = producer({
35
- name: "LibSkynetExampleProducer",
35
+ name: "example-producer",
36
36
  selector: { protocol: { type: "string", description: "for which chain to produce data" } },
37
37
 
38
38
  env: {
package/log.js CHANGED
@@ -1,5 +1,5 @@
1
1
  function getLine(params) {
2
- let line = `${new Date().toISOString()}\t`;
2
+ let line = "";
3
3
 
4
4
  // Convert to string and filter out newline to tabs (AWS Athena)
5
5
  for (let i = 0, l = params.length; i < l; i++) {
@@ -11,12 +11,16 @@ function getLine(params) {
11
11
  return line.trim();
12
12
  }
13
13
 
14
+ function timestamp() {
15
+ return new Date().toISOString();
16
+ }
17
+
14
18
  const inline = {
15
19
  log: function (...args) {
16
- console.log(getLine(args));
20
+ console.log(`${timestamp()} ${getLine(args)}`);
17
21
  },
18
22
  error: function (...args) {
19
- console.error(getLine(args));
23
+ console.error(`${timestamp()} ${getLine(args)}`);
20
24
  },
21
25
  };
22
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@certik/skynet",
3
- "version": "0.9.5",
3
+ "version": "0.10.0",
4
4
  "description": "Skynet Shared JS library",
5
5
  "main": "index.js",
6
6
  "author": "CertiK Engineering",
@@ -16,6 +16,7 @@
16
16
  "aws-sdk": "^2.932.0",
17
17
  "chalk": "^4.1.1",
18
18
  "execa": "^5.0.0",
19
+ "express": "^4.18.1",
19
20
  "kafkajs": "^1.15.0",
20
21
  "meow": "^7.0.1",
21
22
  "node-fetch": "^2.6.1",
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // this file is executable and is for deployer testing
4
- // try this test command
5
- // $ examples/deploy-consumer --protocol bsc
6
-
7
- const { createDeploy } = require("../deploy");
8
-
9
- const { deploy } = createDeploy({
10
- name: "lib-skynet-test-consumer",
11
- workingDirectory: "lib-skynet",
12
- bin: "examples/kafka-consumer",
13
- selector: { protocol: { type: "string" } },
14
- env: {
15
- SKYNET_KAFKA_SERVER: process.env.SKYNET_KAFKA_SERVER,
16
- SKYNET_KAFKA_USERNAME: process.env.SKYNET_KAFKA_USERNAME,
17
- SKYNET_KAFKA_PASSWORD: process.env.SKYNET_KAFKA_PASSWORD
18
- },
19
- restart: { attempts: 3 },
20
- cpu: 600,
21
- mem: 200
22
- });
23
-
24
- deploy();
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // this file is executable and is for indexer testing
4
- // try this test command
5
- // $ examples/deploy-indexer --protocol bsc
6
-
7
- const { createDeploy } = require("../deploy");
8
-
9
- const { deploy } = createDeploy({
10
- name: "lib-skynet-test-indexer",
11
- workingDirectory: "lib-skynet",
12
- bin: "examples/indexer",
13
- selector: { protocol: { type: "string" } },
14
- schedule: "@minutely",
15
- cpu: 600,
16
- mem: 200,
17
- });
18
-
19
- deploy();
@@ -1,22 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // this file is executable and is for indexer with mode testing
4
- // try this test command
5
- // $ examples/deploy-mode-indexer --protocol bsc
6
-
7
- const { createModeDeploy } = require("../deploy");
8
-
9
- const { deploy } = createModeDeploy({
10
- name: "lib-skynet-test-mode-indexer",
11
- workingDirectory: "lib-skynet",
12
- bin: "examples/mode-indexer",
13
- selector: { protocol: { type: "string" } },
14
- deltaSchedule: "@minutely",
15
- validateSchedule: "@hourly",
16
- deltaCpu: 600,
17
- deltaMem: 200,
18
- rebuildCpu: 800,
19
- rebuildMem: 400
20
- });
21
-
22
- deploy();
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // this file is executable and is for deployer testing
4
- // try this test command
5
- // $ examples/deploy-producer --protocol bsc --from 1
6
-
7
- const path = require("path");
8
-
9
- const { createDeploy } = require("../deploy");
10
-
11
- const { deploy } = createDeploy({
12
- name: "lib-skynet-test-producer",
13
- workingDirectory: "lib-skynet", // where package.json was located
14
- bin: "examples/kafka-producer",
15
- selector: { protocol: { type: "string" } },
16
- env: {
17
- SKYNET_KAFKA_SERVER: process.env.SKYNET_KAFKA_SERVER,
18
- SKYNET_KAFKA_USERNAME: process.env.SKYNET_KAFKA_USERNAME,
19
- SKYNET_KAFKA_PASSWORD: process.env.SKYNET_KAFKA_PASSWORD
20
- },
21
- cpu: 600,
22
- mem: 200
23
- });
24
-
25
- deploy();
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // this file is executable and is for kafka producer testing
4
- // a few test commands to try on
5
- // $ examples/indexer --protocol bsc --verbose
6
- // $ examples/indexer --protocol eth
7
- // $ examples/indexer --help
8
-
9
- const { createIndexerApp } = require("../indexer");
10
-
11
- async function build({ protocol, verbose }) {
12
- console.log("build called with", protocol, verbose);
13
-
14
- if (protocol === "eth") {
15
- throw new Error("when problem is not recoverable and cannot be resolved by retry, throw error here");
16
- }
17
- }
18
-
19
- const { run } = createIndexerApp({
20
- name: "lib-skynet-test-indexer",
21
- selector: { protocol: { type: "string" } },
22
- build
23
- });
24
-
25
- run();
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // this file is executable and is for kafka consumer testing
4
- // a few test commands to try on
5
- // $ examples/kafka-consumer --protocol bsc --verbose
6
- // $ examples/kafka-consumer --help
7
-
8
- const { createConsumerApp } = require("../kafka");
9
-
10
- async function consume({ protocol, messages, verbose }) {
11
- console.log("consume called with", protocol, messages, verbose);
12
-
13
- messages.forEach(message => {
14
- // if (message.id === 4) {
15
- // throw new Error(
16
- // "when critical error that cannot be fixed happened, we can throw and stop processing here"
17
- // );
18
- // }
19
- });
20
- }
21
-
22
- const { run } = createConsumerApp({
23
- name: "ShareJSTestConsumer",
24
- selector: { protocol: { type: "string" } },
25
-
26
- consumer: {
27
- topic: ({ protocol }) => `lib-skynet-test-kafka-${protocol}-dev`,
28
- consume,
29
- maxRetry: 1 // default to 2
30
- }
31
- });
32
-
33
- run();
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // this file is executable and is for kafka producer testing
4
- // a few test commands to try on
5
- // $ examples/kafka-producer --protocol bsc --verbose
6
- // $ examples/kafka-producer --protocol bsc --from 4 --verbose
7
- // $ examples/kafka-producer --protocol bsc --from 4 --to 5 --verbose
8
- // $ examples/kafka-producer --protocol bsc --status
9
- // $ examples/kafka-producer --help
10
-
11
- const { createProducerApp } = require("../kafka");
12
-
13
- async function produce({ protocol, from, to, verbose, send }) {
14
- console.log("produce called with", protocol, from, to, verbose);
15
-
16
- if (from <= 50) {
17
- // send records to topic
18
- const items = [];
19
- for (let i = from; i <= to; i++) {
20
- items.push({ id: i, name: `User ${i}` });
21
- }
22
-
23
- await send(items);
24
-
25
- return []; // no errors
26
- } else if (from > 80) {
27
- throw new Error("critical error, cannot continue, better to throw");
28
- } else {
29
- const failed = [];
30
-
31
- for (let i = from; i <= to; i++) {
32
- failed.push(i);
33
- }
34
-
35
- // return failed item ids, which will be send to dead letter topic
36
- return failed;
37
- }
38
- }
39
-
40
- const { run } = createProducerApp({
41
- name: "ShareJSTestProducer",
42
- selector: { protocol: { type: "string" } },
43
-
44
- producer: {
45
- topic: ({ protocol }) => `lib-skynet-test-kafka-${protocol}-dev`,
46
- deadLetterTopic: ({ protocol }) => `lib-skynet-test-kafka-${protocol}-dead-letter-dev`, // problematic ids will be sent to dead letter topic for later retry
47
- produce,
48
- batchSize: 10, // default to 50
49
- maxRetry: 1 // default to 2
50
- },
51
-
52
- state: {
53
- type: "block", // can be omitted, default is block
54
- updateInterval: ({ protocol }) => (protocol === "bsc" ? 3000 : 20000), // how often max id is increasing, will determine poll interval, default to 5000ms
55
- getMinId: async () => 4, // default returns 1
56
- getMaxId: async ({ protocol }) => {
57
- console.log("getMaxId called", protocol);
58
-
59
- return 100;
60
- }
61
- }
62
- });
63
-
64
- run();
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // this file is executable and is for kafka producer testing
4
- // a few test commands to try on
5
- // $ examples/indexer --protocol bsc --verbose
6
- // $ examples/indexer --help
7
-
8
- const { createModeIndexerApp } = require("../indexer");
9
- const { readFile, writeFile } = require("fs/promises");
10
-
11
- async function build({ protocol, from, to, verbose }) {
12
- console.log("build called with", protocol, from, to, verbose);
13
-
14
- for (let i = from; i <= to; i++) {
15
- if (i !== 5) {
16
- // simulate a gap, id = 5
17
- // we can use validate mode to fix
18
- const filename = `/tmp/ShareJSTestModeIndexer-${protocol}-${i}`;
19
-
20
- await writeFile(filename, "helloworld");
21
- }
22
- }
23
-
24
- console.log("build done", protocol, from, to);
25
- }
26
-
27
- async function validate({ protocol, from, to, verbose }) {
28
- console.log("validate called with", protocol, from, to, verbose);
29
-
30
- for (let i = from; i <= to; i++) {
31
- const filename = `/tmp/ShareJSTestModeIndexer-${protocol}-${i}`;
32
-
33
- try {
34
- await readFile(filename);
35
- } catch (notFound) {
36
- // fix missing data
37
- await writeFile(filename, "helloworld");
38
- }
39
- }
40
-
41
- console.log("validate done", protocol, from, to);
42
- }
43
-
44
- const { run } = createModeIndexerApp({
45
- name: "ShareJSTestModeIndexer",
46
- selector: { protocol: { type: "string" } },
47
-
48
- build,
49
- buildBatchSize: 5,
50
- buildConcurrency: 2, // default is 1, no concurrency
51
- validate,
52
- validateBatchSize: 2,
53
- validateConcurrency: 2, // default is 1, no concurrency
54
-
55
- state: {
56
- type: "block", // can be omitted, default is block
57
- getMinId: async () => 2, // default returns 1
58
- getMaxId: async ({ protocol }) => {
59
- return 10;
60
- }
61
- }
62
- });
63
-
64
- run();