opticedge-cloud-utils 1.1.13 → 1.1.15
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/.eslintignore +2 -2
- package/.eslintrc.js +30 -30
- package/.prettierignore +4 -4
- package/.prettierrc +8 -8
- package/dist/chunk.d.ts +1 -0
- package/dist/chunk.js +37 -0
- package/dist/env.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/jest.config.js +12 -12
- package/package.json +40 -40
- package/src/auth.ts +29 -29
- package/src/chunk.ts +33 -0
- package/src/db/mongo.ts +45 -45
- package/src/db/mongo2.ts +45 -45
- package/src/db/mongo3.ts +63 -63
- package/src/env.ts +10 -8
- package/src/index.ts +14 -13
- package/src/parser.ts +9 -9
- package/src/regex.ts +19 -19
- package/src/retry.ts +155 -155
- package/src/secrets.ts +21 -21
- package/src/task.ts +132 -132
- package/src/tw/utils.ts +18 -18
- package/src/tw/wallet.ts +68 -68
- package/src/validator.ts +4 -4
- package/tests/auth.test.ts +89 -89
- package/tests/chunk.test.ts +48 -0
- package/tests/db/mongo.test.ts +82 -82
- package/tests/db/mongo2.test.ts +96 -96
- package/tests/db/mongo3.test.ts +149 -149
- package/tests/env.test.ts +18 -18
- package/tests/parser.test.ts +26 -26
- package/tests/regex.test.ts +69 -69
- package/tests/retry.test.ts +416 -416
- package/tests/secrets.test.ts +44 -44
- package/tests/task.test.ts +337 -337
- package/tests/tw/utils.test.ts +29 -29
- package/tests/tw/wallet.test.ts +206 -206
- package/tests/validator.ts +37 -37
- package/tsconfig.json +17 -17
package/src/env.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
export function getEnv(name: string, fallback?: any): any {
|
|
4
|
+
const value = process.env[name]
|
|
5
|
+
if (!value) {
|
|
6
|
+
if (fallback !== undefined) return fallback
|
|
7
|
+
throw new Error(`Missing env var ${name}`)
|
|
8
|
+
}
|
|
9
|
+
return value
|
|
10
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
export * from './db/mongo'
|
|
2
|
-
export * from './db/mongo2'
|
|
3
|
-
export * from './db/mongo3'
|
|
4
|
-
export * from './tw/utils'
|
|
5
|
-
export * from './tw/wallet'
|
|
6
|
-
export * from './auth'
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './
|
|
9
|
-
export * from './
|
|
10
|
-
export * from './
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './
|
|
1
|
+
export * from './db/mongo'
|
|
2
|
+
export * from './db/mongo2'
|
|
3
|
+
export * from './db/mongo3'
|
|
4
|
+
export * from './tw/utils'
|
|
5
|
+
export * from './tw/wallet'
|
|
6
|
+
export * from './auth'
|
|
7
|
+
export * from './chunk'
|
|
8
|
+
export * from './env'
|
|
9
|
+
export * from './parser'
|
|
10
|
+
export * from './regex'
|
|
11
|
+
export * from './retry'
|
|
12
|
+
export * from './secrets'
|
|
13
|
+
export * from './task'
|
|
14
|
+
export * from './validator'
|
package/src/parser.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Helper: accept number or numeric-string, return number or null
|
|
2
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
|
-
const toNumber = (v: any): number | null => {
|
|
4
|
-
if (typeof v === 'number' && Number.isFinite(v)) return v
|
|
5
|
-
if (typeof v === 'string' && /^\d+$/.test(v)) return Number(v)
|
|
6
|
-
return null
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export { toNumber }
|
|
1
|
+
// Helper: accept number or numeric-string, return number or null
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
|
+
const toNumber = (v: any): number | null => {
|
|
4
|
+
if (typeof v === 'number' && Number.isFinite(v)) return v
|
|
5
|
+
if (typeof v === 'string' && /^\d+$/.test(v)) return Number(v)
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { toNumber }
|
package/src/regex.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Escape user input so it can be safely used as a regex pattern string.
|
|
3
|
-
*
|
|
4
|
-
* - Escapes regex metacharacters.
|
|
5
|
-
* - Replaces any run of whitespace with the token `\s+` so searches tolerate variable whitespace.
|
|
6
|
-
* - Caps input length to avoid pathological long-input ReDoS vectors (default cap = 100).
|
|
7
|
-
*
|
|
8
|
-
* @param input - user-provided string to escape
|
|
9
|
-
* @param maxLength - maximum number of characters to consider from input (default 100)
|
|
10
|
-
* @returns a safe regex pattern string (do NOT wrap with // when passing to Mongo $regex)
|
|
11
|
-
*/
|
|
12
|
-
export function escapeForRegex(input: string, maxLength = 100): string {
|
|
13
|
-
const safeStr = String(input ?? '')
|
|
14
|
-
const capped = safeStr.length > maxLength ? safeStr.slice(0, maxLength) : safeStr
|
|
15
|
-
// escape regex metacharacters: . * + ? ^ $ { } ( ) | [ ] \ /
|
|
16
|
-
const escaped = capped.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
17
|
-
// convert any whitespace run to the regex token \s+ (as a string)
|
|
18
|
-
return escaped.replace(/\s+/g, '\\s+')
|
|
19
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Escape user input so it can be safely used as a regex pattern string.
|
|
3
|
+
*
|
|
4
|
+
* - Escapes regex metacharacters.
|
|
5
|
+
* - Replaces any run of whitespace with the token `\s+` so searches tolerate variable whitespace.
|
|
6
|
+
* - Caps input length to avoid pathological long-input ReDoS vectors (default cap = 100).
|
|
7
|
+
*
|
|
8
|
+
* @param input - user-provided string to escape
|
|
9
|
+
* @param maxLength - maximum number of characters to consider from input (default 100)
|
|
10
|
+
* @returns a safe regex pattern string (do NOT wrap with // when passing to Mongo $regex)
|
|
11
|
+
*/
|
|
12
|
+
export function escapeForRegex(input: string, maxLength = 100): string {
|
|
13
|
+
const safeStr = String(input ?? '')
|
|
14
|
+
const capped = safeStr.length > maxLength ? safeStr.slice(0, maxLength) : safeStr
|
|
15
|
+
// escape regex metacharacters: . * + ? ^ $ { } ( ) | [ ] \ /
|
|
16
|
+
const escaped = capped.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
17
|
+
// convert any whitespace run to the regex token \s+ (as a string)
|
|
18
|
+
return escaped.replace(/\s+/g, '\\s+')
|
|
19
|
+
}
|
package/src/retry.ts
CHANGED
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
// src/retry.ts
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
-
|
|
4
|
-
import axios from 'axios'
|
|
5
|
-
|
|
6
|
-
export type RetryOptions = {
|
|
7
|
-
retries?: number
|
|
8
|
-
baseDelayMs?: number
|
|
9
|
-
maxDelayMs?: number
|
|
10
|
-
timeoutMs?: number
|
|
11
|
-
signal?: AbortSignal | null
|
|
12
|
-
isRetryable?: (err: any) => boolean
|
|
13
|
-
onRetry?: (err: any, attempt: number, nextDelayMs: number) => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function sleep(ms: number, signal?: AbortSignal | null) {
|
|
17
|
-
return new Promise<void>((resolve, reject) => {
|
|
18
|
-
if (signal?.aborted) return reject(new Error('Aborted'))
|
|
19
|
-
const t = setTimeout(() => {
|
|
20
|
-
signal?.removeEventListener?.('abort', onAbort)
|
|
21
|
-
resolve()
|
|
22
|
-
}, ms)
|
|
23
|
-
|
|
24
|
-
function onAbort() {
|
|
25
|
-
clearTimeout(t)
|
|
26
|
-
signal?.removeEventListener?.('abort', onAbort)
|
|
27
|
-
reject(new Error('Aborted'))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
signal?.addEventListener?.('abort', onAbort)
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function isRetryableDefault(err: any): boolean {
|
|
35
|
-
if (!err) return false
|
|
36
|
-
|
|
37
|
-
// gRPC numeric code for UNAVAILABLE is 14
|
|
38
|
-
if (err?.code === 14 || err?.grpcCode === 14) return true
|
|
39
|
-
|
|
40
|
-
// Common Node network error codes
|
|
41
|
-
const syscode = String(err?.code ?? '').toLowerCase()
|
|
42
|
-
if (
|
|
43
|
-
syscode === 'econnreset' ||
|
|
44
|
-
syscode === 'etimedout' ||
|
|
45
|
-
syscode === 'econnrefused' ||
|
|
46
|
-
syscode === 'enotfound' ||
|
|
47
|
-
syscode === 'enetunreach' ||
|
|
48
|
-
syscode === 'eai_again' ||
|
|
49
|
-
syscode === 'ehostunreach' ||
|
|
50
|
-
syscode === 'epipe'
|
|
51
|
-
) {
|
|
52
|
-
return true
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// HTTP/Fetch style errors (axios / fetch)
|
|
56
|
-
const status = err?.response?.status ?? err?.status
|
|
57
|
-
if (typeof status === 'number') {
|
|
58
|
-
// retry on 429 (rate limit) and 5xx (server errors)
|
|
59
|
-
if (status === 429 || (status >= 500 && status < 600)) return true
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Message-based heuristics: include variants like "timed out" as well as "timeout"
|
|
63
|
-
const msg = String(err?.message ?? '').toLowerCase()
|
|
64
|
-
|
|
65
|
-
// Check for common retryable phrases:
|
|
66
|
-
// - timeout, timed out, time out
|
|
67
|
-
// - temporary
|
|
68
|
-
// - unavailable
|
|
69
|
-
if (/(timeout|timed out|time out|temporary|unavailable)/.test(msg)) return true
|
|
70
|
-
|
|
71
|
-
// Also accept explicit substrings for network-errno-like messages
|
|
72
|
-
if (msg.includes('econnreset') || msg.includes('etimedout')) return true
|
|
73
|
-
|
|
74
|
-
return false
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function isRetryableAxios(err: any): boolean {
|
|
78
|
-
if (!err) return false
|
|
79
|
-
|
|
80
|
-
// If axios knows about it:
|
|
81
|
-
if (axios.isAxiosError(err)) {
|
|
82
|
-
// No response -> network error / timeout -> retry
|
|
83
|
-
if (!err.response) return true
|
|
84
|
-
|
|
85
|
-
const status = err.response.status
|
|
86
|
-
// retry on 429 or 5xx
|
|
87
|
-
if (status === 429 || (status >= 500 && status < 600)) return true
|
|
88
|
-
|
|
89
|
-
// 4xx client errors -> do not retry
|
|
90
|
-
return false
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Fallback to your existing heuristics (gRPC codes, node syscodes, etc.)
|
|
94
|
-
return isRetryableDefault(err)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export async function retry<T>(fn: () => Promise<T>, opts: RetryOptions = {}): Promise<T> {
|
|
98
|
-
const retries = opts.retries ?? 4
|
|
99
|
-
const base = opts.baseDelayMs ?? 200
|
|
100
|
-
const max = opts.maxDelayMs ?? 5000
|
|
101
|
-
const timeoutMs = opts.timeoutMs
|
|
102
|
-
const signal = opts.signal ?? null
|
|
103
|
-
const isRetryable = opts.isRetryable ?? isRetryableDefault
|
|
104
|
-
const onRetry = opts.onRetry
|
|
105
|
-
|
|
106
|
-
const start = Date.now()
|
|
107
|
-
|
|
108
|
-
let attempt = 0
|
|
109
|
-
let lastErr: any
|
|
110
|
-
|
|
111
|
-
// attempt = number of retries already performed; initial try is attempt==0
|
|
112
|
-
while (attempt <= retries) {
|
|
113
|
-
if (signal?.aborted) throw new Error('Aborted')
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
return await fn()
|
|
117
|
-
} catch (err) {
|
|
118
|
-
lastErr = err
|
|
119
|
-
|
|
120
|
-
if (!isRetryable(err)) {
|
|
121
|
-
throw err
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (attempt >= retries) break
|
|
125
|
-
|
|
126
|
-
// compute next retry (1-based)
|
|
127
|
-
const nextAttempt = attempt + 1
|
|
128
|
-
const exp = Math.min(max, base * Math.pow(2, nextAttempt - 1))
|
|
129
|
-
const delay = Math.floor(Math.random() * exp)
|
|
130
|
-
|
|
131
|
-
if (typeof timeoutMs === 'number') {
|
|
132
|
-
const elapsed = Date.now() - start
|
|
133
|
-
if (elapsed + delay > timeoutMs) {
|
|
134
|
-
break
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
// pass 1-based attempt to onRetry to mean "this is the upcoming retry number"
|
|
140
|
-
onRetry?.(err, nextAttempt, delay)
|
|
141
|
-
} catch {
|
|
142
|
-
/* ignore onRetry errors */
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
await sleep(delay, signal)
|
|
146
|
-
attempt = nextAttempt
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const finalErr = new Error(
|
|
151
|
-
`Retry failed after ${attempt} retries: ${String(lastErr?.message ?? lastErr)}`
|
|
152
|
-
)
|
|
153
|
-
;(finalErr as any).original = lastErr
|
|
154
|
-
throw finalErr
|
|
155
|
-
}
|
|
1
|
+
// src/retry.ts
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
|
|
4
|
+
import axios from 'axios'
|
|
5
|
+
|
|
6
|
+
export type RetryOptions = {
|
|
7
|
+
retries?: number
|
|
8
|
+
baseDelayMs?: number
|
|
9
|
+
maxDelayMs?: number
|
|
10
|
+
timeoutMs?: number
|
|
11
|
+
signal?: AbortSignal | null
|
|
12
|
+
isRetryable?: (err: any) => boolean
|
|
13
|
+
onRetry?: (err: any, attempt: number, nextDelayMs: number) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function sleep(ms: number, signal?: AbortSignal | null) {
|
|
17
|
+
return new Promise<void>((resolve, reject) => {
|
|
18
|
+
if (signal?.aborted) return reject(new Error('Aborted'))
|
|
19
|
+
const t = setTimeout(() => {
|
|
20
|
+
signal?.removeEventListener?.('abort', onAbort)
|
|
21
|
+
resolve()
|
|
22
|
+
}, ms)
|
|
23
|
+
|
|
24
|
+
function onAbort() {
|
|
25
|
+
clearTimeout(t)
|
|
26
|
+
signal?.removeEventListener?.('abort', onAbort)
|
|
27
|
+
reject(new Error('Aborted'))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
signal?.addEventListener?.('abort', onAbort)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isRetryableDefault(err: any): boolean {
|
|
35
|
+
if (!err) return false
|
|
36
|
+
|
|
37
|
+
// gRPC numeric code for UNAVAILABLE is 14
|
|
38
|
+
if (err?.code === 14 || err?.grpcCode === 14) return true
|
|
39
|
+
|
|
40
|
+
// Common Node network error codes
|
|
41
|
+
const syscode = String(err?.code ?? '').toLowerCase()
|
|
42
|
+
if (
|
|
43
|
+
syscode === 'econnreset' ||
|
|
44
|
+
syscode === 'etimedout' ||
|
|
45
|
+
syscode === 'econnrefused' ||
|
|
46
|
+
syscode === 'enotfound' ||
|
|
47
|
+
syscode === 'enetunreach' ||
|
|
48
|
+
syscode === 'eai_again' ||
|
|
49
|
+
syscode === 'ehostunreach' ||
|
|
50
|
+
syscode === 'epipe'
|
|
51
|
+
) {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// HTTP/Fetch style errors (axios / fetch)
|
|
56
|
+
const status = err?.response?.status ?? err?.status
|
|
57
|
+
if (typeof status === 'number') {
|
|
58
|
+
// retry on 429 (rate limit) and 5xx (server errors)
|
|
59
|
+
if (status === 429 || (status >= 500 && status < 600)) return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Message-based heuristics: include variants like "timed out" as well as "timeout"
|
|
63
|
+
const msg = String(err?.message ?? '').toLowerCase()
|
|
64
|
+
|
|
65
|
+
// Check for common retryable phrases:
|
|
66
|
+
// - timeout, timed out, time out
|
|
67
|
+
// - temporary
|
|
68
|
+
// - unavailable
|
|
69
|
+
if (/(timeout|timed out|time out|temporary|unavailable)/.test(msg)) return true
|
|
70
|
+
|
|
71
|
+
// Also accept explicit substrings for network-errno-like messages
|
|
72
|
+
if (msg.includes('econnreset') || msg.includes('etimedout')) return true
|
|
73
|
+
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isRetryableAxios(err: any): boolean {
|
|
78
|
+
if (!err) return false
|
|
79
|
+
|
|
80
|
+
// If axios knows about it:
|
|
81
|
+
if (axios.isAxiosError(err)) {
|
|
82
|
+
// No response -> network error / timeout -> retry
|
|
83
|
+
if (!err.response) return true
|
|
84
|
+
|
|
85
|
+
const status = err.response.status
|
|
86
|
+
// retry on 429 or 5xx
|
|
87
|
+
if (status === 429 || (status >= 500 && status < 600)) return true
|
|
88
|
+
|
|
89
|
+
// 4xx client errors -> do not retry
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Fallback to your existing heuristics (gRPC codes, node syscodes, etc.)
|
|
94
|
+
return isRetryableDefault(err)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function retry<T>(fn: () => Promise<T>, opts: RetryOptions = {}): Promise<T> {
|
|
98
|
+
const retries = opts.retries ?? 4
|
|
99
|
+
const base = opts.baseDelayMs ?? 200
|
|
100
|
+
const max = opts.maxDelayMs ?? 5000
|
|
101
|
+
const timeoutMs = opts.timeoutMs
|
|
102
|
+
const signal = opts.signal ?? null
|
|
103
|
+
const isRetryable = opts.isRetryable ?? isRetryableDefault
|
|
104
|
+
const onRetry = opts.onRetry
|
|
105
|
+
|
|
106
|
+
const start = Date.now()
|
|
107
|
+
|
|
108
|
+
let attempt = 0
|
|
109
|
+
let lastErr: any
|
|
110
|
+
|
|
111
|
+
// attempt = number of retries already performed; initial try is attempt==0
|
|
112
|
+
while (attempt <= retries) {
|
|
113
|
+
if (signal?.aborted) throw new Error('Aborted')
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
return await fn()
|
|
117
|
+
} catch (err) {
|
|
118
|
+
lastErr = err
|
|
119
|
+
|
|
120
|
+
if (!isRetryable(err)) {
|
|
121
|
+
throw err
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (attempt >= retries) break
|
|
125
|
+
|
|
126
|
+
// compute next retry (1-based)
|
|
127
|
+
const nextAttempt = attempt + 1
|
|
128
|
+
const exp = Math.min(max, base * Math.pow(2, nextAttempt - 1))
|
|
129
|
+
const delay = Math.floor(Math.random() * exp)
|
|
130
|
+
|
|
131
|
+
if (typeof timeoutMs === 'number') {
|
|
132
|
+
const elapsed = Date.now() - start
|
|
133
|
+
if (elapsed + delay > timeoutMs) {
|
|
134
|
+
break
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// pass 1-based attempt to onRetry to mean "this is the upcoming retry number"
|
|
140
|
+
onRetry?.(err, nextAttempt, delay)
|
|
141
|
+
} catch {
|
|
142
|
+
/* ignore onRetry errors */
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await sleep(delay, signal)
|
|
146
|
+
attempt = nextAttempt
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const finalErr = new Error(
|
|
151
|
+
`Retry failed after ${attempt} retries: ${String(lastErr?.message ?? lastErr)}`
|
|
152
|
+
)
|
|
153
|
+
;(finalErr as any).original = lastErr
|
|
154
|
+
throw finalErr
|
|
155
|
+
}
|
package/src/secrets.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { SecretManagerServiceClient } from '@google-cloud/secret-manager'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Returns the latest value of a Secret Manager secret.
|
|
5
|
-
*
|
|
6
|
-
* @param projectId – GCP project that owns the secret.
|
|
7
|
-
* @param secretName – Secret name (without version).
|
|
8
|
-
* @returns – UTF-8 string value.
|
|
9
|
-
*/
|
|
10
|
-
export async function getSecret(projectId: string, secretName: string): Promise<string> {
|
|
11
|
-
if (!projectId) throw new Error('projectId is required')
|
|
12
|
-
if (!secretName) throw new Error('secretName is required')
|
|
13
|
-
|
|
14
|
-
const secretClient = new SecretManagerServiceClient()
|
|
15
|
-
|
|
16
|
-
const [version] = await secretClient.accessSecretVersion({
|
|
17
|
-
name: `projects/${projectId}/secrets/${secretName}/versions/latest`
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
return (version.payload?.data as Buffer)?.toString('utf-8') ?? ''
|
|
21
|
-
}
|
|
1
|
+
import { SecretManagerServiceClient } from '@google-cloud/secret-manager'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the latest value of a Secret Manager secret.
|
|
5
|
+
*
|
|
6
|
+
* @param projectId – GCP project that owns the secret.
|
|
7
|
+
* @param secretName – Secret name (without version).
|
|
8
|
+
* @returns – UTF-8 string value.
|
|
9
|
+
*/
|
|
10
|
+
export async function getSecret(projectId: string, secretName: string): Promise<string> {
|
|
11
|
+
if (!projectId) throw new Error('projectId is required')
|
|
12
|
+
if (!secretName) throw new Error('secretName is required')
|
|
13
|
+
|
|
14
|
+
const secretClient = new SecretManagerServiceClient()
|
|
15
|
+
|
|
16
|
+
const [version] = await secretClient.accessSecretVersion({
|
|
17
|
+
name: `projects/${projectId}/secrets/${secretName}/versions/latest`
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return (version.payload?.data as Buffer)?.toString('utf-8') ?? ''
|
|
21
|
+
}
|