gcf-common-lib 0.35.0 → 0.36.0

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/utils.js CHANGED
@@ -1,84 +1,105 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.timeoutAfter = timeoutAfter;
4
- exports.delay = delay;
5
- exports.ms = ms;
6
- exports.sec = sec;
7
- exports.indexToA1 = indexToA1;
8
- exports.A1ToIndex = A1ToIndex;
9
- exports.colNumToA1 = colNumToA1;
10
- exports.A1ToColNum = A1ToColNum;
11
- exports.safeJsonParse = safeJsonParse;
12
- /**
13
- *
14
- * @param seconds Google function v1 timeout limit (max: 9 min)
15
- */
16
- async function timeoutAfter(seconds = 540) {
17
- return new Promise((resolve, reject) => setTimeout(() => reject(new Error(`${seconds} seconds timeout exceeded`)), seconds * 1000));
18
- }
19
- async function delay(seconds) {
20
- return new Promise(resolve => setTimeout(() => resolve(), seconds * 1000));
21
- }
22
- function ms(o) {
23
- return sec(o) * 1000;
24
- }
25
- function sec(o) {
26
- const multiMap = {};
27
- multiMap.s = 1;
28
- multiMap.m = multiMap.s * 60;
29
- multiMap.h = multiMap.m * 60;
30
- multiMap.d = multiMap.h * 24;
31
- multiMap.w = multiMap.d * 7;
32
- return Object.entries(o)
33
- .map(([k, v]) => (multiMap[k] ?? 0) * (v ?? 0))
34
- .reduce((sum, v) => sum + v, 0);
35
- }
36
- function indexToA1(idx) {
37
- return colNumToA1(idx + 1);
38
- }
39
- function A1ToIndex(value) {
40
- return A1ToColNum(value) - 1;
41
- }
42
- function colNumToA1(columnNumber) {
43
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
44
- // To store result (Excel column name)
45
- const charIdxArr = [];
46
- while (columnNumber > 0) {
47
- // Find remainder
48
- const rem = columnNumber % chars.length;
49
- // If remainder is 0, then a
50
- // 'Z' must be there in output
51
- if (rem === 0) {
52
- charIdxArr.push(chars.length - 1);
53
- columnNumber = Math.floor(columnNumber / chars.length) - 1;
54
- }
55
- else {
56
- // If remainder is non-zero
57
- charIdxArr.push(rem - 1);
58
- columnNumber = Math.floor(columnNumber / chars.length);
59
- }
60
- }
61
- // Reverse the string and print result
62
- return charIdxArr
63
- .reverse()
64
- .map(n => chars[n])
65
- .join('');
66
- }
67
- function A1ToColNum(value) {
68
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
69
- let result = 0;
70
- // eslint-disable-next-line unicorn/no-for-loop
71
- for (let i = 0; i < value.length; i++) {
72
- result *= chars.length;
73
- result += chars.indexOf(value[i]) + 1;
74
- }
75
- return result;
76
- }
77
- function safeJsonParse(value, fallbackValue) {
78
- try {
79
- return JSON.parse(value);
80
- }
81
- catch {
82
- return fallbackValue;
83
- }
84
- }
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.timeoutAfter = timeoutAfter;
7
+ exports.delay = delay;
8
+ exports.ms = ms;
9
+ exports.sec = sec;
10
+ exports.indexToA1 = indexToA1;
11
+ exports.A1ToIndex = A1ToIndex;
12
+ exports.colNumToA1 = colNumToA1;
13
+ exports.A1ToColNum = A1ToColNum;
14
+ exports.safeJsonParse = safeJsonParse;
15
+ exports.isStrictlyNumeric = isStrictlyNumeric;
16
+ exports.strictNumber = strictNumber;
17
+ const isString_1 = __importDefault(require("lodash/isString"));
18
+ /**
19
+ *
20
+ * @param seconds Google function v1 timeout limit (max: 9 min)
21
+ */
22
+ async function timeoutAfter(seconds = 540) {
23
+ return new Promise((resolve, reject) => setTimeout(() => reject(new Error(`${seconds} seconds timeout exceeded`)), seconds * 1000));
24
+ }
25
+ async function delay(seconds) {
26
+ return new Promise(resolve => setTimeout(() => resolve(), seconds * 1000));
27
+ }
28
+ function ms(o) {
29
+ return sec(o) * 1000;
30
+ }
31
+ function sec(o) {
32
+ const multiMap = {};
33
+ multiMap.s = 1;
34
+ multiMap.m = multiMap.s * 60;
35
+ multiMap.h = multiMap.m * 60;
36
+ multiMap.d = multiMap.h * 24;
37
+ multiMap.w = multiMap.d * 7;
38
+ return Object.entries(o)
39
+ .map(([k, v]) => (multiMap[k] ?? 0) * (v ?? 0))
40
+ .reduce((sum, v) => sum + v, 0);
41
+ }
42
+ function indexToA1(idx) {
43
+ return colNumToA1(idx + 1);
44
+ }
45
+ function A1ToIndex(value) {
46
+ return A1ToColNum(value) - 1;
47
+ }
48
+ function colNumToA1(columnNumber) {
49
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
50
+ // To store result (Excel column name)
51
+ const charIdxArr = [];
52
+ while (columnNumber > 0) {
53
+ // Find remainder
54
+ const rem = columnNumber % chars.length;
55
+ // If remainder is 0, then a
56
+ // 'Z' must be there in output
57
+ if (rem === 0) {
58
+ charIdxArr.push(chars.length - 1);
59
+ columnNumber = Math.floor(columnNumber / chars.length) - 1;
60
+ }
61
+ else {
62
+ // If remainder is non-zero
63
+ charIdxArr.push(rem - 1);
64
+ columnNumber = Math.floor(columnNumber / chars.length);
65
+ }
66
+ }
67
+ // Reverse the string and print result
68
+ return charIdxArr
69
+ .reverse()
70
+ .map(n => chars[n])
71
+ .join('');
72
+ }
73
+ function A1ToColNum(value) {
74
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
75
+ let result = 0;
76
+ // eslint-disable-next-line unicorn/no-for-loop
77
+ for (let i = 0; i < value.length; i++) {
78
+ result *= chars.length;
79
+ result += chars.indexOf(value[i]) + 1;
80
+ }
81
+ return result;
82
+ }
83
+ function safeJsonParse(value, fallbackValue) {
84
+ try {
85
+ return JSON.parse(value);
86
+ }
87
+ catch {
88
+ return fallbackValue;
89
+ }
90
+ }
91
+ function isStrictlyNumeric(val) {
92
+ val = '' + val;
93
+ if ((0, isString_1.default)(val) && val.trim() === '') {
94
+ return false;
95
+ }
96
+ const num = Number(val);
97
+ return !Number.isNaN(num) && Number.isFinite(num);
98
+ }
99
+ function strictNumber(num) {
100
+ num = '' + num;
101
+ if (!isStrictlyNumeric(num)) {
102
+ return Number.NaN;
103
+ }
104
+ return Number(num);
105
+ }
package/src/utils.ts CHANGED
@@ -1,88 +1,110 @@
1
- import Dict = NodeJS.Dict;
2
-
3
- /**
4
- *
5
- * @param seconds Google function v1 timeout limit (max: 9 min)
6
- */
7
- export async function timeoutAfter(seconds: number = 540) {
8
- return new Promise<void>((resolve, reject) =>
9
- setTimeout(() => reject(new Error(`${seconds} seconds timeout exceeded`)), seconds * 1000),
10
- );
11
- }
12
-
13
- export async function delay(seconds: number) {
14
- return new Promise<void>(resolve => setTimeout(() => resolve(), seconds * 1000));
15
- }
16
-
17
- export function ms(o: { w?: number; d?: number; h?: number; m?: number; s?: number }) {
18
- return sec(o) * 1000;
19
- }
20
-
21
- export function sec(o: { w?: number; d?: number; h?: number; m?: number; s?: number }) {
22
- const multiMap: any = {};
23
- multiMap.s = 1;
24
- multiMap.m = multiMap.s * 60;
25
- multiMap.h = multiMap.m * 60;
26
- multiMap.d = multiMap.h * 24;
27
- multiMap.w = multiMap.d * 7;
28
-
29
- return Object.entries(o)
30
- .map(([k, v]) => (multiMap[k] ?? 0) * (v ?? 0))
31
- .reduce((sum, v) => sum + v, 0);
32
- }
33
-
34
- export function indexToA1(idx: number) {
35
- return colNumToA1(idx + 1);
36
- }
37
-
38
- export function A1ToIndex(value: string) {
39
- return A1ToColNum(value) - 1;
40
- }
41
-
42
- export function colNumToA1(columnNumber: number) {
43
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
44
-
45
- // To store result (Excel column name)
46
- const charIdxArr: number[] = [];
47
-
48
- while (columnNumber > 0) {
49
- // Find remainder
50
- const rem = columnNumber % chars.length;
51
-
52
- // If remainder is 0, then a
53
- // 'Z' must be there in output
54
- if (rem === 0) {
55
- charIdxArr.push(chars.length - 1);
56
- columnNumber = Math.floor(columnNumber / chars.length) - 1;
57
- } else {
58
- // If remainder is non-zero
59
- charIdxArr.push(rem - 1);
60
- columnNumber = Math.floor(columnNumber / chars.length);
61
- }
62
- }
63
-
64
- // Reverse the string and print result
65
- return charIdxArr
66
- .reverse()
67
- .map(n => chars[n])
68
- .join('');
69
- }
70
-
71
- export function A1ToColNum(value: string) {
72
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
73
- let result = 0;
74
- // eslint-disable-next-line unicorn/no-for-loop
75
- for (let i = 0; i < value.length; i++) {
76
- result *= chars.length;
77
- result += chars.indexOf(value[i]) + 1;
78
- }
79
- return result;
80
- }
81
-
82
- export function safeJsonParse<T = Dict<any>>(value: string, fallbackValue?: T) {
83
- try {
84
- return JSON.parse(value) as T;
85
- } catch {
86
- return fallbackValue;
87
- }
88
- }
1
+ import Dict = NodeJS.Dict;
2
+ import isString from 'lodash/isString';
3
+
4
+ /**
5
+ *
6
+ * @param seconds Google function v1 timeout limit (max: 9 min)
7
+ */
8
+ export async function timeoutAfter(seconds: number = 540) {
9
+ return new Promise<void>((resolve, reject) =>
10
+ setTimeout(() => reject(new Error(`${seconds} seconds timeout exceeded`)), seconds * 1000),
11
+ );
12
+ }
13
+
14
+ export async function delay(seconds: number) {
15
+ return new Promise<void>(resolve => setTimeout(() => resolve(), seconds * 1000));
16
+ }
17
+
18
+ export function ms(o: { w?: number; d?: number; h?: number; m?: number; s?: number }) {
19
+ return sec(o) * 1000;
20
+ }
21
+
22
+ export function sec(o: { w?: number; d?: number; h?: number; m?: number; s?: number }) {
23
+ const multiMap: any = {};
24
+ multiMap.s = 1;
25
+ multiMap.m = multiMap.s * 60;
26
+ multiMap.h = multiMap.m * 60;
27
+ multiMap.d = multiMap.h * 24;
28
+ multiMap.w = multiMap.d * 7;
29
+
30
+ return Object.entries(o)
31
+ .map(([k, v]) => (multiMap[k] ?? 0) * (v ?? 0))
32
+ .reduce((sum, v) => sum + v, 0);
33
+ }
34
+
35
+ export function indexToA1(idx: number) {
36
+ return colNumToA1(idx + 1);
37
+ }
38
+
39
+ export function A1ToIndex(value: string) {
40
+ return A1ToColNum(value) - 1;
41
+ }
42
+
43
+ export function colNumToA1(columnNumber: number) {
44
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
45
+
46
+ // To store result (Excel column name)
47
+ const charIdxArr: number[] = [];
48
+
49
+ while (columnNumber > 0) {
50
+ // Find remainder
51
+ const rem = columnNumber % chars.length;
52
+
53
+ // If remainder is 0, then a
54
+ // 'Z' must be there in output
55
+ if (rem === 0) {
56
+ charIdxArr.push(chars.length - 1);
57
+ columnNumber = Math.floor(columnNumber / chars.length) - 1;
58
+ } else {
59
+ // If remainder is non-zero
60
+ charIdxArr.push(rem - 1);
61
+ columnNumber = Math.floor(columnNumber / chars.length);
62
+ }
63
+ }
64
+
65
+ // Reverse the string and print result
66
+ return charIdxArr
67
+ .reverse()
68
+ .map(n => chars[n])
69
+ .join('');
70
+ }
71
+
72
+ export function A1ToColNum(value: string) {
73
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
74
+ let result = 0;
75
+ // eslint-disable-next-line unicorn/no-for-loop
76
+ for (let i = 0; i < value.length; i++) {
77
+ result *= chars.length;
78
+ result += chars.indexOf(value[i]) + 1;
79
+ }
80
+ return result;
81
+ }
82
+
83
+ export function safeJsonParse<T = Dict<any>>(value: string, fallbackValue?: T) {
84
+ try {
85
+ return JSON.parse(value) as T;
86
+ } catch {
87
+ return fallbackValue;
88
+ }
89
+ }
90
+
91
+ export function isStrictlyNumeric(val: any) {
92
+ val = '' + val;
93
+
94
+ if (isString(val) && val.trim() === '') {
95
+ return false;
96
+ }
97
+
98
+ const num = Number(val);
99
+ return !Number.isNaN(num) && Number.isFinite(num);
100
+ }
101
+
102
+ export function strictNumber(num: any) {
103
+ num = '' + num;
104
+
105
+ if (!isStrictlyNumeric(num)) {
106
+ return Number.NaN;
107
+ }
108
+
109
+ return Number(num);
110
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = require("node:test");
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const src_1 = require("../src");
9
+ (0, node_test_1.describe)('GcfCommon.process (no network I/O)', () => {
10
+ (0, node_test_1.it)('calls response with handler result on success and returns value', async () => {
11
+ const payload = {
12
+ event: {
13
+ '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage',
14
+ json: { ok: 1 },
15
+ attributes: {},
16
+ },
17
+ context: undefined,
18
+ };
19
+ const calls = [];
20
+ const original = src_1.GcfCommon.response;
21
+ src_1.GcfCommon.response = async (_p, json, attributes) => {
22
+ calls.push({ json, attributes });
23
+ };
24
+ try {
25
+ const result = await src_1.GcfCommon.process(payload, async () => ({ result: 42 }));
26
+ strict_1.default.deepEqual(result, { result: 42 });
27
+ strict_1.default.equal(calls.length, 1);
28
+ strict_1.default.deepEqual(calls[0]?.json, { result: 42 });
29
+ strict_1.default.equal(calls[0]?.attributes, undefined);
30
+ }
31
+ finally {
32
+ src_1.GcfCommon.response = original;
33
+ }
34
+ });
35
+ (0, node_test_1.it)('calls response with buildResponse(error) and rethrows on error', async () => {
36
+ const payload = {
37
+ event: {
38
+ '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage',
39
+ json: { ok: 1 },
40
+ attributes: {},
41
+ },
42
+ context: undefined,
43
+ };
44
+ const calls = [];
45
+ const original = src_1.GcfCommon.response;
46
+ src_1.GcfCommon.response = async (_p, json, attributes) => {
47
+ calls.push({ json, attributes });
48
+ };
49
+ try {
50
+ await strict_1.default.rejects(() => src_1.GcfCommon.process(payload, async () => { throw new Error('boom'); }), /boom/);
51
+ strict_1.default.equal(calls.length, 1);
52
+ const call = calls[0];
53
+ strict_1.default.ok(call?.json?.error);
54
+ strict_1.default.equal(call?.json?.error?.name, 'Error');
55
+ strict_1.default.match(call?.json?.error?.message ?? '', /^GCF \[/);
56
+ strict_1.default.match(call?.json?.error?.message ?? '', /boom/);
57
+ strict_1.default.deepEqual(call?.attributes, { error: '1' });
58
+ }
59
+ finally {
60
+ src_1.GcfCommon.response = original;
61
+ }
62
+ });
63
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { GcfCommon, TPayload, TResponse, TPSEvent } from '../src';
4
+
5
+ describe('GcfCommon.process (no network I/O)', () => {
6
+ it('calls response with handler result on success and returns value', async () => {
7
+ const payload: TPayload<TPSEvent> = {
8
+ event: {
9
+ '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage',
10
+ json: { ok: 1 },
11
+ attributes: {},
12
+ },
13
+ context: undefined,
14
+ };
15
+
16
+ const calls: Array<{ json?: TResponse; attributes?: Record<string, any> }> = [];
17
+ const original = GcfCommon.response;
18
+ GcfCommon.response = async (_p, json, attributes) => {
19
+ calls.push({ json, attributes });
20
+ };
21
+
22
+ try {
23
+ const result = await GcfCommon.process(payload, async () => ({ result: 42 }));
24
+ assert.deepEqual(result, { result: 42 });
25
+ assert.equal(calls.length, 1);
26
+ assert.deepEqual(calls[0]?.json, { result: 42 });
27
+ assert.equal(calls[0]?.attributes, undefined);
28
+ } finally {
29
+ GcfCommon.response = original;
30
+ }
31
+ });
32
+
33
+ it('calls response with buildResponse(error) and rethrows on error', async () => {
34
+ const payload: TPayload<TPSEvent> = {
35
+ event: {
36
+ '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage',
37
+ json: { ok: 1 },
38
+ attributes: {},
39
+ },
40
+ context: undefined,
41
+ };
42
+
43
+ const calls: Array<{ json?: TResponse; attributes?: Record<string, any> }> = [];
44
+ const original = GcfCommon.response;
45
+ GcfCommon.response = async (_p, json, attributes) => {
46
+ calls.push({ json, attributes });
47
+ };
48
+
49
+ try {
50
+ await assert.rejects(
51
+ () => GcfCommon.process(payload, async () => { throw new Error('boom'); }),
52
+ /boom/,
53
+ );
54
+ assert.equal(calls.length, 1);
55
+ const call = calls[0];
56
+ assert.ok(call?.json?.error);
57
+ assert.equal(call?.json?.error?.name, 'Error');
58
+ assert.match(call?.json?.error?.message ?? '', /^GCF \[/);
59
+ assert.match(call?.json?.error?.message ?? '', /boom/);
60
+ assert.deepEqual(call?.attributes, { error: '1' });
61
+ } finally {
62
+ GcfCommon.response = original;
63
+ }
64
+ });
65
+ });
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = require("node:test");
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const src_1 = require("../src");
9
+ (0, node_test_1.describe)('GcfCommon.getMetadataOrAttribute', () => {
10
+ (0, node_test_1.it)('extracts metadata from GCS finalize event', () => {
11
+ const event = {
12
+ bucket: 'b',
13
+ contentType: 'text/plain',
14
+ crc32c: 'x',
15
+ etag: 'e',
16
+ generation: '1',
17
+ id: 'id',
18
+ kind: 'storage#object',
19
+ md5Hash: 'm',
20
+ mediaLink: 'ml',
21
+ metadata: { action: '1', env: 'test' },
22
+ metageneration: '1',
23
+ name: 'file.txt',
24
+ selfLink: 'sl',
25
+ size: '10',
26
+ storageClass: 'sc',
27
+ timeCreated: new Date().toISOString(),
28
+ timeStorageClassUpdated: new Date().toISOString(),
29
+ updated: new Date().toISOString(),
30
+ };
31
+ const context = {
32
+ eventId: '1',
33
+ timestamp: `${Date.now()}`,
34
+ eventType: 'google.storage.object.finalize',
35
+ resource: {
36
+ name: 'file.txt',
37
+ service: 'storage.googleapis.com',
38
+ type: 'storage#object',
39
+ },
40
+ };
41
+ const payload = { event, context };
42
+ const res = src_1.GcfCommon.getMetadataOrAttribute(payload);
43
+ strict_1.default.deepEqual(res, { a: '1', env: 'test' });
44
+ });
45
+ (0, node_test_1.it)('extracts attributes from Pub/Sub event', () => {
46
+ const event = {
47
+ '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage',
48
+ json: { ok: 1 },
49
+ attributes: { action: 'y', request_id: 'r1' },
50
+ };
51
+ const context = {
52
+ eventId: '2',
53
+ timestamp: `${Date.now()}`,
54
+ eventType: 'google.pubsub.topic.publish',
55
+ resource: {
56
+ name: 'topic',
57
+ service: 'pubsub.googleapis.com',
58
+ type: 'type.googleapis.com/google.pubsub.v1.PubsubMessage',
59
+ },
60
+ };
61
+ const payload = { event, context };
62
+ const res = src_1.GcfCommon.getMetadataOrAttribute(payload);
63
+ strict_1.default.deepEqual(res, { x: 'y', request_id: 'r1' });
64
+ });
65
+ (0, node_test_1.it)('extracts attributes from HTTP-like request body', () => {
66
+ const payload = {
67
+ request: { body: { message: { attributes: { foo: 'bar', queue: '' }, data: 'ZGF0YQ==' } } },
68
+ };
69
+ const res = src_1.GcfCommon.getMetadataOrAttribute(payload);
70
+ strict_1.default.deepEqual(res, { foo: 'bar', queue: '' });
71
+ });
72
+ (0, node_test_1.it)('returns empty object if no context and no request', () => {
73
+ const payload = {};
74
+ const res = src_1.GcfCommon.getMetadataOrAttribute(payload);
75
+ strict_1.default.deepEqual(res, {});
76
+ });
77
+ });