@eyevinn/player-analytics-shared 0.6.0 → 0.7.1
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/adapters/db/ClickHouseDBAdapter.ts +143 -0
- package/adapters/queue/SqsQueueAdapter.ts +36 -1
- package/build/adapters/db/ClickHouseDBAdapter.d.ts +13 -0
- package/build/adapters/db/ClickHouseDBAdapter.js +142 -0
- package/build/adapters/db/ClickHouseDBAdapter.js.map +1 -0
- package/build/adapters/queue/SqsQueueAdapter.d.ts +4 -0
- package/build/adapters/queue/SqsQueueAdapter.js +54 -1
- package/build/adapters/queue/SqsQueueAdapter.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/index.ts +1 -0
- package/package.json +2 -1
- package/spec/adapters/SqsQueueAdapter.spec.ts +8 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createClient } from '@clickhouse/client';
|
|
2
|
+
import { AbstractDBAdapter, ErrorType, IGetItemInput, IGetItems, IHandleErrorOutput, IPutItemInput } from '../../types/interfaces';
|
|
3
|
+
import winston from 'winston';
|
|
4
|
+
|
|
5
|
+
interface EventItem {
|
|
6
|
+
event: string;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
playhead: number;
|
|
10
|
+
duration: number;
|
|
11
|
+
payload: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ClickHouseDBAdapter implements AbstractDBAdapter {
|
|
15
|
+
logger: winston.Logger;
|
|
16
|
+
dbClient: any;
|
|
17
|
+
|
|
18
|
+
constructor(logger: winston.Logger) {
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
this.dbClient = createClient({
|
|
21
|
+
url: process.env.CLICKHOUSE_URL
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async tableExists(name: string): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
const query = `SELECT 1 FROM system.tables WHERE database = currentDatabase() AND name = '${name}'`;
|
|
28
|
+
const resultSet = await this.dbClient.query({
|
|
29
|
+
query,
|
|
30
|
+
format: 'JSONEachRow'
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const rows = await resultSet.json();
|
|
34
|
+
if (rows.length > 0) {
|
|
35
|
+
return true;
|
|
36
|
+
} else {
|
|
37
|
+
// Create table if it does not exists
|
|
38
|
+
const createTableQuery = `
|
|
39
|
+
CREATE TABLE IF NOT EXISTS ${name} (
|
|
40
|
+
event String,
|
|
41
|
+
sessionId String,
|
|
42
|
+
timestamp DateTime64(3),
|
|
43
|
+
playhead Float64,
|
|
44
|
+
duration Float64,
|
|
45
|
+
live Boolean,
|
|
46
|
+
contentId String,
|
|
47
|
+
userId String,
|
|
48
|
+
deviceId String,
|
|
49
|
+
deviceModel String,
|
|
50
|
+
deviceType String,
|
|
51
|
+
payload String, /* Stored as JSON string */
|
|
52
|
+
|
|
53
|
+
/* Add derived columns for better query performance */
|
|
54
|
+
event_date Date DEFAULT toDate(timestamp),
|
|
55
|
+
event_hour DateTime DEFAULT toStartOfHour(timestamp)
|
|
56
|
+
)
|
|
57
|
+
ENGINE = MergeTree()
|
|
58
|
+
PARTITION BY toYYYYMM(timestamp)
|
|
59
|
+
ORDER BY (sessionId, timestamp)
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
await this.dbClient.query({
|
|
63
|
+
query: createTableQuery
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
this.logger.info(`Table '${name}' created successfully.`);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
this.logger.error(`Error checking if table '${name}' exists:`);
|
|
71
|
+
this.logger.error(err);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async putItem(params: IPutItemInput): Promise<boolean> {
|
|
77
|
+
const tableName = params.tableName;
|
|
78
|
+
const item = params.data as EventItem;
|
|
79
|
+
|
|
80
|
+
// Prepare the item for insertion
|
|
81
|
+
this.logger.debug(item);
|
|
82
|
+
|
|
83
|
+
// Convert payload to JSON string if it's an object
|
|
84
|
+
const payload = typeof item.payload === 'object'
|
|
85
|
+
? JSON.stringify(item.payload)
|
|
86
|
+
: item.payload || '';
|
|
87
|
+
|
|
88
|
+
// Prepare the data for insertion
|
|
89
|
+
let parsedPayload = {};
|
|
90
|
+
if (payload) {
|
|
91
|
+
try {
|
|
92
|
+
parsedPayload = JSON.parse(payload);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.logger.warn('Payload not json, skipping parsing');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const data = [{
|
|
98
|
+
event: item.event,
|
|
99
|
+
sessionId: item.sessionId,
|
|
100
|
+
timestamp: item.timestamp,
|
|
101
|
+
playhead: item.playhead || -1,
|
|
102
|
+
duration: item.duration || -1,
|
|
103
|
+
live: parsedPayload['live'] || false,
|
|
104
|
+
contentId: parsedPayload['contentId'] || '',
|
|
105
|
+
userId: parsedPayload['userId'] || '',
|
|
106
|
+
deviceId: parsedPayload['deviceId'] || '',
|
|
107
|
+
deviceModel: parsedPayload['deviceModel'] || '',
|
|
108
|
+
deviceType: parsedPayload['deviceType'] || '',
|
|
109
|
+
payload
|
|
110
|
+
}];
|
|
111
|
+
|
|
112
|
+
// Insert the data
|
|
113
|
+
await this.dbClient.insert({
|
|
114
|
+
table: tableName,
|
|
115
|
+
values: data,
|
|
116
|
+
format: 'JSONEachRow'
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.logger.debug(`Successfully inserted item into ${tableName}`);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async getItem(params: IGetItemInput): Promise<any> {
|
|
124
|
+
throw new Error('Method not implemented.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async deleteItem(params: IGetItemInput): Promise<boolean> {
|
|
128
|
+
throw new Error('Method not implemented.');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async getItemsBySession(params: IGetItems): Promise<any[]> {
|
|
132
|
+
throw new Error('Method not implemented.');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
handleError(errorObject: any): IHandleErrorOutput {
|
|
136
|
+
this.logger.error(errorObject);
|
|
137
|
+
const errorOutput: IHandleErrorOutput = {
|
|
138
|
+
errorType: ErrorType.ABORT,
|
|
139
|
+
error: errorObject,
|
|
140
|
+
};
|
|
141
|
+
return errorOutput;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
DeleteMessageCommandInput,
|
|
8
8
|
Message,
|
|
9
9
|
DeleteMessageCommand,
|
|
10
|
+
paginateListQueues,
|
|
11
|
+
CreateQueueCommand,
|
|
10
12
|
} from '@aws-sdk/client-sqs';
|
|
11
13
|
import { AbstractQueueAdapter } from '../../types/interfaces';
|
|
12
14
|
import winston from 'winston';
|
|
@@ -14,6 +16,8 @@ import winston from 'winston';
|
|
|
14
16
|
export class SqsQueueAdapter implements AbstractQueueAdapter {
|
|
15
17
|
logger: winston.Logger;
|
|
16
18
|
client: SQSClient;
|
|
19
|
+
queueUrl: string;
|
|
20
|
+
queueExists: boolean = false;
|
|
17
21
|
|
|
18
22
|
constructor(logger: winston.Logger) {
|
|
19
23
|
this.logger = logger;
|
|
@@ -23,14 +27,45 @@ export class SqsQueueAdapter implements AbstractQueueAdapter {
|
|
|
23
27
|
} else {
|
|
24
28
|
region = process.env.AWS_REGION;
|
|
25
29
|
}
|
|
30
|
+
this.queueUrl = process.env.SQS_QUEUE_URL!;
|
|
26
31
|
this.logger.info(`SQS Region: ${region}`);
|
|
27
32
|
this.client = new SQSClient({ region: region, endpoint: process.env.SQS_ENDPOINT });
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
private async checkQueueExists(): Promise<boolean> {
|
|
36
|
+
const paginatedQueues = paginateListQueues({ client: this.client }, {});
|
|
37
|
+
const queues: string[] = [];
|
|
38
|
+
|
|
39
|
+
for await (const page of paginatedQueues) {
|
|
40
|
+
if (page.QueueUrls?.length) {
|
|
41
|
+
queues.push(...page.QueueUrls);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return queues.find((queue) => queue === this.queueUrl) ? true : false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async createQueue() {
|
|
48
|
+
const command = new CreateQueueCommand({
|
|
49
|
+
QueueName: new URL(this.queueUrl).pathname.split('/').pop()
|
|
50
|
+
});
|
|
51
|
+
const response = await this.client.send(command);
|
|
52
|
+
this.logger.info(`Queue created: ${response.QueueUrl} (expected ${this.queueUrl})`);
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
async pushToQueue(event: Object): Promise<any> {
|
|
31
|
-
if (
|
|
56
|
+
if (this.queueUrl === 'undefined') {
|
|
32
57
|
return { message: 'SQS_QUEUE_URL is undefined' };
|
|
33
58
|
}
|
|
59
|
+
if (!this.queueExists) {
|
|
60
|
+
this.logger.info('Checking if queue exists');
|
|
61
|
+
if (!(await this.checkQueueExists())) {
|
|
62
|
+
this.logger.error('Queue does not exist, creating queue');
|
|
63
|
+
await this.createQueue();
|
|
64
|
+
this.queueExists = true;
|
|
65
|
+
} else {
|
|
66
|
+
this.queueExists = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
34
69
|
const params: SendMessageCommandInput = {
|
|
35
70
|
MessageAttributes: {
|
|
36
71
|
Event: {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AbstractDBAdapter, IGetItemInput, IGetItems, IHandleErrorOutput, IPutItemInput } from '../../types/interfaces';
|
|
2
|
+
import winston from 'winston';
|
|
3
|
+
export declare class ClickHouseDBAdapter implements AbstractDBAdapter {
|
|
4
|
+
logger: winston.Logger;
|
|
5
|
+
dbClient: any;
|
|
6
|
+
constructor(logger: winston.Logger);
|
|
7
|
+
tableExists(name: string): Promise<boolean>;
|
|
8
|
+
putItem(params: IPutItemInput): Promise<boolean>;
|
|
9
|
+
getItem(params: IGetItemInput): Promise<any>;
|
|
10
|
+
deleteItem(params: IGetItemInput): Promise<boolean>;
|
|
11
|
+
getItemsBySession(params: IGetItems): Promise<any[]>;
|
|
12
|
+
handleError(errorObject: any): IHandleErrorOutput;
|
|
13
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ClickHouseDBAdapter = void 0;
|
|
13
|
+
const client_1 = require("@clickhouse/client");
|
|
14
|
+
const interfaces_1 = require("../../types/interfaces");
|
|
15
|
+
class ClickHouseDBAdapter {
|
|
16
|
+
constructor(logger) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.dbClient = (0, client_1.createClient)({
|
|
19
|
+
url: process.env.CLICKHOUSE_URL
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
tableExists(name) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
try {
|
|
25
|
+
const query = `SELECT 1 FROM system.tables WHERE database = currentDatabase() AND name = '${name}'`;
|
|
26
|
+
const resultSet = yield this.dbClient.query({
|
|
27
|
+
query,
|
|
28
|
+
format: 'JSONEachRow'
|
|
29
|
+
});
|
|
30
|
+
const rows = yield resultSet.json();
|
|
31
|
+
if (rows.length > 0) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Create table if it does not exists
|
|
36
|
+
const createTableQuery = `
|
|
37
|
+
CREATE TABLE IF NOT EXISTS ${name} (
|
|
38
|
+
event String,
|
|
39
|
+
sessionId String,
|
|
40
|
+
timestamp DateTime64(3),
|
|
41
|
+
playhead Float64,
|
|
42
|
+
duration Float64,
|
|
43
|
+
live Boolean,
|
|
44
|
+
contentId String,
|
|
45
|
+
userId String,
|
|
46
|
+
deviceId String,
|
|
47
|
+
deviceModel String,
|
|
48
|
+
deviceType String,
|
|
49
|
+
payload String, /* Stored as JSON string */
|
|
50
|
+
|
|
51
|
+
/* Add derived columns for better query performance */
|
|
52
|
+
event_date Date DEFAULT toDate(timestamp),
|
|
53
|
+
event_hour DateTime DEFAULT toStartOfHour(timestamp)
|
|
54
|
+
)
|
|
55
|
+
ENGINE = MergeTree()
|
|
56
|
+
PARTITION BY toYYYYMM(timestamp)
|
|
57
|
+
ORDER BY (sessionId, timestamp)
|
|
58
|
+
`;
|
|
59
|
+
yield this.dbClient.query({
|
|
60
|
+
query: createTableQuery
|
|
61
|
+
});
|
|
62
|
+
this.logger.info(`Table '${name}' created successfully.`);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
this.logger.error(`Error checking if table '${name}' exists:`);
|
|
68
|
+
this.logger.error(err);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
putItem(params) {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
const tableName = params.tableName;
|
|
76
|
+
const item = params.data;
|
|
77
|
+
// Prepare the item for insertion
|
|
78
|
+
this.logger.debug(item);
|
|
79
|
+
// Convert payload to JSON string if it's an object
|
|
80
|
+
const payload = typeof item.payload === 'object'
|
|
81
|
+
? JSON.stringify(item.payload)
|
|
82
|
+
: item.payload || '';
|
|
83
|
+
// Prepare the data for insertion
|
|
84
|
+
let parsedPayload = {};
|
|
85
|
+
if (payload) {
|
|
86
|
+
try {
|
|
87
|
+
parsedPayload = JSON.parse(payload);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.logger.warn('Payload not json, skipping parsing');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const data = [{
|
|
94
|
+
event: item.event,
|
|
95
|
+
sessionId: item.sessionId,
|
|
96
|
+
timestamp: item.timestamp,
|
|
97
|
+
playhead: item.playhead || -1,
|
|
98
|
+
duration: item.duration || -1,
|
|
99
|
+
live: parsedPayload['live'] || false,
|
|
100
|
+
contentId: parsedPayload['contentId'] || '',
|
|
101
|
+
userId: parsedPayload['userId'] || '',
|
|
102
|
+
deviceId: parsedPayload['deviceId'] || '',
|
|
103
|
+
deviceModel: parsedPayload['deviceModel'] || '',
|
|
104
|
+
deviceType: parsedPayload['deviceType'] || '',
|
|
105
|
+
payload
|
|
106
|
+
}];
|
|
107
|
+
// Insert the data
|
|
108
|
+
yield this.dbClient.insert({
|
|
109
|
+
table: tableName,
|
|
110
|
+
values: data,
|
|
111
|
+
format: 'JSONEachRow'
|
|
112
|
+
});
|
|
113
|
+
this.logger.debug(`Successfully inserted item into ${tableName}`);
|
|
114
|
+
return true;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
getItem(params) {
|
|
118
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
119
|
+
throw new Error('Method not implemented.');
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
deleteItem(params) {
|
|
123
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
throw new Error('Method not implemented.');
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
getItemsBySession(params) {
|
|
128
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
throw new Error('Method not implemented.');
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
handleError(errorObject) {
|
|
133
|
+
this.logger.error(errorObject);
|
|
134
|
+
const errorOutput = {
|
|
135
|
+
errorType: interfaces_1.ErrorType.ABORT,
|
|
136
|
+
error: errorObject,
|
|
137
|
+
};
|
|
138
|
+
return errorOutput;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.ClickHouseDBAdapter = ClickHouseDBAdapter;
|
|
142
|
+
//# sourceMappingURL=ClickHouseDBAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClickHouseDBAdapter.js","sourceRoot":"","sources":["../../../adapters/db/ClickHouseDBAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,11 @@ import winston from 'winston';
|
|
|
4
4
|
export declare class SqsQueueAdapter implements AbstractQueueAdapter {
|
|
5
5
|
logger: winston.Logger;
|
|
6
6
|
client: SQSClient;
|
|
7
|
+
queueUrl: string;
|
|
8
|
+
queueExists: boolean;
|
|
7
9
|
constructor(logger: winston.Logger);
|
|
10
|
+
private checkQueueExists;
|
|
11
|
+
private createQueue;
|
|
8
12
|
pushToQueue(event: Object): Promise<any>;
|
|
9
13
|
pullFromQueue(): Promise<any>;
|
|
10
14
|
removeFromQueue(queueMsg: Message): Promise<any>;
|
|
@@ -8,11 +8,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
11
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
19
|
exports.SqsQueueAdapter = void 0;
|
|
13
20
|
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
14
21
|
class SqsQueueAdapter {
|
|
15
22
|
constructor(logger) {
|
|
23
|
+
this.queueExists = false;
|
|
16
24
|
this.logger = logger;
|
|
17
25
|
let region;
|
|
18
26
|
if ('QUEUE_REGION' in process.env) {
|
|
@@ -21,14 +29,59 @@ class SqsQueueAdapter {
|
|
|
21
29
|
else {
|
|
22
30
|
region = process.env.AWS_REGION;
|
|
23
31
|
}
|
|
32
|
+
this.queueUrl = process.env.SQS_QUEUE_URL;
|
|
24
33
|
this.logger.info(`SQS Region: ${region}`);
|
|
25
34
|
this.client = new client_sqs_1.SQSClient({ region: region, endpoint: process.env.SQS_ENDPOINT });
|
|
26
35
|
}
|
|
36
|
+
checkQueueExists() {
|
|
37
|
+
var e_1, _a;
|
|
38
|
+
var _b;
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
const paginatedQueues = (0, client_sqs_1.paginateListQueues)({ client: this.client }, {});
|
|
41
|
+
const queues = [];
|
|
42
|
+
try {
|
|
43
|
+
for (var paginatedQueues_1 = __asyncValues(paginatedQueues), paginatedQueues_1_1; paginatedQueues_1_1 = yield paginatedQueues_1.next(), !paginatedQueues_1_1.done;) {
|
|
44
|
+
const page = paginatedQueues_1_1.value;
|
|
45
|
+
if ((_b = page.QueueUrls) === null || _b === void 0 ? void 0 : _b.length) {
|
|
46
|
+
queues.push(...page.QueueUrls);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
51
|
+
finally {
|
|
52
|
+
try {
|
|
53
|
+
if (paginatedQueues_1_1 && !paginatedQueues_1_1.done && (_a = paginatedQueues_1.return)) yield _a.call(paginatedQueues_1);
|
|
54
|
+
}
|
|
55
|
+
finally { if (e_1) throw e_1.error; }
|
|
56
|
+
}
|
|
57
|
+
return queues.find((queue) => queue === this.queueUrl) ? true : false;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
createQueue() {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
const command = new client_sqs_1.CreateQueueCommand({
|
|
63
|
+
QueueName: new URL(this.queueUrl).pathname.split('/').pop()
|
|
64
|
+
});
|
|
65
|
+
const response = yield this.client.send(command);
|
|
66
|
+
this.logger.info(`Queue created: ${response.QueueUrl} (expected ${this.queueUrl})`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
27
69
|
pushToQueue(event) {
|
|
28
70
|
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
-
if (
|
|
71
|
+
if (this.queueUrl === 'undefined') {
|
|
30
72
|
return { message: 'SQS_QUEUE_URL is undefined' };
|
|
31
73
|
}
|
|
74
|
+
if (!this.queueExists) {
|
|
75
|
+
this.logger.info('Checking if queue exists');
|
|
76
|
+
if (!(yield this.checkQueueExists())) {
|
|
77
|
+
this.logger.error('Queue does not exist, creating queue');
|
|
78
|
+
yield this.createQueue();
|
|
79
|
+
this.queueExists = true;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.queueExists = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
32
85
|
const params = {
|
|
33
86
|
MessageAttributes: {
|
|
34
87
|
Event: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SqsQueueAdapter.js","sourceRoot":"","sources":["../../../adapters/queue/SqsQueueAdapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SqsQueueAdapter.js","sourceRoot":"","sources":["../../../adapters/queue/SqsQueueAdapter.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export * from './adapters/queue/BeanstalkdAdapter';
|
|
|
3
3
|
export * from './adapters/queue/RedisAdapter';
|
|
4
4
|
export * from './adapters/db/DynamoDBAdapter';
|
|
5
5
|
export * from './adapters/db/MongoDBAdapter';
|
|
6
|
+
export * from './adapters/db/ClickHouseDBAdapter';
|
|
6
7
|
export * from './util/constants';
|
package/build/index.js
CHANGED
|
@@ -15,5 +15,6 @@ __exportStar(require("./adapters/queue/BeanstalkdAdapter"), exports);
|
|
|
15
15
|
__exportStar(require("./adapters/queue/RedisAdapter"), exports);
|
|
16
16
|
__exportStar(require("./adapters/db/DynamoDBAdapter"), exports);
|
|
17
17
|
__exportStar(require("./adapters/db/MongoDBAdapter"), exports);
|
|
18
|
+
__exportStar(require("./adapters/db/ClickHouseDBAdapter"), exports);
|
|
18
19
|
__exportStar(require("./util/constants"), exports);
|
|
19
20
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mEAAiD;AACjD,qEAAmD;AACnD,gEAA8C;AAE9C,gEAA8C;AAC9C,+DAA6C;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mEAAiD;AACjD,qEAAmD;AACnD,gEAA8C;AAE9C,gEAA8C;AAC9C,+DAA6C;AAC7C,oEAAkD;AAElD,mDAAiC"}
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eyevinn/player-analytics-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Shared modules & adapters for EPAS components",
|
|
5
5
|
"source": "index.ts",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@aws-sdk/client-dynamodb": "^3.751.0",
|
|
30
30
|
"@aws-sdk/client-sqs": "^3.750.0",
|
|
31
31
|
"@aws-sdk/util-dynamodb": "^3.44.0",
|
|
32
|
+
"@clickhouse/client": "^1.10.1",
|
|
32
33
|
"@types/jasmine": "^3.10.2",
|
|
33
34
|
"@types/node": "^16.6.1",
|
|
34
35
|
"aws-sdk-client-mock": "^4.1.0",
|
|
@@ -36,6 +36,8 @@ describe('SQS Queue Adapter', () => {
|
|
|
36
36
|
playhead: 0,
|
|
37
37
|
duration: 0,
|
|
38
38
|
};
|
|
39
|
+
// Using any type assertion to bypass TypeScript's type checking on spyOn
|
|
40
|
+
spyOn(adapter as any, 'checkQueueExists').and.returnValue(true);
|
|
39
41
|
sqsMock.on(SendMessageCommand).resolves(sqsResp);
|
|
40
42
|
const result = await adapter.pushToQueue(event);
|
|
41
43
|
expect(result).toEqual(sqsResp);
|
|
@@ -52,6 +54,8 @@ describe('SQS Queue Adapter', () => {
|
|
|
52
54
|
playhead: 0,
|
|
53
55
|
duration: 0,
|
|
54
56
|
};
|
|
57
|
+
// Using any type assertion to bypass TypeScript's type checking on spyOn
|
|
58
|
+
spyOn(adapter as any, 'checkQueueExists').and.returnValue(true);
|
|
55
59
|
sqsMock.on(SendMessageCommand).resolves(sqsResp);
|
|
56
60
|
const result = await adapter.pushToQueue(event);
|
|
57
61
|
expect(result).toEqual(sqsResp);
|
|
@@ -139,6 +143,10 @@ describe('SQS Queue Adapter', () => {
|
|
|
139
143
|
sqsMock.on(ReceiveMessageCommand).rejects(errMsg);
|
|
140
144
|
sqsMock.on(DeleteMessageCommand).rejects(errMsg);
|
|
141
145
|
const queueAdapter = new SqsQueueAdapter(Logger);
|
|
146
|
+
|
|
147
|
+
// Using any type assertion to bypass TypeScript's type checking on spyOn
|
|
148
|
+
spyOn(queueAdapter as any, 'checkQueueExists').and.returnValue(true);
|
|
149
|
+
|
|
142
150
|
let pushResult = await queueAdapter.pushToQueue(mockEvent);
|
|
143
151
|
let readResult = await queueAdapter.pullFromQueue();
|
|
144
152
|
let removeResult = await queueAdapter.removeFromQueue(mockSQSMessage);
|