opticedge-cloud-utils 1.1.22 → 1.1.24

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/dist/env.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function getEnv(name: string, fallback?: any): any;
1
+ export declare function requireEnv(name: string, defaultValue?: string): string;
package/dist/env.js CHANGED
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.getEnv = getEnv;
5
- function getEnv(name, fallback) {
3
+ exports.requireEnv = requireEnv;
4
+ function requireEnv(name, defaultValue) {
6
5
  const value = process.env[name];
7
- if (!value) {
8
- if (fallback !== undefined)
9
- return fallback;
10
- throw new Error(`Missing env var ${name}`);
6
+ if (value === undefined || value === '') {
7
+ if (defaultValue !== undefined) {
8
+ return defaultValue;
9
+ }
10
+ throw new Error(`${name} env var must be set`);
11
11
  }
12
12
  return value;
13
13
  }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './tw/wallet';
6
6
  export * from './auth';
7
7
  export * from './chunk';
8
8
  export * from './env';
9
+ export * from './logger';
9
10
  export * from './number';
10
11
  export * from './parser';
11
12
  export * from './pub';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ __exportStar(require("./tw/wallet"), exports);
22
22
  __exportStar(require("./auth"), exports);
23
23
  __exportStar(require("./chunk"), exports);
24
24
  __exportStar(require("./env"), exports);
25
+ __exportStar(require("./logger"), exports);
25
26
  __exportStar(require("./number"), exports);
26
27
  __exportStar(require("./parser"), exports);
27
28
  __exportStar(require("./pub"), exports);
@@ -0,0 +1,8 @@
1
+ export declare enum LogLevel {
2
+ DEBUG = "DEBUG",
3
+ INFO = "INFO",
4
+ WARNING = "WARNING",
5
+ ERROR = "ERROR",
6
+ CRITICAL = "CRITICAL"
7
+ }
8
+ export declare function log(level: LogLevel, message: string, metadata?: Record<string, any>): void;
package/dist/logger.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ // src/logger.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.LogLevel = void 0;
5
+ exports.log = log;
6
+ var LogLevel;
7
+ (function (LogLevel) {
8
+ LogLevel["DEBUG"] = "DEBUG";
9
+ LogLevel["INFO"] = "INFO";
10
+ LogLevel["WARNING"] = "WARNING";
11
+ LogLevel["ERROR"] = "ERROR";
12
+ LogLevel["CRITICAL"] = "CRITICAL";
13
+ })(LogLevel || (exports.LogLevel = LogLevel = {}));
14
+ function log(level, message, metadata = {}) {
15
+ const payload = {
16
+ severity: level,
17
+ message,
18
+ ...sanitize(metadata)
19
+ };
20
+ // IMPORTANT: must be single-line JSON for Cloud Logging
21
+ console.log(JSON.stringify(payload));
22
+ }
23
+ /**
24
+ * Makes metadata JSON-safe:
25
+ * - ObjectId works automatically (mongodb@7)
26
+ * - Error objects become structured
27
+ * - BigInt becomes string
28
+ */
29
+ function sanitize(obj) {
30
+ const safe = {};
31
+ for (const [key, value] of Object.entries(obj)) {
32
+ if (value instanceof Error) {
33
+ safe[key] = {
34
+ name: value.name,
35
+ message: value.message,
36
+ stack: value.stack
37
+ };
38
+ continue;
39
+ }
40
+ if (typeof value === 'bigint') {
41
+ safe[key] = value.toString();
42
+ continue;
43
+ }
44
+ try {
45
+ JSON.stringify(value);
46
+ safe[key] = value;
47
+ }
48
+ catch {
49
+ safe[key] = String(value);
50
+ }
51
+ }
52
+ return safe;
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opticedge-cloud-utils",
3
- "version": "1.1.22",
3
+ "version": "1.1.24",
4
4
  "description": "Common utilities for cloud functions",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/env.ts CHANGED
@@ -1,10 +1,12 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
3
- export function getEnv(name: string, fallback?: any): any {
1
+ export function requireEnv(name: string, defaultValue?: string): string {
4
2
  const value = process.env[name]
5
- if (!value) {
6
- if (fallback !== undefined) return fallback
7
- throw new Error(`Missing env var ${name}`)
3
+
4
+ if (value === undefined || value === '') {
5
+ if (defaultValue !== undefined) {
6
+ return defaultValue
7
+ }
8
+ throw new Error(`${name} env var must be set`)
8
9
  }
10
+
9
11
  return value
10
- }
12
+ }
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from './tw/wallet'
6
6
  export * from './auth'
7
7
  export * from './chunk'
8
8
  export * from './env'
9
+ export * from './logger'
9
10
  export * from './number'
10
11
  export * from './parser'
11
12
  export * from './pub'
package/src/logger.ts ADDED
@@ -0,0 +1,55 @@
1
+ // src/logger.ts
2
+
3
+ export enum LogLevel {
4
+ DEBUG = 'DEBUG',
5
+ INFO = 'INFO',
6
+ WARNING = 'WARNING',
7
+ ERROR = 'ERROR',
8
+ CRITICAL = 'CRITICAL'
9
+ }
10
+
11
+ export function log(level: LogLevel, message: string, metadata: Record<string, any> = {}): void {
12
+ const payload = {
13
+ severity: level,
14
+ message,
15
+ ...sanitize(metadata)
16
+ }
17
+
18
+ // IMPORTANT: must be single-line JSON for Cloud Logging
19
+ console.log(JSON.stringify(payload))
20
+ }
21
+
22
+ /**
23
+ * Makes metadata JSON-safe:
24
+ * - ObjectId works automatically (mongodb@7)
25
+ * - Error objects become structured
26
+ * - BigInt becomes string
27
+ */
28
+ function sanitize(obj: Record<string, any>): Record<string, any> {
29
+ const safe: Record<string, any> = {}
30
+
31
+ for (const [key, value] of Object.entries(obj)) {
32
+ if (value instanceof Error) {
33
+ safe[key] = {
34
+ name: value.name,
35
+ message: value.message,
36
+ stack: value.stack
37
+ }
38
+ continue
39
+ }
40
+
41
+ if (typeof value === 'bigint') {
42
+ safe[key] = value.toString()
43
+ continue
44
+ }
45
+
46
+ try {
47
+ JSON.stringify(value)
48
+ safe[key] = value
49
+ } catch {
50
+ safe[key] = String(value)
51
+ }
52
+ }
53
+
54
+ return safe
55
+ }
package/tests/env.test.ts CHANGED
@@ -1,18 +1,65 @@
1
- import { getEnv } from '../src/env'
1
+ import { requireEnv } from '../src/env'
2
2
 
3
- describe('getEnv', () => {
4
- it('returns the value of an existing env var', () => {
5
- process.env.TEST_KEY = 'test-value'
6
- expect(getEnv('TEST_KEY')).toBe('test-value')
3
+ describe('requireEnv', () => {
4
+ const ORIGINAL_ENV = process.env
5
+
6
+ beforeEach(() => {
7
+ jest.resetModules()
8
+ process.env = { ...ORIGINAL_ENV }
9
+ })
10
+
11
+ afterAll(() => {
12
+ process.env = ORIGINAL_ENV
13
+ })
14
+
15
+ // ============================================================
16
+ // Success cases
17
+ // ============================================================
18
+
19
+ it('returns env value when set', () => {
20
+ process.env.TEST_VAR = 'hello'
21
+ expect(requireEnv('TEST_VAR')).toBe('hello')
22
+ })
23
+
24
+ it('returns "0" correctly (should not treat as missing)', () => {
25
+ process.env.TEST_VAR = '0'
26
+ expect(requireEnv('TEST_VAR')).toBe('0')
7
27
  })
8
28
 
9
- it('returns fallback if env var is not set', () => {
10
- delete process.env.OPTIONAL_KEY
11
- expect(getEnv('OPTIONAL_KEY', 'default')).toBe('default')
29
+ it('returns "false" correctly (should not treat as missing)', () => {
30
+ process.env.TEST_VAR = 'false'
31
+ expect(requireEnv('TEST_VAR')).toBe('false')
32
+ })
33
+
34
+ // ============================================================
35
+ // Default handling
36
+ // ============================================================
37
+
38
+ it('returns default when env is undefined', () => {
39
+ delete process.env.TEST_VAR
40
+ expect(requireEnv('TEST_VAR', 'fallback')).toBe('fallback')
41
+ })
42
+
43
+ it('returns default when env is empty string', () => {
44
+ process.env.TEST_VAR = ''
45
+ expect(requireEnv('TEST_VAR', 'fallback')).toBe('fallback')
46
+ })
47
+
48
+ // ============================================================
49
+ // Error cases
50
+ // ============================================================
51
+
52
+ it('throws when env is undefined and no default provided', () => {
53
+ delete process.env.TEST_VAR
54
+ expect(() => requireEnv('TEST_VAR')).toThrow(
55
+ 'TEST_VAR env var must be set'
56
+ )
12
57
  })
13
58
 
14
- it('throws error if env var is not set and no fallback provided', () => {
15
- delete process.env.MISSING_KEY
16
- expect(() => getEnv('MISSING_KEY')).toThrow('Missing env var MISSING_KEY')
59
+ it('throws when env is empty string and no default provided', () => {
60
+ process.env.TEST_VAR = ''
61
+ expect(() => requireEnv('TEST_VAR')).toThrow(
62
+ 'TEST_VAR env var must be set'
63
+ )
17
64
  })
18
- })
65
+ })
@@ -0,0 +1,116 @@
1
+ import { log, LogLevel } from '../src/logger'
2
+ import { ObjectId } from 'mongodb'
3
+
4
+ describe('logger.ts (100% coverage)', () => {
5
+ let consoleSpy: jest.SpyInstance
6
+
7
+ beforeEach(() => {
8
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
9
+ })
10
+
11
+ afterEach(() => {
12
+ consoleSpy.mockRestore()
13
+ })
14
+
15
+ // ------------------------------------------------------------
16
+ // Basic log levels
17
+ // ------------------------------------------------------------
18
+
19
+ it('logs INFO correctly', () => {
20
+ log(LogLevel.INFO, 'Test message', { foo: 'bar' })
21
+
22
+ expect(consoleSpy).toHaveBeenCalledTimes(1)
23
+
24
+ const output = JSON.parse(consoleSpy.mock.calls[0][0])
25
+ expect(output).toEqual({
26
+ severity: 'INFO',
27
+ message: 'Test message',
28
+ foo: 'bar'
29
+ })
30
+ })
31
+
32
+ it('logs all LogLevel values', () => {
33
+ for (const level of Object.values(LogLevel)) {
34
+ log(level, 'Level test')
35
+ const output = JSON.parse(consoleSpy.mock.calls.pop()?.[0])
36
+ expect(output.severity).toBe(level)
37
+ }
38
+ })
39
+
40
+ // ------------------------------------------------------------
41
+ // ObjectId handling
42
+ // ------------------------------------------------------------
43
+
44
+ it('serializes ObjectId correctly', () => {
45
+ const id = new ObjectId()
46
+
47
+ log(LogLevel.INFO, 'ObjectId test', { id })
48
+
49
+ const output = JSON.parse(consoleSpy.mock.calls[0][0])
50
+
51
+ expect(output.id).toBe(id.toHexString())
52
+ })
53
+
54
+ // ------------------------------------------------------------
55
+ // Error handling
56
+ // ------------------------------------------------------------
57
+
58
+ it('serializes Error objects correctly', () => {
59
+ const error = new Error('Boom')
60
+
61
+ log(LogLevel.ERROR, 'Error test', { error })
62
+
63
+ const output = JSON.parse(consoleSpy.mock.calls[0][0])
64
+
65
+ expect(output.error).toMatchObject({
66
+ name: 'Error',
67
+ message: 'Boom'
68
+ })
69
+
70
+ expect(output.error.stack).toBeDefined()
71
+ })
72
+
73
+ // ------------------------------------------------------------
74
+ // BigInt handling
75
+ // ------------------------------------------------------------
76
+
77
+ it('converts BigInt to string', () => {
78
+ const big = BigInt(123456789)
79
+
80
+ log(LogLevel.INFO, 'BigInt test', { big })
81
+
82
+ const output = JSON.parse(consoleSpy.mock.calls[0][0])
83
+
84
+ expect(output.big).toBe('123456789')
85
+ })
86
+
87
+ // ------------------------------------------------------------
88
+ // Fallback serialization branch
89
+ // ------------------------------------------------------------
90
+
91
+ it('falls back to string for unserializable values', () => {
92
+ const circular: any = {}
93
+ circular.self = circular
94
+
95
+ log(LogLevel.INFO, 'Circular test', { circular })
96
+
97
+ const output = JSON.parse(consoleSpy.mock.calls[0][0])
98
+
99
+ expect(output.circular).toBe('[object Object]')
100
+ })
101
+
102
+ // ------------------------------------------------------------
103
+ // No metadata case
104
+ // ------------------------------------------------------------
105
+
106
+ it('works without metadata', () => {
107
+ log(LogLevel.WARNING, 'No metadata')
108
+
109
+ const output = JSON.parse(consoleSpy.mock.calls[0][0])
110
+
111
+ expect(output).toEqual({
112
+ severity: 'WARNING',
113
+ message: 'No metadata'
114
+ })
115
+ })
116
+ })