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/src/env.ts CHANGED
@@ -1,8 +1,10 @@
1
- export function getEnv(name: string, fallback?: any): any {
2
- const value = process.env[name]
3
- if (!value) {
4
- if (fallback !== undefined) return fallback
5
- throw new Error(`Missing env var ${name}`)
6
- }
7
- return value
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 './env'
8
- export * from './parser'
9
- export * from './regex'
10
- export * from './retry'
11
- export * from './secrets'
12
- export * from './task'
13
- export * from './validator'
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
+ }