triangle-utils 1.2.12 → 1.3.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.
@@ -1,7 +1,13 @@
1
1
  import { S3, NoSuchKey, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"
2
2
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
3
+ import { Config } from "./types/Config"
3
4
 
4
- function parse_s3_filename(s3_filename) {
5
+ interface S3Key {
6
+ Bucket : string,
7
+ Key : string
8
+ }
9
+
10
+ function parse_s3_filename(s3_filename : string) : S3Key | undefined {
5
11
  if (s3_filename.substring(0, 5) !== "s3://") {
6
12
  return undefined
7
13
  }
@@ -11,13 +17,15 @@ function parse_s3_filename(s3_filename) {
11
17
  return { Bucket : bucket, Key : filename }
12
18
  }
13
19
 
14
- export default class Utils_S3 {
20
+ export class Utils_S3 {
21
+
22
+ readonly s3 : S3
15
23
 
16
- constructor(config) {
24
+ constructor(config : Config) {
17
25
  this.s3 = new S3({ region : config.region })
18
26
  }
19
27
 
20
- async file_exists(s3_filename) {
28
+ async file_exists(s3_filename : string) {
21
29
  const s3_key = parse_s3_filename(s3_filename)
22
30
  if (s3_key === undefined) {
23
31
  return undefined
@@ -30,15 +38,19 @@ export default class Utils_S3 {
30
38
  }
31
39
  }
32
40
 
33
- async get_file(s3_filename, json=false) {
41
+ async get_file(s3_filename : string) : Promise<string | undefined> {
34
42
  const s3_key = parse_s3_filename(s3_filename)
35
43
  if (s3_key === undefined) {
36
44
  return undefined
37
45
  }
38
46
  try {
39
- return await this.s3.getObject(s3_key)
40
- .then(response => response.Body.transformToString("utf-8"))
41
- .then(text => json ? JSON.parse(text) : text)
47
+ const response = await this.s3.getObject(s3_key)
48
+ const body = response.Body
49
+ if (body === undefined) {
50
+ return undefined
51
+ }
52
+ const text = body.transformToString("utf-8")
53
+ return text
42
54
  } catch (error) {
43
55
  if (error instanceof NoSuchKey) {
44
56
  return undefined
@@ -47,8 +59,10 @@ export default class Utils_S3 {
47
59
  }
48
60
  }
49
61
 
50
- async create_file(s3_filename, content, options = {}) {
51
- const content_type = options.content_type !== undefined ? options.content_type : {}
62
+ async create_file(s3_filename : string, content : string | Buffer, options : {
63
+ content_type? : string
64
+ } = {}) {
65
+ const content_type = options.content_type !== undefined ? options.content_type : undefined
52
66
 
53
67
  const exists = await this.file_exists(s3_filename)
54
68
  if (exists) {
@@ -65,27 +79,33 @@ export default class Utils_S3 {
65
79
  })
66
80
  }
67
81
 
68
- async delete_file(s3_filename) {
82
+ async delete_file(s3_filename : string) : Promise<void>{
69
83
  const s3_key = parse_s3_filename(s3_filename)
70
84
  if (s3_key === undefined) {
71
85
  return undefined
72
86
  }
73
- return await this.s3.deleteObject(s3_key)
87
+ await this.s3.deleteObject(s3_key)
74
88
  }
75
89
 
76
- async generate_download_url(s3_filename) {
90
+ async generate_download_url(s3_filename : string) : Promise<string | undefined> {
77
91
  const exists = await this.file_exists(s3_filename)
78
92
  if (!exists) {
79
93
  return undefined
80
94
  }
81
95
  const s3_key = parse_s3_filename(s3_filename)
96
+ if (s3_key === undefined) {
97
+ return undefined
98
+ }
82
99
  const command = new GetObjectCommand(s3_key)
83
100
  const url = await getSignedUrl(this.s3, command)
84
101
  return url
85
102
  }
86
103
 
87
- async generate_upload_url(s3_filename) {
104
+ async generate_upload_url(s3_filename : string) : Promise<string | undefined> {
88
105
  const s3_key = parse_s3_filename(s3_filename)
106
+ if (s3_key === undefined) {
107
+ return undefined
108
+ }
89
109
  const command = new PutObjectCommand(s3_key)
90
110
  const url = await getSignedUrl(this.s3, command)
91
111
  return url
@@ -1,43 +1,52 @@
1
- import { S3Vectors } from "@aws-sdk/client-s3vectors"
1
+ import { ListVectorsCommandInput, S3Vectors } from "@aws-sdk/client-s3vectors"
2
+ import { Config } from "./types/Config"
2
3
 
3
- export default class Utils_S3Vectors {
4
+ interface Vector {
5
+ key : string,
6
+ data : number[],
7
+ metadata : any
8
+ }
4
9
 
5
- constructor(config) {
10
+ export class Utils_S3Vectors {
11
+
12
+ readonly s3vectors : S3Vectors
13
+
14
+ constructor(config : Config) {
6
15
  this.s3vectors = new S3Vectors({ region : config.region })
7
16
  }
8
17
 
9
- async scan(vector_bucket, vector_index) {
10
- const vectors = []
11
- let next_token = undefined
18
+ async scan(vector_bucket : string, vector_index : string) : Promise<string[]> {
19
+ const vector_keys = []
20
+ let next_token : string | undefined = undefined
12
21
  while (true) {
13
- const response = (next_token === undefined) ? (await this.s3vectors.listVectors({
14
- vectorBucketName : vector_bucket,
15
- indexName : vector_index,
16
- maxResults : 1000
17
- })) : (await this.s3vectors.listVectors({
22
+ const request : ListVectorsCommandInput = {
18
23
  vectorBucketName : vector_bucket,
19
24
  indexName : vector_index,
20
25
  maxResults : 1000,
21
26
  nextToken : next_token
22
- }))
23
- vectors.push(...response.vectors)
27
+ }
28
+ const response = await this.s3vectors.listVectors(request)
29
+ if (response === undefined || response.vectors === undefined) {
30
+ return []
31
+ }
32
+ vector_keys.push(...response.vectors.map(vector => vector.key))
24
33
  next_token = response.nextToken
25
34
  if (next_token === undefined) {
26
- return vectors
35
+ return vector_keys.filter(vector_key => vector_key !== undefined)
27
36
  }
28
37
  }
29
38
  }
30
39
 
31
- async get(vector_bucket, vector_index, vector_key) {
32
- const vector = await this.s3vectors.getVectors({
40
+ async get(vector_bucket : string, vector_index : string, vector_key : string) : Promise<Vector | undefined> {
41
+ const response = await this.s3vectors.getVectors({
33
42
  vectorBucketName : vector_bucket,
34
43
  indexName : vector_index,
35
44
  keys : [vector_key],
36
45
  returnData : true,
37
46
  returnMetadata : true
38
47
  })
39
- .then(response => response.vectors[0])
40
- if (vector === undefined) {
48
+ const vector = response.vectors?.[0]
49
+ if (vector === undefined || vector.key === undefined || vector.data === undefined || vector.data.float32 === undefined || vector.metadata === undefined) {
41
50
  return undefined
42
51
  }
43
52
  return {
@@ -47,10 +56,10 @@ export default class Utils_S3Vectors {
47
56
  }
48
57
  }
49
58
 
50
- async batch_get(vector_bucket, vector_index, vector_keys) {
59
+ async batch_get(vector_bucket : string, vector_index : string, vector_keys : string[]) {
51
60
  const vectors = []
52
61
  for (let i = 0; i < vector_keys.length; i += 100) {
53
- const vectors_by_key = await this.s3vectors.getVectors({
62
+ const new_vectors = await this.s3vectors.getVectors({
54
63
  vectorBucketName : vector_bucket,
55
64
  indexName : vector_index,
56
65
  keys : vector_keys.slice(i, i + 100),
@@ -58,20 +67,25 @@ export default class Utils_S3Vectors {
58
67
  returnMetadata : true
59
68
  })
60
69
  .then(response => response.vectors)
61
- .then(vectors => Object.fromEntries(vectors.map(vector => [
70
+ if (new_vectors === undefined) {
71
+ return []
72
+ }
73
+ const vectors_by_key : Record<string, Vector | undefined> = Object.fromEntries(new_vectors
74
+ .map(vector => [
62
75
  vector.key,
63
- {
76
+ vector.key !== undefined && vector.data !== undefined && vector.data.float32 !== undefined && vector.metadata !== undefined ? {
64
77
  key : vector.key,
65
78
  data : vector.data.float32,
66
79
  metadata : vector.metadata
67
- }
68
- ])))
80
+ } : undefined
81
+ ])
82
+ )
69
83
  vectors.push(...vector_keys.map(vector_key => vectors_by_key[vector_key]))
70
84
  }
71
85
  return vectors
72
86
  }
73
87
 
74
- async create(vector_bucket, vector_index, vector) {
88
+ async create(vector_bucket : string, vector_index : string, vector : Vector) : Promise<void> {
75
89
  await this.s3vectors.putVectors({
76
90
  vectorBucketName : vector_bucket,
77
91
  indexName : vector_index,
@@ -85,7 +99,7 @@ export default class Utils_S3Vectors {
85
99
  })
86
100
  }
87
101
 
88
- async batch_create(vector_bucket, vector_index, vectors) {
102
+ async batch_create(vector_bucket : string, vector_index : string, vectors : Vector[]) : Promise<void> {
89
103
  for (let i = 0; i < vectors.length; i += 100) {
90
104
  await this.s3vectors.putVectors({
91
105
  vectorBucketName : vector_bucket,
@@ -102,7 +116,7 @@ export default class Utils_S3Vectors {
102
116
  }
103
117
  }
104
118
 
105
- async delete(vector_bucket, vector_index, vector_key) {
119
+ async delete(vector_bucket : string, vector_index : string, vector_key : string) : Promise<void> {
106
120
  await this.s3vectors.deleteVectors({
107
121
  vectorBucketName : vector_bucket,
108
122
  indexName : vector_index,
@@ -110,7 +124,7 @@ export default class Utils_S3Vectors {
110
124
  })
111
125
  }
112
126
 
113
- async batch_delete(vector_bucket, vector_index, vector_keys) {
127
+ async batch_delete(vector_bucket : string, vector_index : string, vector_keys : string[]) : Promise<void> {
114
128
  for (let i = 0; i < vector_keys.length; i += 100) {
115
129
  await this.s3vectors.deleteVectors({
116
130
  vectorBucketName : vector_bucket,
@@ -120,7 +134,7 @@ export default class Utils_S3Vectors {
120
134
  }
121
135
  }
122
136
 
123
- async query(vector_bucket, vector_index, data, num_vectors, filters) {
137
+ async query(vector_bucket : string, vector_index : string, data : number[], num_vectors : number, filters : Record<string, any> = {}) {
124
138
  const response = await this.s3vectors.queryVectors({
125
139
  vectorBucketName : vector_bucket,
126
140
  indexName : vector_index,
@@ -1,15 +1,19 @@
1
1
  import { youtube_v3 } from "googleapis"
2
+ import { Config } from "./types/Config"
2
3
 
3
- export default class Utils_Youtube {
4
+ export class Utils_Youtube {
4
5
 
5
- constructor(config) {
6
+ readonly config : Config
7
+ readonly youtube : youtube_v3.Youtube
8
+
9
+ constructor(config : Config) {
6
10
  this.config = config
7
11
  this.youtube = new youtube_v3.Youtube({
8
12
  auth: config.youtube_api_key
9
13
  })
10
14
  }
11
15
 
12
- async batch_get_videos(video_ids, attributes = ["snippet", "contentDetails"]) {
16
+ async batch_get_videos(video_ids : string[], attributes : string[] = ["snippet", "contentDetails"]) {
13
17
  for (let i = 0; i < 3; i++) {
14
18
  try {
15
19
  const response = await this.youtube.videos.list({
@@ -20,9 +24,12 @@ export default class Utils_Youtube {
20
24
  console.log("Status", response.status)
21
25
  return undefined
22
26
  }
23
- return response.data.items
24
- } catch (e) {
25
- console.log(e.stack)
27
+ const items = response.data.items
28
+ return items
29
+ } catch (error) {
30
+ if (error instanceof Error) {
31
+ console.log(error.stack)
32
+ }
26
33
  console.log("Failed to get from Scraping Bee.")
27
34
  }
28
35
  }
@@ -0,0 +1,9 @@
1
+ export interface Config {
2
+ region : string,
3
+ // fec_api_key? : string,
4
+ google_app_password? : string,
5
+ scraping_bee_api_key? : string,
6
+ youtube_api_key? : string,
7
+ citizenportal_api_key? : string,
8
+ [key : string]: any
9
+ }
@@ -0,0 +1,85 @@
1
+ import { Config } from "../src/types/Config.ts"
2
+ import { Utils } from "../src/Utils.ts"
3
+ import { SecretsManager } from "@aws-sdk/client-secrets-manager"
4
+
5
+ const region = "us-east-1"
6
+
7
+ let config : Config = { region : region }
8
+
9
+ beforeAll(async () => {
10
+ // const secret_name = "api_keys"
11
+
12
+ // const secrets_manager = new SecretsManager({ region: "us-east-1" })
13
+
14
+ // const api_keys = await secrets_manager.getSecretValue({
15
+ // SecretId: secret_name
16
+ // }).then(response => JSON.parse(response.SecretString))
17
+
18
+ // config = {
19
+ // google_email : "louishou@triangleanalytics.com",
20
+ // alerts_email : "alerts@triangleanalytics.com",
21
+ // region : "us-east-1",
22
+ // ...api_keys
23
+ // }
24
+
25
+ })
26
+
27
+ describe("Testing Utils", () => {
28
+ describe("Testing Misc", () => {
29
+ test("email", async () => {
30
+ const utils = new Utils(config)
31
+ await utils.admin_alert("Testing Admin Alert.")
32
+ })
33
+ test("sha256", async () => {
34
+ const utils = new Utils(config)
35
+ const hash = await utils.sha256("Triangle Analytics")
36
+ expect(hash).toEqual("7f6f79a3fdee7c38bb3b0381211575a35ffcfcf5867d6045b07b4181e6f24153")
37
+ })
38
+ })
39
+ describe("Testing DynamoDB", () => {
40
+ test("scan", async () => {
41
+ const utils = new Utils(config)
42
+ const candidates = await utils.dynamodb.scan("candidates")
43
+ expect(candidates).toBeDefined()
44
+ expect(candidates.length).toBeGreaterThan(3000)
45
+ })
46
+ test("get; single key", async () => {
47
+ const utils = new Utils(config)
48
+ const candidate = await utils.dynamodb.get("candidates", { candidate_id : "S6MI00392" })
49
+ expect(candidate).toBeDefined()
50
+ if (candidate === undefined) {
51
+ return
52
+ }
53
+ expect(candidate.fec_name).toBeDefined()
54
+ })
55
+ test("get; composite key", async () => {
56
+ const utils = new Utils(config)
57
+ const candidate_article = await utils.dynamodb.get("candidate_articles", { candidate_id : "S6MI00392", article_id : "2025-08-1674855ce5e59c2b6a19a165037dffa5627b1cee071def07a614acb1c28d0e2eff" })
58
+ expect(candidate_article).toBeDefined()
59
+ if (candidate_article === undefined) {
60
+ return
61
+ }
62
+ expect(candidate_article.url).toBeDefined()
63
+ })
64
+ test("get_max", async () => {
65
+ const utils = new Utils(config)
66
+ const candidate_article = await utils.dynamodb.get_max("candidate_articles", { candidate_id : "S6MI00392" })
67
+ expect(candidate_article).toBeDefined()
68
+ if (candidate_article === undefined) {
69
+ return
70
+ }
71
+ expect(candidate_article.url).toBeDefined()
72
+ expect(candidate_article.article_id).toBeDefined()
73
+ expect(candidate_article.article_id.localeCompare("2025-08-1674855ce5e59c2b6a19a165037dffa5627b1cee071def07a614acb1c28d0e2eff") > 0).toBeTruthy()
74
+ })
75
+ test("query", async () => {
76
+ const utils = new Utils(config)
77
+ const candidate_articles = await utils.dynamodb.query("candidate_articles", { candidate_id : "S6MI00392" })
78
+ expect(candidate_articles).toBeDefined()
79
+ if (candidate_articles === undefined) {
80
+ return
81
+ }
82
+ expect(candidate_articles.length).toBeGreaterThan(400)
83
+ })
84
+ })
85
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist",
4
+
5
+ // Treat files as modules even if it doesn't use import/export
6
+ "moduleDetection": "force",
7
+
8
+ // Ignore module structure
9
+ "module":"esnext",
10
+ "target" : "es2022",
11
+ "noImplicitAny" : true,
12
+ "noImplicitReturns" : true,
13
+ "moduleResolution": "node",
14
+
15
+ // Allow JS files to be imported from TS and vice versa
16
+ "allowJs": true,
17
+
18
+ // Use correct ESM import behavior
19
+ "esModuleInterop": true,
20
+ "resolveJsonModule": true,
21
+ // Disallow features that require cross-file awareness
22
+ "isolatedModules": true,
23
+ "lib": ["es2015"],
24
+ "rootDir": "./",
25
+ "skipLibCheck": true,
26
+ "maxNodeModuleJsDepth": 0
27
+ },
28
+
29
+ "include": ["./src/**/*.*"],
30
+ "exclude":["node_modules"]
31
+ }
package/Utils.js DELETED
@@ -1,33 +0,0 @@
1
- import states from "./states.js"
2
-
3
- import Utils_Misc from "./utils/Utils_Misc.js"
4
- import Utils_DynamoDB from "./utils/Utils_DynamoDB.js"
5
- import Utils_S3 from "./utils/Utils_S3.js"
6
- import Utils_Bedrock from "./utils/Utils_Bedrock.js"
7
- import Utils_Bee from "./utils/Utils_Bee.js"
8
- import Utils_S3Vectors from "./utils/Utils_S3Vectors.js"
9
- import Utils_Youtube from "./utils/Utils_Youtube.js"
10
- import Utils_CitizenPortal from "./utils/Utils_CitizenPortal.js"
11
-
12
- export default class Utils extends Utils_Misc {
13
- constructor(config) {
14
- super(config)
15
- this.config = config
16
- this.states = states
17
- if (config.region !== undefined) {
18
- this.dynamodb = new Utils_DynamoDB(config)
19
- this.s3 = new Utils_S3(config)
20
- this.s3vectors = new Utils_S3Vectors(config)
21
- this.bedrock = new Utils_Bedrock(config)
22
- }
23
- if (config.scraping_bee_api_key !== undefined) {
24
- this.bee = new Utils_Bee(config)
25
- }
26
- if (config.youtube_api_key !== undefined) {
27
- this.youtube = new Utils_Youtube(config)
28
- }
29
- if (config.citizenportal_api_key !== undefined) {
30
- this.citizenportal = new Utils_CitizenPortal(config)
31
- }
32
- }
33
- }
package/test.js DELETED
@@ -1,62 +0,0 @@
1
- import Utils from "./Utils.js";
2
- import { SecretsManager } from "@aws-sdk/client-secrets-manager"
3
-
4
- const secret_name = "api_keys"
5
-
6
- const secrets_manager = new SecretsManager({ region: "us-east-2" })
7
-
8
- const api_keys = await secrets_manager.getSecretValue({
9
- SecretId: secret_name
10
- }).then(response => JSON.parse(response.SecretString))
11
-
12
- const config = {
13
- google_email : "louishou@triangleanalytics.com",
14
- alerts_email : "alerts@triangleanalytics.com",
15
- region : "us-east-1",
16
- ...api_keys
17
- }
18
-
19
- const utils = new Utils(config)
20
-
21
- // const videos = await utils.youtube.batch_get_videos(["N4dyOzjjYYk"])
22
-
23
- // console.log(videos)
24
-
25
- const states = await utils.citizenportal.get("taxonomies", {
26
- parent_id : "2145",
27
- limit : 100
28
- })
29
-
30
- console.log(states)
31
-
32
- // const documents = await utils.citizenportal.get_jurisdiction_documents(28222)
33
-
34
- // console.log(documents.length)
35
-
36
- // // let response = await fetch("https://api.citizenportal.ai/api/v1/meetings", {
37
- // // headers : {
38
- // // "X-API-Key" : config.citizenportal_api_key
39
- // // }
40
- // // })
41
- // // .then(response => response.json())
42
-
43
- // // for (const doc of response.data) {
44
-
45
- // // response = await fetch("https://api.citizenportal.ai/api/v1/meetings/" + doc.packageId + "/transcript", {
46
- // // headers : {
47
- // // "X-API-Key" : config.citizenportal_api_key
48
- // // }
49
- // // })
50
- // // .then(response => response.json())
51
- // // console.log(response)
52
- // // }
53
-
54
- // // console.log(response)
55
- // let response = await fetch("https://api.citizenportal.ai/api/v1/meetings/606000/transcript", {
56
- // headers : {
57
- // "X-API-Key" : config.citizenportal_api_key
58
- // }
59
- // })
60
- // .then(response => response.json())
61
-
62
- // console.log(response)
@@ -1,26 +0,0 @@
1
- export default class Utils_CitizenPortal {
2
-
3
- constructor(config) {
4
- this.config = config
5
- }
6
-
7
- async get(path, options) {
8
- for (let i = 0; i < 3; i++) {
9
- try {
10
- const citizenportal_api = "https://api.citizenportal.ai/api/v1/" + path + "?" + Object.entries(options).filter(([key, value]) => value !== undefined).map(([key, value]) => key + "=" + value).join("&")
11
- console.log(citizenportal_api)
12
- const response = await fetch(citizenportal_api, {
13
- headers : {
14
- "X-API-Key" : this.config.citizenportal_api_key
15
- }
16
- }).then(response => response.json())
17
- return response
18
- } catch (e) {
19
- console.log(e.stack)
20
- console.log("Failed to get from CitizenPortal.")
21
- }
22
- }
23
- console.log("Failed to get from CitizenPortal, quitting.")
24
- return undefined
25
- }
26
- }