@certik/skynet 0.25.0 → 0.25.2

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.
Files changed (52) hide show
  1. package/.vscode/settings.json +5 -0
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +8 -2
  4. package/dist/abi.d.ts +111 -0
  5. package/dist/abi.js +571 -0
  6. package/dist/address.d.ts +2 -0
  7. package/dist/address.js +24 -0
  8. package/dist/api.d.ts +31 -0
  9. package/dist/api.js +260 -0
  10. package/dist/app.d.ts +101 -0
  11. package/dist/app.js +2077 -0
  12. package/dist/availability.d.ts +23 -0
  13. package/dist/availability.js +133 -0
  14. package/dist/cli.d.ts +5 -0
  15. package/dist/cli.js +41 -0
  16. package/dist/const.d.ts +34 -0
  17. package/dist/const.js +162 -0
  18. package/dist/date.d.ts +5 -0
  19. package/dist/date.js +56 -0
  20. package/dist/deploy.d.ts +75 -0
  21. package/dist/deploy.js +587 -0
  22. package/dist/dynamodb.d.ts +16 -0
  23. package/dist/dynamodb.js +479 -0
  24. package/dist/env.d.ts +6 -0
  25. package/dist/env.js +26 -0
  26. package/dist/goalert.d.ts +19 -0
  27. package/dist/goalert.js +43 -0
  28. package/dist/graphql.d.ts +6 -0
  29. package/dist/graphql.js +35 -0
  30. package/dist/indexer.d.ts +69 -0
  31. package/dist/indexer.js +1099 -0
  32. package/dist/log.d.ts +13 -0
  33. package/dist/log.js +63 -0
  34. package/dist/object-hash.d.ts +1 -0
  35. package/dist/object-hash.js +61 -0
  36. package/dist/por.d.ts +37 -0
  37. package/dist/por.js +120 -0
  38. package/dist/s3.d.ts +20 -0
  39. package/dist/s3.js +122 -0
  40. package/dist/search.d.ts +5 -0
  41. package/dist/search.js +105 -0
  42. package/dist/selector.d.ts +17 -0
  43. package/dist/selector.js +44 -0
  44. package/dist/slack.d.ts +14 -0
  45. package/dist/slack.js +29 -0
  46. package/dist/util.d.ts +4 -0
  47. package/dist/util.js +27 -0
  48. package/examples/api.ts +0 -0
  49. package/examples/indexer.ts +0 -0
  50. package/examples/mode-indexer.ts +0 -0
  51. package/package.json +1 -1
  52. package/src/deploy.ts +1 -1
