@certik/skynet 0.17.0 → 0.18.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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.0
4
+
5
+ - BREAKING: remove dependencies on `SKYNET_AWS_ACCESS_KEY_ID` and `SKYNET_AWS_SECRET_ACCESS_KEY` and `SKYNET_AWS_REGION`
6
+ - BREAKING: rename OPSGENIE_API_KEY to SKYNET_OPSGENIE_API_KEY
7
+ - BREAKING: removed producer and consumer support
8
+
3
9
  ## 0.17.0
4
10
 
5
11
  - BREAKING: changed the way an indexer integrate with added doppler integration support in deploy
package/app.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { ERROR_LEVEL } from "./monitor";
2
1
  import { Selector } from "./selector";
3
2
  import express from "express";
4
3
 
@@ -110,8 +109,6 @@ type ValidateWithConcurrency = Validate & { concurrency: number | undefined };
110
109
 
111
110
  export const SENSITIVE_VALUE: null;
112
111
 
113
- export { ERROR_LEVEL };
114
-
115
112
  export function every(n: number | undefined): {
116
113
  second: string;
117
114
  seconds: string;
@@ -134,30 +131,10 @@ export function api(apiParams: {
134
131
  beforeListen: (args: { app: ReturnType<typeof express> }) => void;
135
132
  }): () => void;
136
133
 
137
- export function consumer(consumerParams: {
138
- name: string;
139
- selector?: Selector;
140
- consume: Consume;
141
- check?: Check;
142
- env: Env | undefined;
143
- region: string | undefined;
144
- }): () => void;
145
-
146
- export function producer(producerParams: {
147
- name: string;
148
- selector?: Selector;
149
- produce: Produce;
150
- check?: Check;
151
- state: State;
152
- env?: Env;
153
- region?: string;
154
- }): () => void;
155
-
156
134
  export function indexer(indexerParams: {
157
135
  name: string;
158
136
  selector?: Selector;
159
137
  build: BuildWithRestart;
160
- check?: Check;
161
138
  env?: Env;
162
139
  region?: string;
163
140
  }): () => void;
@@ -168,7 +145,6 @@ export function modeIndexer(modeIndexerParams: {
168
145
  state: State;
169
146
  build: BuildWithConcurrency;
170
147
  validate?: ValidateWithConcurrency;
171
- check?: Check;
172
148
  env?: Env;
173
149
  region?: string;
174
150
  }): () => void;
package/app.js CHANGED
@@ -1,17 +1,7 @@
1
1
  import { EOL } from "os";
2
-
3
- import {
4
- createIndexerApp,
5
- createModeIndexerApp,
6
- getIndexerLatestId,
7
- getIndexerValidatedId,
8
- getIndexerState,
9
- } from "./indexer.js";
10
-
2
+ import { createIndexerApp, createModeIndexerApp } from "./indexer.js";
11
3
  import { createDeploy, createModeDeploy } from "./deploy.js";
12
- import { createProducerApp, createConsumerApp, getProducerLatestId } from "./kafka.js";
13
4
  import { startApiApp } from "./api.js";
14
- import { createMonitor, ERROR_LEVEL } from "./monitor.js";
15
5
  import { getBinaryName, detectBin, detectWorkingDirectory } from "./cli.js";
16
6
 
17
7
  function printAppHelp() {
@@ -261,164 +251,6 @@ function modeIndexer({ name, selector, state, build, validate, env = {}, region
261
251
  });
262
252
  }
263
253
 
264
- function checkProducerProduceParameter(produce) {
265
- const errors = [];
266
-
267
- if (!produce?.func) {
268
- errors.push("must define produce.func");
269
- }
270
-
271
- if (!produce?.topic) {
272
- errors.push("must define produce.topic");
273
- }
274
-
275
- if (!produce?.deadLetterTopic) {
276
- errors.push("must define produce.deadLetterTopic");
277
- }
278
-
279
- if (!produce?.cpu) {
280
- errors.push("must define produce.cpu");
281
- }
282
-
283
- if (!produce?.mem) {
284
- errors.push("must define produce.mem");
285
- }
286
-
287
- return errors;
288
- }
289
-
290
- function producer({ name, selector, produce, state, env = {}, region = "skynet-dc1" }) {
291
- const envWithDefaultValues = {
292
- SKYNET_KAFKA_SERVER: SENSITIVE_VALUE,
293
- SKYNET_KAFKA_USERNAME: SENSITIVE_VALUE,
294
- SKYNET_KAFKA_PASSWORD: SENSITIVE_VALUE,
295
- ...env,
296
- };
297
-
298
- return createApp({
299
- parameterErrors: [
300
- ...checkProducerProduceParameter(produce),
301
- ...checkStateParameter(state),
302
- ...checkEnvParameter(env),
303
- ],
304
- env: envWithDefaultValues,
305
- onRun: () => {
306
- const { run } = createProducerApp({
307
- binaryName: `${getBinaryName()} run`,
308
- name,
309
- selector,
310
-
311
- producer: {
312
- topic: produce.topic,
313
- deadLetterTopic: produce.deadLetterTopic,
314
- produce: produce.func,
315
- batchSize: produce.batchSize,
316
- maxRetry: produce.maxRetry,
317
- },
318
-
319
- state,
320
- });
321
-
322
- process.title = name;
323
-
324
- return run();
325
- },
326
- onDeploy: () => {
327
- const bin = detectBin();
328
- const needDoppler = Object.values(env).some((v) => v === SENSITIVE_VALUE);
329
-
330
- const { deploy } = createDeploy({
331
- binaryName: `${getBinaryName()} deploy`,
332
- name,
333
- workingDirectory: detectWorkingDirectory(),
334
- bin: needDoppler ? `doppler run -- ${bin} run` : `${bin} run`,
335
- selector,
336
- region,
337
- env: envWithDefaultValues,
338
- schedule: "@minutely",
339
- killTimeout: produce.killTimeout,
340
- cpu: produce.cpu,
341
- mem: produce.mem,
342
- });
343
-
344
- return deploy();
345
- },
346
- });
347
- }
348
-
349
- function checkConsumerConsumeParameter(consume) {
350
- const errors = [];
351
-
352
- if (!consume?.func) {
353
- errors.push("must define consume.func");
354
- }
355
-
356
- if (!consume?.topic) {
357
- errors.push("must define consume.topic");
358
- }
359
-
360
- if (!consume?.cpu) {
361
- errors.push("must define consume.cpu");
362
- }
363
-
364
- if (!consume?.mem) {
365
- errors.push("must define consume.mem");
366
- }
367
-
368
- return errors;
369
- }
370
-
371
- function consumer({ name, selector, consume, env = {}, region = "skynet-dc1" }) {
372
- const envWithDefaultValues = {
373
- SKYNET_KAFKA_SERVER: SENSITIVE_VALUE,
374
- SKYNET_KAFKA_USERNAME: SENSITIVE_VALUE,
375
- SKYNET_KAFKA_PASSWORD: SENSITIVE_VALUE,
376
- ...env,
377
- };
378
-
379
- return createApp({
380
- parameterErrors: [...checkConsumerConsumeParameter(consume), ...checkEnvParameter(env)],
381
- env: envWithDefaultValues,
382
- onRun: () => {
383
- const { run } = createConsumerApp({
384
- binaryName: `${getBinaryName()} run`,
385
- name,
386
- selector,
387
-
388
- consumer: {
389
- topic: consume.topic,
390
- consume: consume.func,
391
- maxRetry: consume.maxRetry,
392
- },
393
- });
394
-
395
- process.title = name;
396
-
397
- return run();
398
- },
399
- onDeploy: () => {
400
- const bin = detectBin();
401
- const needDoppler = Object.values(env).some((v) => v === SENSITIVE_VALUE);
402
-
403
- const { deploy } = createDeploy({
404
- binaryName: `${getBinaryName()} deploy`,
405
- name,
406
- workingDirectory: detectWorkingDirectory(),
407
- bin: needDoppler ? `doppler run -- ${bin} run` : `${bin} run`,
408
- selector,
409
- region,
410
- env: envWithDefaultValues,
411
- schedule: "@minutely",
412
- killTimeout: consume.killTimeout,
413
- cpu: consume.cpu,
414
- mem: consume.mem,
415
- });
416
-
417
- return deploy();
418
- },
419
- });
420
- }
421
-
422
254
  function checkApiServeParameter(serve, routes) {
423
255
  const errors = [];
424
256
 
@@ -560,4 +392,4 @@ const every = (n = 1) => {
560
392
  };
561
393
  };
562
394
 
563
- export { indexer, modeIndexer, producer, consumer, api, every, SENSITIVE_VALUE, ERROR_LEVEL };
395
+ export { indexer, modeIndexer, api, every, SENSITIVE_VALUE };
package/bun.lockb CHANGED
Binary file
package/deploy.js CHANGED
@@ -63,7 +63,9 @@ const genConfig = ({
63
63
 
64
64
  group "default" {
65
65
  ${count && count > 1 ? `count = ${count}` : ""}
66
- ${count && count > 1 ? `# Rolling Update
66
+ ${
67
+ count && count > 1
68
+ ? `# Rolling Update
67
69
  update {
68
70
  max_parallel = 1
69
71
  min_healthy_time = "10s"
@@ -137,7 +139,7 @@ const genConfig = ({
137
139
  DOPPLER_CONFIG="${isProduction ? "prd" : "dev"}"
138
140
  SKYNET_ENVIRONMENT="${isProduction ? "prd" : "dev"}"
139
141
  ${Object.entries(additionalEnv)
140
- .filter(([k, v]) => !!v)
142
+ .filter((kv) => !!kv[1])
141
143
  .map(([key, value]) => `${key}="${value}"`)
142
144
  .join(" \n")}
143
145
  }
package/dynamodb.js CHANGED
@@ -9,7 +9,6 @@ import {
9
9
  TransactWriteCommand,
10
10
  } from "@aws-sdk/lib-dynamodb";
11
11
  import { DynamoDBClient, DescribeTableCommand } from "@aws-sdk/client-dynamodb";
12
- import { getAWSSDKConfig } from "./env.js";
13
12
  import { wait } from "./availability.js";
14
13
 
15
14
  let _dynamoDB;
@@ -18,7 +17,7 @@ let _docClient;
18
17
 
19
18
  function getDynamoDB(forceNew = false) {
20
19
  if (!_dynamoDB || forceNew) {
21
- _dynamoDB = new DynamoDBClient(getAWSSDKConfig());
20
+ _dynamoDB = new DynamoDBClient();
22
21
  }
23
22
  return _dynamoDB;
24
23
  }
package/env.d.ts CHANGED
@@ -1,15 +1,5 @@
1
1
  export function ensureAndGet<T>(envName: string, defaultValue: T): string | T;
2
2
  export function getEnvOrThrow(envName: string): string;
3
- export function getAWSAccessKeyId(): string | undefined;
4
- export function getAWSSecretAccessKey(): string | undefined;
5
- export function getAWSSDKConfig(): {
6
- region: string;
7
- credentials?: {
8
- accessKeyId: string;
9
- secretAccessKey: string;
10
- };
11
- };
12
- export function getAWSRegion(): string;
13
3
  export function getEnvironment(): string;
14
4
  export function getNodeRealApiKey(identifier: string): string | undefined;
15
5
  export function isProduction(): boolean;
package/env.js CHANGED
@@ -1,29 +1,3 @@
1
- function getAWSAccessKeyId() {
2
- return getEnvOrThrow("SKYNET_AWS_ACCESS_KEY_ID");
3
- }
4
-
5
- function getAWSSecretAccessKey() {
6
- return getEnvOrThrow("SKYNET_AWS_SECRET_ACCESS_KEY");
7
- }
8
-
9
- function getAWSSDKConfig() {
10
- const region = getAWSRegion();
11
- const accessKeyId = getAWSAccessKeyId();
12
- const secretAccessKey = getAWSSecretAccessKey();
13
-
14
- return {
15
- region,
16
- credentials: {
17
- accessKeyId,
18
- secretAccessKey,
19
- },
20
- };
21
- }
22
-
23
- function getAWSRegion() {
24
- return ensureAndGet(["SKYNET_AWS_REGION"], "us-east-1");
25
- }
26
-
27
1
  function getEnvironment() {
28
2
  return ensureAndGet("SKYNET_ENVIRONMENT", "dev");
29
3
  }
@@ -68,10 +42,6 @@ function isDev() {
68
42
  export {
69
43
  ensureAndGet,
70
44
  getEnvOrThrow,
71
- getAWSAccessKeyId,
72
- getAWSSecretAccessKey,
73
- getAWSSDKConfig,
74
- getAWSRegion,
75
45
  getEnvironment,
76
46
  isProduction,
77
47
  isDev,
package/indexer.js CHANGED
@@ -547,7 +547,7 @@ ${getSelectorDesc(selector)}
547
547
  // for those indexers that does not rely on a curosr
548
548
  // e.g. should always rebuild everything from scratch
549
549
  // or that the state can be easily inferred from existing data
550
- function createIndexerApp({ binaryName, name, selector = {}, build, maxRetry = 2 }) {
550
+ function createIndexerApp({ binaryName, selector = {}, build, maxRetry = 2 }) {
551
551
  function run() {
552
552
  if (!binaryName) {
553
553
  binaryName = getBinaryName();
package/opsgenie.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import md5 from "md5";
2
2
 
3
3
  function getGenieKey(key) {
4
- return key || process.env.OPSGENIE_API_KEY;
4
+ return key || process.env.SKYNET_OPSGENIE_API_KEY;
5
5
  }
6
6
 
7
7
  function getGenieEndPoint() {
8
- return process.env.OPSGENIE_END_POINT || "https://api.opsgenie.com/v2/alerts";
8
+ return process.env.SKYNET_OPSGENIE_END_POINT || "https://api.opsgenie.com/v2/alerts";
9
9
  }
10
10
 
11
11
  export async function postGenieMessage(body, apiKey, verbose) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@certik/skynet",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Skynet Shared JS library",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -23,21 +23,20 @@
23
23
  "@slack/web-api": "^6.11.0",
24
24
  "bottleneck": "^2.19.5",
25
25
  "chalk": "^5.3.0",
26
- "execa": "^8.0.1",
27
- "express": "^4.18.2",
28
- "kafkajs": "^2.2.4",
26
+ "execa": "^9.3.0",
27
+ "express": "^4.19.2",
29
28
  "md5": "^2.3.0",
30
- "meow": "^12.1.1",
31
- "snowflake-sdk": "^1.9.2",
32
- "web3": "^4.3.0",
29
+ "meow": "^13.2.0",
30
+ "snowflake-sdk": "^1.11.0",
31
+ "web3": "^4.11.0",
33
32
  "which": "^4.0.0"
34
33
  },
35
34
  "devDependencies": {
36
- "ava": "^6.0.1",
37
- "eslint": "^8.56.0",
35
+ "ava": "^6.1.3",
36
+ "eslint": "8",
38
37
  "eslint-plugin-import": "^2.29.1",
39
- "prettier": "^3.1.1",
40
- "sinon": "^17.0.1"
38
+ "prettier": "^3.3.3",
39
+ "sinon": "^18.0.0"
41
40
  },
42
41
  "license": "MIT",
43
42
  "publishConfig": {
package/s3.js CHANGED
@@ -8,13 +8,12 @@ import {
8
8
  NotFound,
9
9
  NoSuchKey,
10
10
  } from "@aws-sdk/client-s3";
11
- import { getAWSSDKConfig } from "./env.js";
12
11
 
13
12
  let _s3Client;
14
13
 
15
14
  function getS3(forceNew = false) {
16
15
  if (!_s3Client || forceNew) {
17
- _s3Client = new S3Client(getAWSSDKConfig());
16
+ _s3Client = new S3Client();
18
17
  }
19
18
  return _s3Client;
20
19
  }
package/sqs.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { SQSClient } from "@aws-sdk/client-sqs";
2
- import { getAWSSDKConfig } from "./env.js";
3
2
 
4
3
  export function getSQS() {
5
- return new SQSClient(getAWSSDKConfig());
4
+ return new SQSClient();
6
5
  }
package/kafka.d.ts DELETED
@@ -1,31 +0,0 @@
1
- import { KafkaMessage } from "kafkajs";
2
- import { Produce, Consume, State } from "./app";
3
- import { SelectorFlags, Selector } from "./selector";
4
-
5
- export function createProducerApp(createProducerAppParams: {
6
- binaryName: string;
7
- name: string;
8
- selector?: Selector;
9
- producer: Produce;
10
- state: State;
11
- }): { run: () => Promise<void> };
12
-
13
- export function createConsumerApp(createConsumerAppParams: {
14
- binaryName: string;
15
- name: string;
16
- selector?: Selector;
17
- consumer: Consume;
18
- }): { run: () => Promise<void> };
19
-
20
- export function produceMessages(
21
- producerId: string,
22
- callback: (send: <T>(topic: string, messages: T) => Promise<void>) => Promise<void>,
23
- ): Promise<void>;
24
-
25
- export function consumeMessages(
26
- consumerId: string,
27
- topic: string | RegExp,
28
- callback: (message: KafkaMessage, stopConsumeMessages: () => Promise<void>) => Promise<void>,
29
- ): Promise<() => Promise<void>>;
30
-
31
- export function getProducerLatestId(name: string, selectorFlags: SelectorFlags): Promise<number>;
package/kafka.js DELETED
@@ -1,445 +0,0 @@
1
- import meow from "meow";
2
- import { Kafka, logLevel } from "kafkajs";
3
- import { getEnvironment, getEnvOrThrow } from "./env.js";
4
- import { getSelectorFlags, getSelectorDesc, toSelectorString } from "./selector.js";
5
- import { createRecord, getRecordByKey, deleteRecordsByHashKey } from "./dynamodb.js";
6
- import { wait, exponentialRetry } from "./availability.js";
7
- import { getBinaryName } from "./cli.js";
8
- import { inline } from "./log.js";
9
-
10
- const STATE_TABLE_NAME = "skynet-" + getEnvironment() + "-indexer-state";
11
-
12
- async function getProducerLatestId(name, selectorFlags) {
13
- const record = await getRecordByKey(STATE_TABLE_NAME, {
14
- name: `${name}At(${toSelectorString(selectorFlags)})`,
15
- });
16
-
17
- if (record) {
18
- return record.value;
19
- }
20
-
21
- return 0;
22
- }
23
-
24
- async function setProducerLatestId(name, selectorFlags, value) {
25
- await createRecord(STATE_TABLE_NAME, {
26
- name: `${name}At(${toSelectorString(selectorFlags)})`,
27
- value,
28
- });
29
- }
30
-
31
- async function deleteProducerLatestId(name, selectorFlags, verbose) {
32
- await deleteRecordsByHashKey(STATE_TABLE_NAME, null, `${name}At(${toSelectorString(selectorFlags)})`, verbose);
33
- }
34
-
35
- function sendToTopic(producer, topic, verbose) {
36
- async function send(records) {
37
- if (verbose) {
38
- inline.log(`sending ${records.length} records to topic`, records);
39
- }
40
-
41
- await producer.send({
42
- topic,
43
- messages: records.map((r) => ({
44
- value: Buffer.from(typeof r === "string" ? r : JSON.stringify(r)),
45
- })),
46
- });
47
- }
48
-
49
- return send;
50
- }
51
-
52
- function getDefaultKafkaCredential() {
53
- return {
54
- server: getEnvOrThrow("SKYNET_KAFKA_SERVER"),
55
- username: getEnvOrThrow("SKYNET_KAFKA_USERNAME"),
56
- password: getEnvOrThrow("SKYNET_KAFKA_PASSWORD"),
57
- };
58
- }
59
-
60
- async function initProducer(clientId, customKafkaCredential = undefined) {
61
- const kafkaCredential = customKafkaCredential ?? getDefaultKafkaCredential();
62
- const kafka = new Kafka({
63
- clientId,
64
- brokers: [kafkaCredential.server],
65
- logLevel: logLevel.ERROR,
66
- ssl: true,
67
- sasl: {
68
- mechanism: "plain",
69
- username: kafkaCredential.username,
70
- password: kafkaCredential.password,
71
- },
72
- });
73
- const producerInstance = kafka.producer();
74
- await producerInstance.connect();
75
-
76
- return producerInstance;
77
- }
78
-
79
- async function closeProducer(producerInstance) {
80
- await producerInstance.disconnect();
81
- }
82
-
83
- async function initConsumer(clientId, customKafkaCredential = undefined) {
84
- const kafkaCredential = customKafkaCredential ? customKafkaCredential : getDefaultKafkaCredential();
85
- const kafka = new Kafka({
86
- clientId,
87
- brokers: [kafkaCredential.server],
88
- logLevel: logLevel.ERROR,
89
- ssl: true,
90
- sasl: {
91
- mechanism: "plain",
92
- username: kafkaCredential.username,
93
- password: kafkaCredential.password,
94
- },
95
- });
96
- const consumerInstance = kafka.consumer({ groupId: clientId });
97
-
98
- await consumerInstance.connect();
99
-
100
- return consumerInstance;
101
- }
102
-
103
- async function closeConsumer(consumerInstance) {
104
- await consumerInstance.disconnect();
105
- }
106
-
107
- async function produceMessages(producerId, callback) {
108
- const producerInstance = await initProducer(producerId);
109
- const send = async (topic, messages) => {
110
- let records;
111
- if (Array.isArray(messages)) {
112
- records = messages;
113
- } else {
114
- records = [messages];
115
- }
116
- return sendToTopic(producerInstance, topic, false)(records);
117
- };
118
- await callback(send);
119
- await closeProducer(producerInstance);
120
- }
121
-
122
- async function consumeMessages(consumerId, topic, callback) {
123
- const consumerInstance = await initConsumer(consumerId);
124
-
125
- await consumerInstance.subscribe({ topic });
126
-
127
- const stopConsumeMessages = async () => {
128
- await closeConsumer(consumerInstance);
129
- };
130
- await consumerInstance.run({
131
- eachMessage: async ({ topic: receivedTopic, message }) => {
132
- if (topic === receivedTopic) {
133
- await callback(message, stopConsumeMessages);
134
- }
135
- },
136
- });
137
- return stopConsumeMessages;
138
- }
139
-
140
- function createProducerApp({ binaryName, name, selector = {}, producer, state }) {
141
- function run() {
142
- if (!binaryName) {
143
- binaryName = getBinaryName();
144
- }
145
-
146
- const finalState = {
147
- type: "block",
148
- updateInterval: 5000,
149
- getMinId: async () => 1,
150
- getMaxId: async () => {
151
- throw new Error("must implement state.getMaxId");
152
- },
153
- ...state,
154
- };
155
-
156
- const cli = meow(
157
- `
158
- Usage
159
- $ ${binaryName} <options>
160
-
161
- Options
162
- ${getSelectorDesc(selector)}
163
- --from min ${finalState.type} to produce
164
- --to max ${finalState.type} to produce
165
- --status print current status and exit
166
- --verbose Output debug messages
167
- --reset restart producer
168
- `,
169
- {
170
- importMeta: import.meta,
171
- description: false,
172
- version: false,
173
- flags: {
174
- ...getSelectorFlags(selector),
175
- from: {
176
- aliases: ["since"],
177
- type: "number",
178
- default: 0,
179
- },
180
- to: {
181
- aliases: ["until"],
182
- type: "number",
183
- default: 0,
184
- },
185
- verbose: {
186
- type: "boolean",
187
- default: false,
188
- },
189
- },
190
- }
191
- );
192
-
193
- const { topic, deadLetterTopic, produce, batchSize, maxRetry } = {
194
- batchSize: 50,
195
- maxRetry: 2,
196
- ...producer,
197
- };
198
-
199
- const kafkaCredential = (producer && producer.kafkaCredential) || getDefaultKafkaCredential();
200
-
201
- async function build({ from, to, status, reset, verbose, ...selectorFlags }) {
202
- if (reset) {
203
- await deleteProducerLatestId(name, selectorFlags);
204
- if (finalState.onReset) {
205
- await finalState.onReset(selectorFlags, verbose);
206
- }
207
-
208
- inline.log("producer reset");
209
-
210
- process.exit(0);
211
- }
212
-
213
- if (status) {
214
- const latestId = await getProducerLatestId(name, selectorFlags);
215
-
216
- inline.log(`LatestID=${latestId}`);
217
-
218
- process.exit(0);
219
- }
220
-
221
- if (!from) {
222
- const prevId = await getProducerLatestId(name, selectorFlags);
223
-
224
- if (prevId) {
225
- from = prevId + 1;
226
- } else {
227
- // initial build
228
- from = await finalState.getMinId(selectorFlags);
229
- }
230
- }
231
-
232
- const finalTopic = typeof topic === "function" ? topic(selectorFlags) : topic;
233
- const finalDeadLetterTopic =
234
- typeof deadLetterTopic === "function" ? deadLetterTopic(selectorFlags) : deadLetterTopic;
235
-
236
- const updateInterval =
237
- typeof finalState.updateInterval === "function"
238
- ? finalState.updateInterval(selectorFlags)
239
- : finalState.updateInterval;
240
-
241
- const producerInstance = await initProducer(name, kafkaCredential);
242
-
243
- inline.log("producing to topic", finalTopic);
244
-
245
- // if to flag was not provided, run this program indefinitely
246
- const isLongRunning = to === 0;
247
-
248
- let lastId = to > 0 ? to : await finalState.getMaxId(selectorFlags);
249
-
250
- let pollHandle;
251
-
252
- async function cleanUpForLongRunningMode() {
253
- clearInterval(pollHandle);
254
-
255
- await closeProducer(producerInstance);
256
-
257
- process.exit(0);
258
- }
259
-
260
- if (isLongRunning) {
261
- pollHandle = setInterval(async () => {
262
- lastId = await finalState.getMaxId(selectorFlags);
263
- }, updateInterval);
264
-
265
- // clean up before exit
266
- process.on("SIGINT", cleanUpForLongRunningMode);
267
- }
268
-
269
- // the main loop
270
- for (let i = from; isLongRunning || i <= lastId; ) {
271
- if (i > lastId) {
272
- console.log("no more items to process, sleep for", updateInterval, "ms");
273
-
274
- await wait(updateInterval);
275
- } else {
276
- const batchStart = i;
277
- const batchEnd = Math.min(batchStart + batchSize - 1, lastId);
278
-
279
- inline.log(`building batch ${batchStart}~${batchEnd}`);
280
-
281
- // add a retry for errors
282
- const result = await exponentialRetry(
283
- async () => {
284
- try {
285
- const failed = await produce({
286
- ...selectorFlags,
287
- from: batchStart,
288
- to: batchEnd,
289
- verbose,
290
- send: sendToTopic(producerInstance, finalTopic, verbose),
291
- });
292
-
293
- if (Array.isArray(failed) && failed.length > 0) {
294
- await sendToTopic(producerInstance, finalDeadLetterTopic, verbose)(failed);
295
- }
296
-
297
- return true;
298
- } catch (err) {
299
- inline.error(`Critical error processing batch ${batchStart}~${batchEnd}`, err);
300
-
301
- return false;
302
- }
303
- },
304
- {
305
- maxRetry,
306
- test: (r) => r,
307
- verbose,
308
- }
309
- );
310
-
311
- if (!result) {
312
- if (isLongRunning) {
313
- cleanUpForLongRunningMode();
314
- }
315
-
316
- throw new Error(`Terminate producer due to critical errors, batch ${batchStart}~${batchEnd}`);
317
- }
318
-
319
- await setProducerLatestId(name, selectorFlags, batchEnd);
320
-
321
- if (i + batchSize <= lastId) i += batchSize;
322
- else i = batchEnd + 1;
323
- }
324
-
325
- if (isLongRunning) {
326
- lastId = await finalState.getMaxId(selectorFlags);
327
- }
328
- }
329
-
330
- await closeProducer(producerInstance);
331
- }
332
-
333
- return build(cli.flags).catch((err) => {
334
- inline.error(err);
335
- process.exit(1);
336
- });
337
- }
338
-
339
- return { run };
340
- }
341
-
342
- function createConsumerApp({ binaryName, name, selector = {}, consumer }) {
343
- function run() {
344
- if (!binaryName) {
345
- binaryName = getBinaryName();
346
- }
347
-
348
- const cli = meow(
349
- `
350
- Usage
351
- $ ${binaryName} <options>
352
-
353
- Options
354
- ${getSelectorDesc(selector)}
355
- --verbose Output debug messages
356
- `,
357
- {
358
- importMeta: import.meta,
359
- description: false,
360
- version: false,
361
- flags: {
362
- ...getSelectorFlags(selector),
363
- verbose: {
364
- type: "boolean",
365
- default: false,
366
- },
367
- },
368
- }
369
- );
370
-
371
- const { topic, consume, maxRetry } = {
372
- maxRetry: 2,
373
- ...consumer,
374
- };
375
-
376
- const kafkaCredential = (consume && consumer.kafkaCredential) || getDefaultKafkaCredential();
377
-
378
- async function build({ verbose, ...selectorFlags }) {
379
- const consumerInstance = await initConsumer(name, kafkaCredential);
380
-
381
- try {
382
- const finalTopic = typeof topic === "function" ? topic(selectorFlags) : topic;
383
-
384
- await consumerInstance.subscribe({ topic: finalTopic });
385
-
386
- inline.log("subscribed to topic", finalTopic);
387
-
388
- await consumerInstance.run({
389
- eachBatch: async ({ batch }) => {
390
- if (verbose) {
391
- inline.log("received batch, number of items =", batch.messages.length);
392
- }
393
-
394
- const messages = batch.messages.map((m) => JSON.parse(m.value));
395
-
396
- // add a retry for errors
397
- const result = await exponentialRetry(
398
- async () => {
399
- try {
400
- await consume({ ...selectorFlags, messages, verbose });
401
-
402
- return true;
403
- } catch (err) {
404
- inline.error(`got critical error`, err);
405
-
406
- return false;
407
- }
408
- },
409
- {
410
- maxRetry,
411
- test: (r) => r,
412
- verbose,
413
- }
414
- );
415
-
416
- if (!result) {
417
- inline.error("Terminate consumer due to consume errors, likely bug");
418
-
419
- process.exit(1);
420
- }
421
- },
422
- });
423
- } catch (err) {
424
- inline.error("Terminate consumer due to critical kafka connection error", err);
425
-
426
- process.exit(1);
427
- }
428
- }
429
-
430
- return build(cli.flags).catch((err) => {
431
- inline.error(err);
432
- process.exit(1);
433
- });
434
- }
435
-
436
- return { run };
437
- }
438
-
439
- export {
440
- createProducerApp,
441
- createConsumerApp,
442
- produceMessages,
443
- consumeMessages,
444
- getProducerLatestId,
445
- };
package/monitor.d.ts DELETED
@@ -1,24 +0,0 @@
1
- import { Check } from "./app";
2
- import { SelectorFlags, Selector } from "./selector";
3
-
4
- export const ERROR_LEVEL: {
5
- INFO: string;
6
- CRITICAL: string;
7
- WARNING: string;
8
- };
9
-
10
- export const LEVEL_EMOJI: {
11
- Info: string;
12
- Critical: string;
13
- Warning: string;
14
- };
15
-
16
- export function createMonitor(createMonitorParams: {
17
- binaryName: string;
18
- name: string;
19
- getState?: (name: string, selectorFlags: SelectorFlags) => Promise<void>;
20
- mode?: boolean;
21
- selector?: Selector;
22
- check?: Check;
23
- maxRetry?: number;
24
- }): { monitor: () => void };
package/monitor.js DELETED
@@ -1,197 +0,0 @@
1
- import meow from "meow";
2
- import { getSelectorFlags, getSelectorDesc, toSelectorString } from "./selector.js";
3
- import { getBinaryName } from "./cli.js";
4
- import { exponentialRetry } from "./availability.js";
5
- import { postGenieMessage } from "./opsgenie.js";
6
- import { getJobName } from "./deploy.js";
7
-
8
- const ERROR_LEVEL = {
9
- INFO: "Info",
10
- CRITICAL: "Critical",
11
- WARNING: "Warning",
12
- };
13
-
14
- const LEVEL_EMOJI = {
15
- Critical: ":exclamation:",
16
- Warning: ":warning:",
17
- Info: "",
18
- };
19
-
20
- const LEVEL_PRIORITY = {
21
- Critical: 3,
22
- Warning: 2,
23
- Info: 1,
24
- };
25
-
26
- function sortErrors(errors) {
27
- return errors.sort((e1, e2) => (LEVEL_PRIORITY[e2.type] || 0) - (LEVEL_PRIORITY[e1.type] || 0));
28
- }
29
-
30
- async function getMostRecentJobLaunch(name) {
31
- try {
32
- const jobsRes = await fetch(`http://localhost:4646/v1/jobs?prefix=${name}`);
33
-
34
- if (!jobsRes.ok) {
35
- console.log(`[MONITOR] request local nomad API failed`);
36
-
37
- return null;
38
- }
39
-
40
- const jobs = await jobsRes.json();
41
-
42
- if (jobs.length === 0) {
43
- console.log(`[MONITOR] did not see any jobs prefixed with ${name}`);
44
-
45
- return null;
46
- }
47
-
48
- const recentFinishedJob = jobs.reverse().find((job) => {
49
- // filter out monitor job
50
- return job.ID.indexOf("-monitor") === -1 && job.Status === "dead";
51
-
52
- });
53
-
54
- if (!recentFinishedJob) {
55
- console.log(`[MONITOR] did not see any dead jobs`);
56
-
57
- return null;
58
- }
59
-
60
- console.log("[MONITOR]", "most recent job info", recentFinishedJob.ID, recentFinishedJob.JobSummary.Summary);
61
-
62
- return recentFinishedJob;
63
- } catch (getJobError) {
64
- console.log("[MONITOR]", "cannot get most recent job", getJobError.message);
65
- return null;
66
- }
67
- }
68
-
69
- function createMonitor({ binaryName, name, getState = null, mode = false, selector = {}, check, maxRetry = 2 }) {
70
- function monitor() {
71
- if (!binaryName) {
72
- binaryName = getBinaryName();
73
- }
74
-
75
- const cli = meow(
76
- `
77
- Usage
78
-
79
- $ ${binaryName} <options>
80
-
81
- Options
82
- ${
83
- mode ? " --mode could be delta/rebuild/resume-rebuild/validate/one/range/reset\n" : ""
84
- }${getSelectorDesc(selector)}
85
- --production run in production mode
86
- --verbose Output debug messages
87
- `,
88
- {
89
- importMeta: import.meta,
90
- description: false,
91
- version: false,
92
- flags: {
93
- ...getSelectorFlags(selector),
94
- ...(mode && {
95
- mode: {
96
- type: "string",
97
- default: "delta",
98
- },
99
- }),
100
- verbose: {
101
- type: "boolean",
102
- default: false,
103
- },
104
- },
105
- }
106
- );
107
-
108
- async function runCheck({ verbose, production, mode, ...selectorFlags }) {
109
- const startTime = Date.now();
110
-
111
- if (Object.keys(selectorFlags).length > 0) {
112
- console.log(`[MONITOR] starting check, ${toSelectorString(selectorFlags, ", ")}`);
113
- } else {
114
- console.log(`[MONITOR] starting check`);
115
- }
116
-
117
- let checkState = {};
118
-
119
- if (getState) {
120
- checkState = await getState(name, selectorFlags);
121
- }
122
-
123
- let result = await exponentialRetry(
124
- async () => {
125
- try {
126
- const errors = await check({ verbose, state: checkState, mode, ...selectorFlags });
127
-
128
- if (!Array.isArray(errors)) {
129
- throw new Error(`check function must return array of error messages`);
130
- }
131
-
132
- return errors;
133
- } catch (err) {
134
- console.log(`[MONITOR] got error in check`, err);
135
-
136
- return [{ type: ERROR_LEVEL.ERROR, message: `${err.message}` }];
137
- }
138
- },
139
- {
140
- maxRetry,
141
- initialDuration: 10000,
142
- growFactor: 3,
143
- test: (r) => r.length === 0,
144
- verbose,
145
- }
146
- );
147
-
148
- const jobName = getJobName(name, selectorFlags, mode);
149
- const mostRecentJob = await getMostRecentJobLaunch(jobName);
150
-
151
- if (result.length > 0) {
152
- console.log("Found Errors", result);
153
-
154
- if (production) {
155
- const nomadAddr = process.env.SKYNET_NOMAD_PRODUCTION_ADDR;
156
-
157
- // alert on opsgenie
158
- await postGenieMessage(
159
- {
160
- message: `Failed Service Check: ${jobName}`,
161
- description: `<p><b>Service:</b><a href="${nomadAddr}/ui/jobs/${jobName}" target="_blank">${jobName}</a></p><p><b>Issues</b></p><ul>${sortErrors(
162
- result
163
- )
164
- .map((m) => `<li><b>${m.type}:</b> ${m.message}</li>`)
165
- .join("")}</ul>`,
166
- },
167
- verbose
168
- );
169
- } else {
170
- console.log("skip sending messages to opsgenie in dev env");
171
- }
172
- }
173
-
174
- console.log(`[MONITOR] check successfully in ${Date.now() - startTime}ms`);
175
-
176
- if (mostRecentJob && mostRecentJob.ParentID === "" && mostRecentJob.Periodic === false) {
177
- // stop monitor job if the main job was a one time job and completed
178
- const stopRes = await fetch(`http://localhost:4646/v1/job/${jobName}-monitor`, { method: "DELETE" });
179
-
180
- if (stopRes.ok) {
181
- console.log(`[MONITOR] monitor job stopped since the completed main job was a one time job`);
182
- } else {
183
- console.log(`[MONITOR] error stopping monitor job`);
184
- }
185
- }
186
- }
187
-
188
- return runCheck(cli.flags).catch((err) => {
189
- console.log("[MONITOR]", err);
190
- process.exit(1);
191
- });
192
- }
193
-
194
- return { monitor };
195
- }
196
-
197
- export { createMonitor, ERROR_LEVEL, LEVEL_EMOJI };