gcf-common-lib 0.22.6 → 0.23.7
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/package.json +3 -3
- package/src/index.js +79 -98
- package/src/mongo-lock.js +38 -54
- package/src/mongo-lock.ts +1 -1
- package/src/utils.js +5 -16
- package/tsconfig.json +1 -9
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gcf-common-lib",
|
|
3
3
|
"description": "",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.23.7",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"branches": [
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"rxjs": "^7.8.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@tsconfig/
|
|
30
|
+
"@tsconfig/node18": "^1.0.1",
|
|
31
31
|
"@types/lodash": "^4.14.191",
|
|
32
|
-
"@types/node": "^14.
|
|
32
|
+
"@types/node": "^18.14.1"
|
|
33
33
|
},
|
|
34
34
|
"author": "alert83@gmail.com",
|
|
35
35
|
"license": "MIT"
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
@@ -31,106 +22,96 @@ class GcfCommon {
|
|
|
31
22
|
* @param handler
|
|
32
23
|
* @param timeout Seconds
|
|
33
24
|
*/
|
|
34
|
-
static process(event, context, handler, timeout = 535) {
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
yield this.publish(event, context, response, { error: '1' }).catch(noop_1.default);
|
|
55
|
-
throw err;
|
|
56
|
-
}));
|
|
25
|
+
static async process(event, context, handler, timeout = 535) {
|
|
26
|
+
return Promise.race([
|
|
27
|
+
(0, utils_1.timeoutAfter)(timeout),
|
|
28
|
+
handler(event, context),
|
|
29
|
+
])
|
|
30
|
+
.then(async (res) => {
|
|
31
|
+
// console.log('res:', res);
|
|
32
|
+
await this.publish(event, context, res);
|
|
33
|
+
})
|
|
34
|
+
.catch(async (err) => {
|
|
35
|
+
const fname = process?.env?.K_SERVICE ?? 'UNKNOWN';
|
|
36
|
+
const response = {
|
|
37
|
+
error: {
|
|
38
|
+
name: err.name,
|
|
39
|
+
message: `GCF [${fname}]: ${err.message}`,
|
|
40
|
+
stack: err.stack,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
await this.publish(event, context, response, { error: '1' }).catch(noop_1.default);
|
|
44
|
+
throw err;
|
|
57
45
|
});
|
|
58
46
|
}
|
|
59
|
-
static publish(event, context, json, attributes) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.map(([k, v]) => [k, '' + v]))
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
static async publish(event, context, json, attributes) {
|
|
48
|
+
const { topic, requestId, env } = await this.getTopic(event, context);
|
|
49
|
+
console.log('publish:', topic, env, json, attributes);
|
|
50
|
+
if (!(0, isEmpty_1.default)(topic)) {
|
|
51
|
+
return exports.pubSub.topic(topic).publishMessage({
|
|
52
|
+
json: json ?? {},
|
|
53
|
+
attributes: {
|
|
54
|
+
...(0, fromPairs_1.default)(Object.entries(attributes ?? {})
|
|
55
|
+
.map(([k, v]) => [k, '' + v])),
|
|
56
|
+
env: env ?? '',
|
|
57
|
+
requestId: requestId ?? '',
|
|
58
|
+
type: 'response',
|
|
59
|
+
response: '1',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
71
63
|
}
|
|
72
|
-
static getTopic(event, context) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const [meta] = yield exports.storage.bucket(gsEvent.bucket).file(gsEvent.name).getMetadata();
|
|
85
|
-
({ topic, requestId, env } = (_c = meta === null || meta === void 0 ? void 0 : meta.metadata) !== null && _c !== void 0 ? _c : {});
|
|
86
|
-
}
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
case 'google.pubsub.topic.publish': {
|
|
90
|
-
const psEvent = event;
|
|
91
|
-
({ topic, requestId, env } = (_d = psEvent === null || psEvent === void 0 ? void 0 : psEvent.attributes) !== null && _d !== void 0 ? _d : {});
|
|
92
|
-
break;
|
|
64
|
+
static async getTopic(event, context) {
|
|
65
|
+
/** t_{GUID}__{YYYY-MM-DD} */
|
|
66
|
+
let topic;
|
|
67
|
+
let requestId;
|
|
68
|
+
let env;
|
|
69
|
+
switch (context?.eventType) {
|
|
70
|
+
case 'google.storage.object.finalize': {
|
|
71
|
+
const gsEvent = event;
|
|
72
|
+
({ topic, requestId, env } = gsEvent?.metadata ?? {});
|
|
73
|
+
if (!topic && context?.resource?.type === 'storage#object') {
|
|
74
|
+
const [meta] = await exports.storage.bucket(gsEvent.bucket).file(gsEvent.name).getMetadata();
|
|
75
|
+
({ topic, requestId, env } = meta?.metadata ?? {});
|
|
93
76
|
}
|
|
77
|
+
break;
|
|
94
78
|
}
|
|
95
|
-
|
|
96
|
-
|
|
79
|
+
case 'google.pubsub.topic.publish': {
|
|
80
|
+
const psEvent = event;
|
|
81
|
+
({ topic, requestId, env } = psEvent?.attributes ?? {});
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { topic, requestId, env };
|
|
97
86
|
}
|
|
98
|
-
static getOptions(event, context) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if ((_a = gsEvent === null || gsEvent === void 0 ? void 0 : gsEvent.metadata) === null || _a === void 0 ? void 0 : _a.options) {
|
|
105
|
-
return JSON.parse((_c = (_b = gsEvent === null || gsEvent === void 0 ? void 0 : gsEvent.metadata) === null || _b === void 0 ? void 0 : _b.options) !== null && _c !== void 0 ? _c : '{}');
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
const file = exports.storage.bucket(gsEvent.bucket).file(gsEvent.name);
|
|
109
|
-
const [meta] = yield file.getMetadata();
|
|
110
|
-
return JSON.parse((_e = (_d = meta === null || meta === void 0 ? void 0 : meta.metadata) === null || _d === void 0 ? void 0 : _d.options) !== null && _e !== void 0 ? _e : '{}');
|
|
111
|
-
}
|
|
87
|
+
static async getOptions(event, context) {
|
|
88
|
+
switch (context?.eventType) {
|
|
89
|
+
case 'google.storage.object.finalize': {
|
|
90
|
+
const gsEvent = event;
|
|
91
|
+
if (gsEvent?.metadata?.options) {
|
|
92
|
+
return JSON.parse(gsEvent?.metadata?.options ?? '{}');
|
|
112
93
|
}
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
94
|
+
else {
|
|
95
|
+
const file = exports.storage.bucket(gsEvent.bucket).file(gsEvent.name);
|
|
96
|
+
const [meta] = await file.getMetadata();
|
|
97
|
+
return JSON.parse(meta?.metadata?.options ?? '{}');
|
|
116
98
|
}
|
|
117
99
|
}
|
|
118
|
-
|
|
100
|
+
case 'google.pubsub.topic.publish': {
|
|
101
|
+
const psEvent = event;
|
|
102
|
+
return JSON.parse(psEvent?.attributes?.options ?? '{}');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
119
105
|
}
|
|
120
|
-
static getSecret(name, version) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const [response] = yield exports.secretClient.accessSecretVersion({ name: secretVersion });
|
|
127
|
-
return (_b = (_a = response === null || response === void 0 ? void 0 : response.payload) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.toString();
|
|
128
|
-
});
|
|
106
|
+
static async getSecret(name, version) {
|
|
107
|
+
const projectId = await exports.secretClient.getProjectId();
|
|
108
|
+
const secretName = `projects/${projectId}/secrets/${name}`;
|
|
109
|
+
const secretVersion = `${secretName}/versions/${version ?? 'latest'}`;
|
|
110
|
+
const [response] = await exports.secretClient.accessSecretVersion({ name: secretVersion });
|
|
111
|
+
return response?.payload?.data?.toString();
|
|
129
112
|
}
|
|
130
|
-
static getSecrets(names, versions) {
|
|
131
|
-
return
|
|
132
|
-
return Promise.all(names.map((name, idx) => __awaiter(this, void 0, void 0, function* () { return this.getSecret(name, versions === null || versions === void 0 ? void 0 : versions[idx]).catch(); })));
|
|
133
|
-
});
|
|
113
|
+
static async getSecrets(names, versions) {
|
|
114
|
+
return Promise.all(names.map(async (name, idx) => this.getSecret(name, versions?.[idx]).catch()));
|
|
134
115
|
}
|
|
135
116
|
}
|
|
136
117
|
exports.GcfCommon = GcfCommon;
|
package/src/mongo-lock.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
@@ -17,19 +8,18 @@ const moment_1 = __importDefault(require("moment"));
|
|
|
17
8
|
const noop_1 = __importDefault(require("lodash/noop"));
|
|
18
9
|
const rxjs_1 = require("rxjs");
|
|
19
10
|
class MongoLock {
|
|
11
|
+
client;
|
|
20
12
|
constructor(client) {
|
|
21
13
|
this.client = client;
|
|
22
14
|
}
|
|
23
|
-
getCollection() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
yield locksColl.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 });
|
|
30
|
-
}));
|
|
31
|
-
return locksColl;
|
|
15
|
+
async getCollection() {
|
|
16
|
+
const locksColl = this.client.db().collection('locks');
|
|
17
|
+
await locksColl.indexExists('expiresAt')
|
|
18
|
+
.then(async (exists) => {
|
|
19
|
+
if (!exists)
|
|
20
|
+
await locksColl.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 });
|
|
32
21
|
});
|
|
22
|
+
return locksColl;
|
|
33
23
|
}
|
|
34
24
|
/**
|
|
35
25
|
* Create the MongoDB collection and an expiring index on a field named "expiresAt".
|
|
@@ -37,45 +27,39 @@ class MongoLock {
|
|
|
37
27
|
* db.createCollection('locks');
|
|
38
28
|
* db.locks.createIndex( { "expiresAt": 1 }, { expireAfterSeconds: 0 } )
|
|
39
29
|
**/
|
|
40
|
-
acquireLock(name, ttlSeconds) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
throw ex;
|
|
57
|
-
}
|
|
58
|
-
// As we got a duplicate key exception, no lock could be acquired
|
|
59
|
-
return false;
|
|
30
|
+
async acquireLock(name, ttlSeconds) {
|
|
31
|
+
const collection = await this.getCollection();
|
|
32
|
+
// Entry gets removed automatically due to an expiry index in Mongo
|
|
33
|
+
const expiresAt = (0, moment_1.default)().add(ttlSeconds, 'seconds').toDate();
|
|
34
|
+
try {
|
|
35
|
+
await collection.insertOne({
|
|
36
|
+
_id: name,
|
|
37
|
+
expiresAt,
|
|
38
|
+
});
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (ex) {
|
|
42
|
+
// 11000 means duplicate key error, which is expected on an active lock
|
|
43
|
+
if (ex?.code !== 11000) {
|
|
44
|
+
// Unexpected error, what happened here :o
|
|
45
|
+
throw ex;
|
|
60
46
|
}
|
|
61
|
-
|
|
47
|
+
// As we got a duplicate key exception, no lock could be acquired
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
62
50
|
}
|
|
63
|
-
releaseLock(name) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return collection.deleteOne({ _id: name });
|
|
67
|
-
});
|
|
51
|
+
async releaseLock(name) {
|
|
52
|
+
const collection = await this.getCollection();
|
|
53
|
+
return collection.deleteOne({ _id: name });
|
|
68
54
|
}
|
|
69
|
-
acquireAndExecute(name, ttlSeconds, wait, fn) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}), wait ? (0, rxjs_1.retry)({ count, delay }) : rxjs_1.identity, (0, rxjs_1.switchMap)(() => fn().finally(() => this.releaseLock(name).catch(noop_1.default)))));
|
|
78
|
-
});
|
|
55
|
+
async acquireAndExecute(name, ttlSeconds, wait, fn) {
|
|
56
|
+
const delay = 200;
|
|
57
|
+
const count = Math.round((1000 / delay) * ttlSeconds);
|
|
58
|
+
return (0, rxjs_1.lastValueFrom)((0, rxjs_1.defer)(() => (0, rxjs_1.from)(this.acquireLock(name, ttlSeconds))).pipe((0, rxjs_1.map)(locked => {
|
|
59
|
+
console.log(locked);
|
|
60
|
+
if (!locked)
|
|
61
|
+
throw locked;
|
|
62
|
+
}), wait ? (0, rxjs_1.retry)({ count, delay }) : rxjs_1.identity, (0, rxjs_1.switchMap)(() => fn().finally(() => this.releaseLock(name).catch(noop_1.default)))));
|
|
79
63
|
}
|
|
80
64
|
}
|
|
81
65
|
exports.MongoLock = MongoLock;
|
package/src/mongo-lock.ts
CHANGED
|
@@ -8,7 +8,7 @@ export class MongoLock {
|
|
|
8
8
|
constructor(private client: MongoClient) {
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
async getCollection() {
|
|
12
12
|
const locksColl = this.client.db().collection('locks');
|
|
13
13
|
await locksColl.indexExists('expiresAt')
|
|
14
14
|
.then(async exists => {
|
package/src/utils.js
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.A1ToColNum = exports.colNumToA1 = exports.A1ToIndex = exports.indexToA1 = exports.timeoutAfter = void 0;
|
|
13
4
|
/**
|
|
14
5
|
*
|
|
15
6
|
* @param seconds Google function timeout limit (max: 9 min)
|
|
16
7
|
*/
|
|
17
|
-
function timeoutAfter(seconds = 540) {
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}, seconds * 1000);
|
|
23
|
-
});
|
|
8
|
+
async function timeoutAfter(seconds = 540) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
reject(new Error(`${seconds} seconds timeout exceeded`));
|
|
12
|
+
}, seconds * 1000);
|
|
24
13
|
});
|
|
25
14
|
}
|
|
26
15
|
exports.timeoutAfter = timeoutAfter;
|
package/tsconfig.json
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json.schemastore.org/tsconfig",
|
|
3
|
-
"extends": "@tsconfig/
|
|
3
|
+
"extends": "@tsconfig/node18/tsconfig.json",
|
|
4
4
|
"compilerOptions": {
|
|
5
|
-
"target": "ES6",
|
|
6
|
-
"lib": [
|
|
7
|
-
"ES2018",
|
|
8
|
-
"ES2019",
|
|
9
|
-
"ES2020",
|
|
10
|
-
"ES2021",
|
|
11
|
-
"ESNext"
|
|
12
|
-
]
|
|
13
5
|
},
|
|
14
6
|
"exclude": [
|
|
15
7
|
"node_modules/**",
|