@@ -0,0 +1,479 @@
1
+ // src/object-hash.ts
2
+ import xh from "@node-rs/xxhash";
3
+ function getHash(obj) {
4
+ const xxh3 = xh.xxh3.Xxh3.withSeed();
5
+ hash(obj, xxh3);
6
+ return xxh3.digest().toString(16);
7
+ }
8
+ function hash(obj, xxh3) {
9
+ if (obj === null) {
10
+ xxh3.update("null");
11
+ } else if (obj === undefined) {
12
+ xxh3.update("undefined");
13
+ } else if (typeof obj === "string") {
14
+ xxh3.update(obj);
15
+ } else if (typeof obj === "number") {
16
+ xxh3.update(obj.toString());
17
+ } else if (typeof obj === "boolean") {
18
+ xxh3.update(obj.toString());
19
+ } else if (typeof obj === "bigint") {
20
+ xxh3.update(obj.toString());
21
+ } else if (obj instanceof Date) {
22
+ xxh3.update(obj.toISOString());
23
+ } else if (Array.isArray(obj)) {
24
+ arrayHash(obj, xxh3);
25
+ } else if (obj instanceof Set) {
26
+ setHash(obj, xxh3);
27
+ } else if (obj instanceof Map) {
28
+ mapHash(obj, xxh3);
29
+ } else if (typeof obj === "object") {
30
+ objectHash(obj, xxh3);
31
+ } else {
32
+ throw new Error(`Unsupported type: ${obj}`);
33
+ }
34
+ }
35
+ function arrayHash(array, xxh3) {
36
+ xxh3.update("[");
37
+ for (const obj of array) {
38
+ hash(obj, xxh3);
39
+ }
40
+ xxh3.update("]");
41
+ }
42
+ function setHash(_set, _xxh3) {
43
+ throw new Error("Set hashing not implemented");
44
+ }
45
+ function mapHash(map, xxh3) {
46
+ const array = Array.from(map.entries()).sort(([aKey], [bKey]) => aKey.localeCompare(bKey));
47
+ for (const [key, value] of array) {
48
+ hash(key, xxh3);
49
+ hash(value, xxh3);
50
+ }
51
+ }
52
+ function objectHash(obj, xxh3) {
53
+ const array = Object.entries(obj).sort(([aKey], [bKey]) => aKey.localeCompare(bKey));
54
+ for (const [key, value] of array) {
55
+ hash(key, xxh3);
56
+ hash(value, xxh3);
57
+ }
58
+ }
59
+
60
+ // src/availability.ts
61
+ import pThrottle from "p-throttle";
62
+ import pMemoize from "p-memoize";
63
+ import QuickLRU from "quick-lru";
64
+ async function wait(time) {
65
+ return new Promise((resolve) => {
66
+ setTimeout(resolve, time);
67
+ });
68
+ }
69
+ async function exponentialRetry(func, {
70
+ maxRetry,
71
+ initialDuration,
72
+ growFactor,
73
+ test,
74
+ verbose
75
+ }) {
76
+ let retries = maxRetry;
77
+ let duration = initialDuration || 5000;
78
+ const growFactorFinal = growFactor || 2;
79
+ let result = await func();
80
+ while (!test(result) && retries > 0) {
81
+ if (verbose) {
82
+ console.log("failed attempt result", result);
83
+ console.log(`sleep for ${duration}ms after failed attempt, remaining ${retries} attempts`);
84
+ }
85
+ retries = retries - 1;
86
+ await wait(duration);
87
+ result = await func();
88
+ duration = duration * growFactorFinal;
89
+ }
90
+ if (verbose) {
91
+ console.log(`function to retry ends with status ${test(result)}, number of retries done: ${maxRetry - retries}}`);
92
+ }
93
+ return result;
94
+ }
95
+ function withRetry(func, options) {
96
+ let retries = options?.maxRetry || 3;
97
+ let duration = options?.initialDuration || 500;
98
+ const growFactorFinal = options?.growFactor || 2;
99
+ return async (...args) => {
100
+ do {
101
+ try {
102
+ return await func(...args);
103
+ } catch (error) {
104
+ retries = retries - 1;
105
+ if (retries <= 0) {
106
+ throw error;
107
+ }
108
+ await wait(duration);
109
+ duration = duration * growFactorFinal;
110
+ }
111
+ } while (retries > 0);
112
+ throw new Error("unreachable");
113
+ };
114
+ }
115
+ function memoize(func, options) {
116
+ if (!options) {
117
+ options = {};
118
+ }
119
+ if (!options.cache) {
120
+ options.cache = new QuickLRU({ maxSize: options.lruMaxSize || 1e4 });
121
+ }
122
+ if (!options.cacheKey) {
123
+ options.cacheKey = (args) => getHash(args);
124
+ }
125
+ return pMemoize(func, options);
126
+ }
127
+ // src/util.ts
128
+ function range(startAt, endAt, step) {
129
+ const arr = [];
130
+ for (let i = startAt;i <= endAt; i += step) {
131
+ arr.push([i, Math.min(endAt, i + step - 1)]);
132
+ }
133
+ return arr;
134
+ }
135
+ function arrayGroup(array, groupSize) {
136
+ const groups = [];
137
+ for (let i = 0;i < array.length; i += groupSize) {
138
+ groups.push(array.slice(i, i + groupSize));
139
+ }
140
+ return groups;
141
+ }
142
+ function fillRange(start, end) {
143
+ const result = [];
144
+ for (let i = start;i <= end; i++) {
145
+ result.push(i);
146
+ }
147
+ return result;
148
+ }
149
+ // src/dynamodb.ts
150
+ import {
151
+ DynamoDBDocumentClient,
152
+ ScanCommand,
153
+ BatchWriteCommand,
154
+ GetCommand,
155
+ PutCommand,
156
+ QueryCommand,
157
+ UpdateCommand
158
+ } from "@aws-sdk/lib-dynamodb";
159
+ import { DynamoDBClient, DescribeTableCommand } from "@aws-sdk/client-dynamodb";
160
+ var _dynamoDB;
161
+ var _docClient;
162
+ function getDynamoDB(forceNew = false) {
163
+ if (!_dynamoDB || forceNew) {
164
+ _dynamoDB = new DynamoDBClient;
165
+ }
166
+ return _dynamoDB;
167
+ }
168
+ function getDocClient(forceNew = false) {
169
+ const marshallOptions = {
170
+ convertEmptyValues: true,
171
+ removeUndefinedValues: true,
172
+ convertClassInstanceToMap: true
173
+ };
174
+ const unmarshallOptions = {
175
+ wrapNumbers: false
176
+ };
177
+ if (!_docClient || forceNew) {
178
+ _docClient = DynamoDBDocumentClient.from(getDynamoDB(), {
179
+ marshallOptions,
180
+ unmarshallOptions
181
+ });
182
+ }
183
+ return _docClient;
184
+ }
185
+ async function scanWholeTable(options) {
186
+ const dynamodb = getDocClient();
187
+ let items = [];
188
+ let count = 0;
189
+ let scannedCount = 0;
190
+ let data = await dynamodb.send(new ScanCommand(options));
191
+ while (data.LastEvaluatedKey) {
192
+ if (data.Items) {
193
+ items = items.concat(data.Items);
194
+ }
195
+ count += data.Count || 0;
196
+ scannedCount += data.ScannedCount || 0;
197
+ data = await dynamodb.send(new ScanCommand({ ...options, ExclusiveStartKey: data.LastEvaluatedKey }));
198
+ }
199
+ if (data.Items) {
200
+ items = items.concat(data.Items);
201
+ }
202
+ count += data.Count || 0;
203
+ scannedCount += data.ScannedCount || 0;
204
+ return {
205
+ Items: items,
206
+ Count: count,
207
+ ScannedCount: scannedCount
208
+ };
209
+ }
210
+ async function batchCreateRecords(tableName, records, maxWritingCapacity, verbose = false) {
211
+ if (verbose) {
212
+ console.log(`creating ${records.length} items in ${tableName}`);
213
+ }
214
+ const docClient = getDocClient();
215
+ let remainingItems = records;
216
+ let prevRemainingCount = remainingItems.length + 1;
217
+ let factor = 1;
218
+ let rejection = undefined;
219
+ while (remainingItems.length > 0 && factor <= 128 && !rejection) {
220
+ if (prevRemainingCount === remainingItems.length) {
221
+ await wait(5000 * factor);
222
+ factor = factor * 2;
223
+ }
224
+ if (factor >= 32) {
225
+ console.log(`WARNING: no progress for a long time for batchCreateRecords, please check`);
226
+ }
227
+ const slices = arrayGroup(remainingItems.slice(0, maxWritingCapacity), 25);
228
+ const results = await Promise.allSettled(slices.map((rs) => docClient.send(new BatchWriteCommand({
229
+ RequestItems: {
230
+ [tableName]: rs.map((record) => ({ PutRequest: { Item: record } }))
231
+ }
232
+ }))));
233
+ const isFulfilled = (p) => p.status === "fulfilled";
234
+ const isRejected = (p) => p.status === "rejected";
235
+ prevRemainingCount = remainingItems.length;
236
+ remainingItems = remainingItems.slice(maxWritingCapacity);
237
+ results.forEach((rs, idx) => {
238
+ if (isRejected(rs)) {
239
+ remainingItems = remainingItems.concat(slices[idx]);
240
+ rejection = rs;
241
+ } else if (isFulfilled(rs) && rs.value.UnprocessedItems && Object.keys(rs.value.UnprocessedItems).length > 0) {
242
+ const unprocessedItems = rs.value.UnprocessedItems[tableName].map((it) => it.PutRequest?.Item ?? []).flat();
243
+ remainingItems = remainingItems.concat(unprocessedItems);
244
+ }
245
+ });
246
+ if (verbose) {
247
+ console.log(`processed=${prevRemainingCount - remainingItems.length}, remaining=${remainingItems.length}`);
248
+ }
249
+ }
250
+ if (rejection) {
251
+ console.log("batchCreateRecords rejected", rejection);
252
+ throw new Error(`batchCreateRecords rejected, failed items=${remainingItems.length}`);
253
+ }
254
+ if (remainingItems.length > 0) {
255
+ console.log(`failed batchCreateRecords, failed items=${remainingItems.length}`);
256
+ throw new Error(`batchCreateRecords retry failed, failed items=${remainingItems.length}`);
257
+ }
258
+ }
259
+ async function createRecord(tableName, fields, verbose = false) {
260
+ if (verbose) {
261
+ console.log("creating", tableName, fields);
262
+ }
263
+ const docClient = getDocClient();
264
+ const params = {
265
+ TableName: tableName,
266
+ Item: fields
267
+ };
268
+ return docClient.send(new PutCommand(params));
269
+ }
270
+ async function readRecord(tableName, key, verbose = false) {
271
+ if (verbose) {
272
+ console.log("reading", tableName, key);
273
+ }
274
+ const docClient = getDocClient();
275
+ const record = await docClient.send(new GetCommand({
276
+ TableName: tableName,
277
+ Key: key
278
+ }));
279
+ return record.Item;
280
+ }
281
+ async function getRecordsByKey(tableName, keys, indexName) {
282
+ const docClient = getDocClient();
283
+ const keyNames = Object.keys(keys);
284
+ const conditionExpression = keyNames.map((key) => `#${key} = :${key}`).join(" and ");
285
+ const params = {
286
+ TableName: tableName,
287
+ KeyConditionExpression: conditionExpression,
288
+ ExpressionAttributeNames: generateExpressionNames(keyNames),
289
+ ExpressionAttributeValues: generateExpressionValues(keyNames, keys)
290
+ };
291
+ if (indexName) {
292
+ params.IndexName = indexName;
293
+ }
294
+ try {
295
+ let data = await docClient.send(new QueryCommand(params));
296
+ let items = data.Items ?? [];
297
+ while (data.LastEvaluatedKey) {
298
+ data = await docClient.send(new QueryCommand({
299
+ ...params,
300
+ ExclusiveStartKey: data.LastEvaluatedKey
301
+ }));
302
+ if (data.Items) {
303
+ items = items.concat(data.Items);
304
+ }
305
+ }
306
+ return items;
307
+ } catch (err) {
308
+ console.log(err);
309
+ if (err instanceof Error && "statusCode" in err && err.statusCode === 400) {
310
+ return null;
311
+ }
312
+ throw err;
313
+ }
314
+ }
315
+ async function getRecordByKey(tableName, keys, indexName) {
316
+ if (indexName) {
317
+ const records = await getRecordsByKey(tableName, keys, indexName);
318
+ if (records) {
319
+ return records[0];
320
+ } else {
321
+ return null;
322
+ }
323
+ } else {
324
+ return readRecord(tableName, keys);
325
+ }
326
+ }
327
+ function generateExpressionNames(keys) {
328
+ return keys.reduce((acc, key) => ({ ...acc, [`#${key}`]: key }), {});
329
+ }
330
+ function generateExpressionValues(keys, fields) {
331
+ return keys.reduce((acc, key) => ({ ...acc, [`:${key}`]: fields[key] }), {});
332
+ }
333
+ async function updateRecordByKey(tableName, idKey, fields, conditionExpressions = null, verbose = false) {
334
+ if (verbose) {
335
+ console.log("update", tableName, idKey, fields);
336
+ }
337
+ const docClient = getDocClient();
338
+ const idKeyNames = Object.keys(idKey);
339
+ const fieldsToDelete = Object.keys(fields).filter((f) => fields[f] === undefined);
340
+ const fieldsToUpdate = Object.keys(fields).filter((k) => !idKeyNames.includes(k) && !fieldsToDelete.includes(k));
341
+ let data;
342
+ if (fieldsToDelete.length > 0) {
343
+ if (verbose) {
344
+ console.log("delete fields", tableName, fieldsToDelete);
345
+ }
346
+ const deleteParams = {
347
+ TableName: tableName,
348
+ Key: idKey,
349
+ ExpressionAttributeNames: generateExpressionNames(fieldsToDelete),
350
+ UpdateExpression: `REMOVE ${fieldsToDelete.map((f) => `#${f}`).join(", ")}`,
351
+ ReturnValues: "ALL_NEW"
352
+ };
353
+ if (conditionExpressions) {
354
+ deleteParams.ConditionExpression = conditionExpressions;
355
+ }
356
+ data = await docClient.send(new UpdateCommand(deleteParams));
357
+ }
358
+ if (fieldsToUpdate.length > 0) {
359
+ if (verbose) {
360
+ console.log("update fields", tableName, fieldsToUpdate);
361
+ }
362
+ const updateExpressions = fieldsToUpdate.map((key) => `#${key} = :${key}`);
363
+ const params = {
364
+ TableName: tableName,
365
+ Key: idKey,
366
+ ExpressionAttributeNames: generateExpressionNames(fieldsToUpdate),
367
+ ExpressionAttributeValues: generateExpressionValues(fieldsToUpdate, fields),
368
+ UpdateExpression: `SET ${updateExpressions.join(", ")}`,
369
+ ReturnValues: "ALL_NEW"
370
+ };
371
+ if (conditionExpressions) {
372
+ params.ConditionExpression = conditionExpressions;
373
+ }
374
+ data = await docClient.send(new UpdateCommand(params));
375
+ }
376
+ return data?.Attributes;
377
+ }
378
+ async function batchDeleteRecords(tableName, keys) {
379
+ const docClient = getDocClient();
380
+ for (let start = 0;start < keys.length; start += 25) {
381
+ const slice = keys.slice(start, start + 25);
382
+ await docClient.send(new BatchWriteCommand({
383
+ RequestItems: {
384
+ [tableName]: slice.map((key) => {
385
+ return { DeleteRequest: { Key: key } };
386
+ })
387
+ }
388
+ }));
389
+ }
390
+ }
391
+ function getKeyName(keySchema, type) {
392
+ const key = keySchema.find((k) => k.KeyType === type);
393
+ return key?.AttributeName;
394
+ }
395
+ function getIndexKeyName(globalSecondaryIndexes, indexName, type) {
396
+ const idx = globalSecondaryIndexes.find((i) => i.IndexName === indexName);
397
+ return idx?.KeySchema && getKeyName(idx.KeySchema, type);
398
+ }
399
+ async function deleteRecordsByHashKey(tableName, indexName, hashKeyValue, verbose = false) {
400
+ const docClient = getDocClient();
401
+ const meta = await getDynamoDB().send(new DescribeTableCommand({ TableName: tableName }));
402
+ if (!meta.Table) {
403
+ throw new Error(`cannot find table ${tableName}`);
404
+ }
405
+ if (indexName && !meta.Table.GlobalSecondaryIndexes) {
406
+ throw new Error(`cannot find global secondary indexes for table ${tableName}`);
407
+ }
408
+ if (!meta.Table.KeySchema) {
409
+ throw new Error(`cannot find key schema for table ${tableName}`);
410
+ }
411
+ const hashKeyName = indexName ? getIndexKeyName(meta.Table.GlobalSecondaryIndexes, indexName, "HASH") : getKeyName(meta.Table.KeySchema, "HASH");
412
+ if (!hashKeyName) {
413
+ throw new Error(`cannot find hash key name for table ${tableName}`);
414
+ }
415
+ const mainHashKeyName = getKeyName(meta.Table.KeySchema, "HASH");
416
+ if (!mainHashKeyName) {
417
+ throw new Error(`cannot find main hash key name for table ${tableName}`);
418
+ }
419
+ const mainRangeKeyName = getKeyName(meta.Table.KeySchema, "RANGE");
420
+ if (!mainRangeKeyName) {
421
+ throw new Error(`cannot find main range key name for table ${tableName}`);
422
+ }
423
+ let totalDeleted = 0;
424
+ const params = {
425
+ TableName: tableName,
426
+ KeyConditionExpression: "#hashKeyName = :hashKeyValue",
427
+ ExpressionAttributeNames: { "#hashKeyName": hashKeyName },
428
+ ExpressionAttributeValues: { ":hashKeyValue": hashKeyValue }
429
+ };
430
+ if (indexName) {
431
+ params.IndexName = indexName;
432
+ }
433
+ let data = await docClient.send(new QueryCommand(params));
434
+ if (data.Items) {
435
+ await batchDeleteRecords(tableName, data.Items.map((item) => mainRangeKeyName ? {
436
+ [mainHashKeyName]: item[mainHashKeyName],
437
+ [mainRangeKeyName]: item[mainRangeKeyName]
438
+ } : {
439
+ [mainHashKeyName]: item[mainHashKeyName]
440
+ }));
441
+ totalDeleted += data.Items.length;
442
+ }
443
+ while (data.LastEvaluatedKey) {
444
+ data = await docClient.send(new QueryCommand({
445
+ ...params,
446
+ ExclusiveStartKey: data.LastEvaluatedKey
447
+ }));
448
+ if (data.Items) {
449
+ await batchDeleteRecords(tableName, data.Items.map((item) => mainRangeKeyName ? {
450
+ [mainHashKeyName]: item[mainHashKeyName],
451
+ [mainRangeKeyName]: item[mainRangeKeyName]
452
+ } : {
453
+ [mainHashKeyName]: item[mainHashKeyName]
454
+ }));
455
+ totalDeleted += data.Items.length;
456
+ }
457
+ }
458
+ if (verbose) {
459
+ console.log(`successfully delete ${totalDeleted} items`);
460
+ }
461
+ return totalDeleted;
462
+ }
463
+ export {
464
+ updateRecordByKey,
465
+ scanWholeTable,
466
+ getRecordsByKey,
467
+ getRecordByKey,
468
+ getDocClient,
469
+ deleteRecordsByHashKey,
470
+ createRecord,
471
+ batchDeleteRecords,
472
+ batchCreateRecords,
473
+ UpdateCommand,
474
+ ScanCommand,
475
+ QueryCommand,
476
+ PutCommand,
477
+ GetCommand,
478
+ BatchWriteCommand
479
+ };
package/dist/env.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ declare function ensureAndGet(envName: string, defaultValue?: string): string | undefined;
2
+ declare function getEnvironment(): string | undefined;
3
+ declare function getEnvOrThrow(envName: string): string;
4
+ declare function isProduction(): boolean;
5
+ declare function isDev(): boolean;
6
+ export { ensureAndGet, getEnvOrThrow, getEnvironment, isProduction, isDev };
package/dist/env.js ADDED
@@ -0,0 +1,26 @@
1
+ // src/env.ts
2
+ function ensureAndGet(envName, defaultValue) {
3
+ return process.env[envName] || defaultValue;
4
+ }
5
+ function getEnvironment() {
6
+ return ensureAndGet("SKYNET_ENVIRONMENT", "dev");
7
+ }
8
+ function getEnvOrThrow(envName) {
9
+ if (!process.env[envName]) {
10
+ throw new Error(`Must set environment variable ${envName}`);
11
+ }
12
+ return process.env[envName];
13
+ }
14
+ function isProduction() {
15
+ return getEnvironment() === "prd";
16
+ }
17
+ function isDev() {
18
+ return getEnvironment() === "dev";
19
+ }
20
+ export {
21
+ isProduction,
22
+ isDev,
23
+ getEnvironment,
24
+ getEnvOrThrow,
25
+ ensureAndGet
26
+ };
@@ -0,0 +1,19 @@
1
+ export type GoAlertAction = "close";
2
+ export interface GoAlertGenericIncomingRequest {
3
+ /** Short description of the alert sent as SMS and voice. */
4
+ summary: string;
5
+ /** Additional information about the alert, supports markdown. */
6
+ details?: string;
7
+ /** If set to `close`, it will close any matching alerts. */
8
+ action?: GoAlertAction;
9
+ /** All calls for the same service with the same `dedup` string will update the same alert (if open) or create a new one. Defaults to using summary & details together. */
10
+ dedup?: string;
11
+ }
12
+ export interface GoAlertSendOptions {
13
+ url?: string;
14
+ }
15
+ export interface GoAlertSendResult {
16
+ ok: boolean;
17
+ status: number;
18
+ }
19
+ export declare function sendGoAlertAlert(body: GoAlertGenericIncomingRequest, options?: GoAlertSendOptions): Promise<GoAlertSendResult>;
@@ -0,0 +1,43 @@
1
+ // src/goalert.ts
2
+ function getGoAlertUrl(url) {
3
+ return url || process.env["SKYNET_GOALERT_URL"];
4
+ }
5
+ async function sendGoAlertAlert(body, options = {}) {
6
+ if (!body?.summary) {
7
+ throw new Error("missing GoAlert summary");
8
+ }
9
+ const url = getGoAlertUrl(options.url);
10
+ if (!url) {
11
+ throw new Error("missing SKYNET_GOALERT_URL");
12
+ }
13
+ const response = await fetch(url, {
14
+ method: "POST",
15
+ headers: {
16
+ "Content-Type": "application/json"
17
+ },
18
+ body: JSON.stringify(body)
19
+ });
20
+ let parsed = undefined;
21
+ const contentType = response.headers.get("content-type") || "";
22
+ try {
23
+ if (contentType.includes("application/json")) {
24
+ parsed = await response.json();
25
+ } else {
26
+ const text = await response.text();
27
+ parsed = text.length ? text : undefined;
28
+ }
29
+ } catch {
30
+ parsed = undefined;
31
+ }
32
+ if (!response.ok) {
33
+ const extra = typeof parsed === "string" && parsed ? `: ${parsed}` : "";
34
+ throw new Error(`GoAlert API error ${response.status}${extra}`);
35
+ }
36
+ return {
37
+ ok: response.ok,
38
+ status: response.status
39
+ };
40
+ }
41
+ export {
42
+ sendGoAlertAlert
43
+ };
@@ -0,0 +1,6 @@
1
+ type GraphqlVariables = {
2
+ [key: string]: unknown;
3
+ };
4
+ type GraphQLVersion = "v1" | "v2";
5
+ export declare function gql<T>(query: string, variables?: GraphqlVariables, version?: GraphQLVersion): Promise<T>;
6
+ export {};
@@ -0,0 +1,35 @@
1
+ // src/graphql.ts
2
+ async function gql(query, variables = {}, version = "v1") {
3
+ if (!["v1", "v2"].includes(version)) {
4
+ throw new Error(`Invalid GraphQL version: ${version}`);
5
+ }
6
+ let endpoint = process.env["SKYNET_GRAPHQL_ENDPOINT"];
7
+ let apiKey = process.env["SKYNET_GRAPHQL_API_KEY"];
8
+ if (version === "v2") {
9
+ endpoint = process.env["SKYNET_GRAPHQL_ENDPOINT_V2"];
10
+ apiKey = process.env["SKYNET_GRAPHQL_API_KEY_V2"];
11
+ }
12
+ if (!endpoint || !apiKey) {
13
+ throw new Error(`Missing GraphQL ${version} endpoint or API key`);
14
+ }
15
+ const res = await fetch(endpoint, {
16
+ method: "POST",
17
+ headers: {
18
+ "x-api-key": apiKey,
19
+ "content-type": "application/json"
20
+ },
21
+ body: JSON.stringify({ query: query.trim(), variables })
22
+ });
23
+ if (res.ok) {
24
+ const { data, errors } = await res.json();
25
+ if (errors && errors.length > 0) {
26
+ throw new Error(JSON.stringify(errors, null, 2));
27
+ }
28
+ return data;
29
+ } else {
30
+ throw new Error(await res.text());
31
+ }
32
+ }
33
+ export {
34
+ gql
35
+ };
@@ -0,0 +1,69 @@
1
+ import type { SelectorFlags, Selector } from "./selector";
2
+ type IndexerStateValue = string | number;
3
+ type StatelessIndexerFlags<TSelector extends Selector> = SelectorFlags<TSelector & {
4
+ verbose: {
5
+ type: "boolean";
6
+ default: false;
7
+ };
8
+ }>;
9
+ type StatelessBuildFunction<TSelector extends Selector> = ({ verbose, ...selectorFlags }: StatelessIndexerFlags<TSelector>) => Promise<void> | void;
10
+ type ModeIndexerFlags<TSelector extends Selector> = SelectorFlags<TSelector & {
11
+ verbose: {
12
+ type: "boolean";
13
+ default: false;
14
+ };
15
+ mode: {
16
+ type: "string";
17
+ default: "delta";
18
+ };
19
+ from: {
20
+ aliases: ["since"];
21
+ type: "string";
22
+ };
23
+ to: {
24
+ aliases: ["until"];
25
+ type: "string";
26
+ };
27
+ status: {
28
+ type: "boolean";
29
+ default: false;
30
+ };
31
+ }>;
32
+ type ModeBuildFunction<T extends IndexerStateValue, TSelector extends Selector> = ({ from, to, verbose, ...selectorFlags }: {
33
+ from: T;
34
+ to: T;
35
+ verbose: boolean;
36
+ } & ModeIndexerFlags<TSelector>) => Promise<void | T[]> | void | T[];
37
+ type ValidateFunction<TSelector extends Selector> = ({ from, to, verbose, ...selectorFlags }: ModeIndexerFlags<TSelector>) => Promise<void> | void;
38
+ type State<T extends IndexerStateValue, TSelector extends Selector> = {
39
+ type: string;
40
+ updateInterval?: () => number;
41
+ getMinId: (selectorFlags: ModeIndexerFlags<TSelector>) => Promise<T>;
42
+ getMaxId: (selectorFlags: ModeIndexerFlags<TSelector>) => Promise<T>;
43
+ };
44
+ declare function increaseId<T extends IndexerStateValue = IndexerStateValue>(type: string, currentId: T, n: number): T;
45
+ declare function createModeIndexerApp<T extends IndexerStateValue, TSelector extends Selector>({ binaryName, name, selector, build, buildBatchSize, buildConcurrency, validate, validateBatchSize, validateConcurrency, maxRetry, state, }: {
46
+ binaryName: string;
47
+ name: string;
48
+ selector: TSelector;
49
+ build: ModeBuildFunction<T, TSelector>;
50
+ maxRetry?: number;
51
+ buildBatchSize?: number;
52
+ buildConcurrency?: number;
53
+ validate?: ValidateFunction<TSelector>;
54
+ validateBatchSize?: number;
55
+ validateConcurrency?: number;
56
+ state: State<T, TSelector>;
57
+ }): {
58
+ run: () => Promise<void>;
59
+ };
60
+ declare function createIndexerApp<TSelector extends Selector>({ binaryName, selector, build, maxRetry, }: {
61
+ binaryName: string;
62
+ selector?: TSelector;
63
+ build: StatelessBuildFunction<TSelector>;
64
+ maxRetry?: number;
65
+ }): {
66
+ run: () => Promise<void>;
67
+ };
68
+ export { increaseId, createModeIndexerApp, createIndexerApp };
69
+ export type { IndexerStateValue, State, StatelessBuildFunction, ModeBuildFunction, ValidateFunction };