@xube/kit-aws 0.0.112 → 0.1.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/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/resources/athena/get.d.ts +2 -16
- package/dist/resources/athena/get.d.ts.map +1 -1
- package/dist/resources/athena/get.js +6 -93
- package/dist/resources/athena/query.d.ts +4 -0
- package/dist/resources/athena/query.d.ts.map +1 -0
- package/dist/resources/athena/query.js +46 -0
- package/dist/resources/athena/transform.d.ts +3 -0
- package/dist/resources/athena/transform.d.ts.map +1 -0
- package/dist/resources/athena/transform.js +25 -0
- package/dist/resources/athena/types.d.ts +12 -0
- package/dist/resources/athena/types.d.ts.map +1 -0
- package/dist/resources/athena/types.js +2 -0
- package/dist/resources/athena/wait.d.ts +4 -0
- package/dist/resources/athena/wait.d.ts.map +1 -0
- package/dist/resources/athena/wait.js +33 -0
- package/package.json +6 -6
- package/src/index.ts +4 -0
- package/src/resources/athena/get.ts +3 -159
- package/src/resources/athena/query.ts +63 -0
- package/src/resources/athena/transform.ts +28 -0
- package/src/resources/athena/types.ts +12 -0
- package/src/resources/athena/wait.ts +58 -0
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export * from "./resources/dynamodb/transform";
|
|
|
8
8
|
export * from "./resources/parameter-store/get";
|
|
9
9
|
export * from "./resources/athena/client";
|
|
10
10
|
export * from "./resources/athena/get";
|
|
11
|
+
export * from "./resources/athena/query";
|
|
12
|
+
export * from "./resources/athena/transform";
|
|
13
|
+
export * from "./resources/athena/types";
|
|
14
|
+
export * from "./resources/athena/wait";
|
|
11
15
|
export * from "./resources/kinesis/client";
|
|
12
16
|
export * from "./resources/kinesis/put";
|
|
13
17
|
export * from "./resources/firehose/client";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAE7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAE/C,cAAc,iCAAiC,CAAC;AAEhD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAE7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAE/C,cAAc,iCAAiC,CAAC;AAEhD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AAExC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AAExC,cAAc,6BAA6B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,10 @@ __exportStar(require("./resources/dynamodb/transform"), exports);
|
|
|
24
24
|
__exportStar(require("./resources/parameter-store/get"), exports);
|
|
25
25
|
__exportStar(require("./resources/athena/client"), exports);
|
|
26
26
|
__exportStar(require("./resources/athena/get"), exports);
|
|
27
|
+
__exportStar(require("./resources/athena/query"), exports);
|
|
28
|
+
__exportStar(require("./resources/athena/transform"), exports);
|
|
29
|
+
__exportStar(require("./resources/athena/types"), exports);
|
|
30
|
+
__exportStar(require("./resources/athena/wait"), exports);
|
|
27
31
|
__exportStar(require("./resources/kinesis/client"), exports);
|
|
28
32
|
__exportStar(require("./resources/kinesis/put"), exports);
|
|
29
33
|
__exportStar(require("./resources/firehose/client"), exports);
|
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GetQueryResultsCommandOutput } from "@aws-sdk/client-athena";
|
|
2
2
|
import { APIGatewayProxyResult } from "aws-lambda";
|
|
3
|
-
import {
|
|
4
|
-
export declare const waitForQueryCompletion: (athena: AthenaClient, queryExecutionId: string, log?: XubeLog, maxWaitTimeMs?: number, pollIntervalMs?: number) => Promise<boolean>;
|
|
5
|
-
export declare const transformAthenaResults: (resultSet: ResultSet | undefined, isNextPage: boolean) => Record<string, any>[];
|
|
6
|
-
interface PaginationParams {
|
|
7
|
-
isNextPage: boolean;
|
|
8
|
-
queryExecutionId?: string;
|
|
9
|
-
nextToken?: string;
|
|
10
|
-
}
|
|
11
|
-
type FetchDataRequest = {
|
|
12
|
-
from: number;
|
|
13
|
-
to?: number;
|
|
14
|
-
nextToken?: string;
|
|
15
|
-
queryExecutionId?: string;
|
|
16
|
-
};
|
|
3
|
+
import { PaginationParams, FetchDataRequest } from "./types";
|
|
17
4
|
export declare const handlePagination: (request: FetchDataRequest, glueDatabaseName: string, glueTableName: string, s3BucketName: string, s3BucketAthenaResultsPrefix: string) => Promise<string>;
|
|
18
5
|
export declare const fetchQueryResults: (queryExecutionId: string, paginationParams: PaginationParams, limit: number) => Promise<GetQueryResultsCommandOutput>;
|
|
19
6
|
export declare const formatResponse: (resultsResponse: GetQueryResultsCommandOutput, queryExecutionId: string, paginationParams: PaginationParams) => APIGatewayProxyResult;
|
|
20
|
-
export {};
|
|
21
7
|
//# sourceMappingURL=get.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../src/resources/athena/get.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../src/resources/athena/get.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKnD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAG7D,eAAO,MAAM,gBAAgB,YAClB,gBAAgB,oBACP,MAAM,iBACT,MAAM,gBACP,MAAM,+BACS,MAAM,KAClC,OAAO,CAAC,MAAM,CAkBhB,CAAC;AAEF,eAAO,MAAM,iBAAiB,qBACV,MAAM,oBACN,gBAAgB,SAC3B,MAAM,KACZ,OAAO,CAAC,4BAA4B,CAiBtC,CAAC;AAEF,eAAO,MAAM,cAAc,oBACR,4BAA4B,oBAC3B,MAAM,oBACN,gBAAgB,KACjC,qBAuBF,CAAC"}
|
|
@@ -1,99 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.formatResponse = exports.fetchQueryResults = exports.handlePagination =
|
|
3
|
+
exports.formatResponse = exports.fetchQueryResults = exports.handlePagination = void 0;
|
|
4
4
|
const client_athena_1 = require("@aws-sdk/client-athena");
|
|
5
5
|
const kit_constants_1 = require("@xube/kit-constants");
|
|
6
|
-
const kit_log_1 = require("@xube/kit-log");
|
|
7
6
|
const zod_1 = require("zod");
|
|
8
7
|
const client_1 = require("./client");
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
let attempts = 0;
|
|
12
|
-
const maxAttempts = Math.ceil(maxWaitTimeMs / pollIntervalMs);
|
|
13
|
-
while (attempts < maxAttempts) {
|
|
14
|
-
attempts++;
|
|
15
|
-
const queryExecution = await athena.send(new client_athena_1.GetQueryExecutionCommand({
|
|
16
|
-
QueryExecutionId: queryExecutionId,
|
|
17
|
-
}));
|
|
18
|
-
const state = queryExecution.QueryExecution?.Status?.State;
|
|
19
|
-
if (state === "SUCCEEDED") {
|
|
20
|
-
log.info(`Query completed successfully after ${attempts} attempts (${Date.now() - startTime}ms)`);
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
if (state === "FAILED" || state === "CANCELLED") {
|
|
24
|
-
log.error(`Query failed with state: ${state} after ${attempts} attempts (${Date.now() - startTime}ms)`);
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
if (Date.now() - startTime >= maxWaitTimeMs) {
|
|
28
|
-
log.error(`Query timeout: exceeded maximum wait time of ${maxWaitTimeMs}ms after ${attempts} attempts`);
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
32
|
-
}
|
|
33
|
-
log.error(`Query timeout: exceeded maximum attempts (${maxAttempts}) after ${Date.now() - startTime}ms`);
|
|
34
|
-
return false;
|
|
35
|
-
};
|
|
36
|
-
exports.waitForQueryCompletion = waitForQueryCompletion;
|
|
37
|
-
const transformAthenaResults = (resultSet, isNextPage) => {
|
|
38
|
-
if (!resultSet?.Rows || resultSet.Rows.length < 2) {
|
|
39
|
-
return [];
|
|
40
|
-
}
|
|
41
|
-
const headers = resultSet.ResultSetMetadata?.ColumnInfo?.map((col) => col.Name ?? "") ?? [];
|
|
42
|
-
return resultSet.Rows.slice(isNextPage ? 0 : 1).map((row) => {
|
|
43
|
-
const normalizedRow = {};
|
|
44
|
-
row.Data.forEach((col, index) => {
|
|
45
|
-
const header = headers?.[index];
|
|
46
|
-
if (header) {
|
|
47
|
-
try {
|
|
48
|
-
normalizedRow[header] = JSON.parse(col.VarCharValue);
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
normalizedRow[header] = col.VarCharValue;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
return normalizedRow;
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
exports.transformAthenaResults = transformAthenaResults;
|
|
59
|
-
const buildQuery = (request, glueDatabaseName, glueTableName) => {
|
|
60
|
-
const { from, to } = request;
|
|
61
|
-
if (to) {
|
|
62
|
-
return `
|
|
63
|
-
SELECT * FROM "${glueDatabaseName}"."${glueTableName}"
|
|
64
|
-
WHERE "s" BETWEEN ${from} AND ${to}
|
|
65
|
-
ORDER BY "s" DESC
|
|
66
|
-
`;
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
return `
|
|
70
|
-
SELECT * FROM "${glueDatabaseName}"."${glueTableName}"
|
|
71
|
-
WHERE "s" >= ${from}
|
|
72
|
-
ORDER BY "s" DESC
|
|
73
|
-
`;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
const executeNewQuery = async (query, glueDatabaseName, s3BucketName, s3BucketAthenaResultsPrefix) => {
|
|
77
|
-
const athenaClient = (0, client_1.getAthenaClient)();
|
|
78
|
-
const queryExecution = await athenaClient.send(new client_athena_1.StartQueryExecutionCommand({
|
|
79
|
-
QueryString: query,
|
|
80
|
-
QueryExecutionContext: {
|
|
81
|
-
Database: glueDatabaseName,
|
|
82
|
-
},
|
|
83
|
-
WorkGroup: "primary",
|
|
84
|
-
ResultConfiguration: {
|
|
85
|
-
OutputLocation: `s3://${s3BucketName}/${s3BucketAthenaResultsPrefix}`,
|
|
86
|
-
},
|
|
87
|
-
}));
|
|
88
|
-
if (!queryExecution.QueryExecutionId) {
|
|
89
|
-
throw new Error("No QueryExecutionId returned");
|
|
90
|
-
}
|
|
91
|
-
const queryCompleted = await (0, exports.waitForQueryCompletion)(athenaClient, queryExecution.QueryExecutionId);
|
|
92
|
-
if (!queryCompleted) {
|
|
93
|
-
throw new Error(`Query execution failed`);
|
|
94
|
-
}
|
|
95
|
-
return queryExecution.QueryExecutionId;
|
|
96
|
-
};
|
|
8
|
+
const transform_1 = require("./transform");
|
|
9
|
+
const query_1 = require("./query");
|
|
97
10
|
const handlePagination = async (request, glueDatabaseName, glueTableName, s3BucketName, s3BucketAthenaResultsPrefix) => {
|
|
98
11
|
const paginationParams = {
|
|
99
12
|
isNextPage: !!request.nextToken && !!request.queryExecutionId,
|
|
@@ -104,8 +17,8 @@ const handlePagination = async (request, glueDatabaseName, glueTableName, s3Buck
|
|
|
104
17
|
return request.queryExecutionId;
|
|
105
18
|
}
|
|
106
19
|
else {
|
|
107
|
-
const query = buildQuery(request, glueDatabaseName, glueTableName);
|
|
108
|
-
return await executeNewQuery(query, glueDatabaseName, s3BucketName, s3BucketAthenaResultsPrefix);
|
|
20
|
+
const query = (0, query_1.buildQuery)(request, glueDatabaseName, glueTableName);
|
|
21
|
+
return await (0, query_1.executeNewQuery)(query, glueDatabaseName, s3BucketName, s3BucketAthenaResultsPrefix);
|
|
109
22
|
}
|
|
110
23
|
};
|
|
111
24
|
exports.handlePagination = handlePagination;
|
|
@@ -125,7 +38,7 @@ const fetchQueryResults = async (queryExecutionId, paginationParams, limit) => {
|
|
|
125
38
|
exports.fetchQueryResults = fetchQueryResults;
|
|
126
39
|
const formatResponse = (resultsResponse, queryExecutionId, paginationParams) => {
|
|
127
40
|
const DataListSchema = zod_1.z.array(zod_1.z.record(zod_1.z.string(), zod_1.z.any()));
|
|
128
|
-
const data = DataListSchema.parse((0,
|
|
41
|
+
const data = DataListSchema.parse((0, transform_1.transformAthenaResults)(resultsResponse.ResultSet, paginationParams.isNextPage));
|
|
129
42
|
const hasNextPage = !!resultsResponse.NextToken;
|
|
130
43
|
return {
|
|
131
44
|
statusCode: kit_constants_1.StatusCode.OK,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { FetchDataRequest } from "./types";
|
|
2
|
+
export declare const buildQuery: (request: FetchDataRequest, glueDatabaseName: string, glueTableName: string) => string;
|
|
3
|
+
export declare const executeNewQuery: (query: string, glueDatabaseName: string, s3BucketName: string, s3BucketAthenaResultsPrefix: string) => Promise<string>;
|
|
4
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/resources/athena/query.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,eAAO,MAAM,UAAU,YACZ,gBAAgB,oBACP,MAAM,iBACT,MAAM,KACpB,MAgBF,CAAC;AAEF,eAAO,MAAM,eAAe,UACnB,MAAM,oBACK,MAAM,gBACV,MAAM,+BACS,MAAM,KAClC,OAAO,CAAC,MAAM,CA8BhB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeNewQuery = exports.buildQuery = void 0;
|
|
4
|
+
const client_athena_1 = require("@aws-sdk/client-athena");
|
|
5
|
+
const client_1 = require("./client");
|
|
6
|
+
const wait_1 = require("./wait");
|
|
7
|
+
const buildQuery = (request, glueDatabaseName, glueTableName) => {
|
|
8
|
+
const { from, to } = request;
|
|
9
|
+
if (to) {
|
|
10
|
+
return `
|
|
11
|
+
SELECT * FROM "${glueDatabaseName}"."${glueTableName}"
|
|
12
|
+
WHERE "s" BETWEEN ${from} AND ${to}
|
|
13
|
+
ORDER BY "s" DESC
|
|
14
|
+
`;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
return `
|
|
18
|
+
SELECT * FROM "${glueDatabaseName}"."${glueTableName}"
|
|
19
|
+
WHERE "s" >= ${from}
|
|
20
|
+
ORDER BY "s" DESC
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.buildQuery = buildQuery;
|
|
25
|
+
const executeNewQuery = async (query, glueDatabaseName, s3BucketName, s3BucketAthenaResultsPrefix) => {
|
|
26
|
+
const athenaClient = (0, client_1.getAthenaClient)();
|
|
27
|
+
const queryExecution = await athenaClient.send(new client_athena_1.StartQueryExecutionCommand({
|
|
28
|
+
QueryString: query,
|
|
29
|
+
QueryExecutionContext: {
|
|
30
|
+
Database: glueDatabaseName,
|
|
31
|
+
},
|
|
32
|
+
WorkGroup: "primary",
|
|
33
|
+
ResultConfiguration: {
|
|
34
|
+
OutputLocation: `s3://${s3BucketName}/${s3BucketAthenaResultsPrefix}`,
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
if (!queryExecution.QueryExecutionId) {
|
|
38
|
+
throw new Error("No QueryExecutionId returned");
|
|
39
|
+
}
|
|
40
|
+
const queryCompleted = await (0, wait_1.waitForQueryCompletion)(athenaClient, queryExecution.QueryExecutionId);
|
|
41
|
+
if (!queryCompleted) {
|
|
42
|
+
throw new Error(`Query execution failed`);
|
|
43
|
+
}
|
|
44
|
+
return queryExecution.QueryExecutionId;
|
|
45
|
+
};
|
|
46
|
+
exports.executeNewQuery = executeNewQuery;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/resources/athena/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,eAAO,MAAM,sBAAsB,cACtB,SAAS,GAAG,SAAS,cACpB,OAAO,KAClB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAsBrB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformAthenaResults = void 0;
|
|
4
|
+
const transformAthenaResults = (resultSet, isNextPage) => {
|
|
5
|
+
if (!resultSet?.Rows || resultSet.Rows.length < 2) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
const headers = resultSet.ResultSetMetadata?.ColumnInfo?.map((col) => col.Name ?? "") ?? [];
|
|
9
|
+
return resultSet.Rows.slice(isNextPage ? 0 : 1).map((row) => {
|
|
10
|
+
const normalizedRow = {};
|
|
11
|
+
row.Data.forEach((col, index) => {
|
|
12
|
+
const header = headers?.[index];
|
|
13
|
+
if (header) {
|
|
14
|
+
try {
|
|
15
|
+
normalizedRow[header] = JSON.parse(col.VarCharValue);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
normalizedRow[header] = col.VarCharValue;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return normalizedRow;
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
exports.transformAthenaResults = transformAthenaResults;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PaginationParams {
|
|
2
|
+
isNextPage: boolean;
|
|
3
|
+
queryExecutionId?: string;
|
|
4
|
+
nextToken?: string;
|
|
5
|
+
}
|
|
6
|
+
export type FetchDataRequest = {
|
|
7
|
+
from: number;
|
|
8
|
+
to?: number;
|
|
9
|
+
nextToken?: string;
|
|
10
|
+
queryExecutionId?: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/resources/athena/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AthenaClient } from "@aws-sdk/client-athena";
|
|
2
|
+
import { XubeLog } from "@xube/kit-log";
|
|
3
|
+
export declare const waitForQueryCompletion: (athena: AthenaClient, queryExecutionId: string, log?: XubeLog, maxWaitTimeMs?: number, pollIntervalMs?: number) => Promise<boolean>;
|
|
4
|
+
//# sourceMappingURL=wait.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../../src/resources/athena/wait.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA4B,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,eAAO,MAAM,sBAAsB,WACzB,YAAY,oBACF,MAAM,QACnB,OAAO,kBACG,MAAM,mBACL,MAAM,KACrB,OAAO,CAAC,OAAO,CAgDjB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.waitForQueryCompletion = void 0;
|
|
4
|
+
const client_athena_1 = require("@aws-sdk/client-athena");
|
|
5
|
+
const kit_log_1 = require("@xube/kit-log");
|
|
6
|
+
const waitForQueryCompletion = async (athena, queryExecutionId, log = kit_log_1.XubeLog.getInstance(), maxWaitTimeMs = 300000, pollIntervalMs = 500) => {
|
|
7
|
+
const startTime = Date.now();
|
|
8
|
+
let attempts = 0;
|
|
9
|
+
const maxAttempts = Math.ceil(maxWaitTimeMs / pollIntervalMs);
|
|
10
|
+
while (attempts < maxAttempts) {
|
|
11
|
+
attempts++;
|
|
12
|
+
const queryExecution = await athena.send(new client_athena_1.GetQueryExecutionCommand({
|
|
13
|
+
QueryExecutionId: queryExecutionId,
|
|
14
|
+
}));
|
|
15
|
+
const state = queryExecution.QueryExecution?.Status?.State;
|
|
16
|
+
if (state === "SUCCEEDED") {
|
|
17
|
+
log.info(`Query completed successfully after ${attempts} attempts (${Date.now() - startTime}ms)`);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (state === "FAILED" || state === "CANCELLED") {
|
|
21
|
+
log.error(`Query failed with state: ${state} after ${attempts} attempts (${Date.now() - startTime}ms)`);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (Date.now() - startTime >= maxWaitTimeMs) {
|
|
25
|
+
log.error(`Query timeout: exceeded maximum wait time of ${maxWaitTimeMs}ms after ${attempts} attempts`);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
29
|
+
}
|
|
30
|
+
log.error(`Query timeout: exceeded maximum attempts (${maxAttempts}) after ${Date.now() - startTime}ms`);
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
exports.waitForQueryCompletion = waitForQueryCompletion;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xube/kit-aws",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"homepage": "https://github.com/XubeLtd/dev-kit#readme",
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/aws-lambda": "^8.10.119",
|
|
21
|
-
"@xube/kit-build": "^0.0
|
|
21
|
+
"@xube/kit-build": "^0.1.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@aws-sdk/client-athena": "^3.656.0",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"@aws-sdk/client-ssm": "^3.654.0",
|
|
30
30
|
"@aws-sdk/lib-dynamodb": "^3.656.0",
|
|
31
31
|
"@aws-sdk/util-dynamodb": "^3.656.0",
|
|
32
|
-
"@xube/kit-aws-schema": "^0.0
|
|
33
|
-
"@xube/kit-log": "^0.0
|
|
34
|
-
"@xube/kit-request": "^0.0
|
|
35
|
-
"@xube/kit-schema": "^0.0
|
|
32
|
+
"@xube/kit-aws-schema": "^0.1.0",
|
|
33
|
+
"@xube/kit-log": "^0.1.0",
|
|
34
|
+
"@xube/kit-request": "^0.1.0",
|
|
35
|
+
"@xube/kit-schema": "^0.1.0",
|
|
36
36
|
"zod": "^3.23.8"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,10 @@ export * from "./resources/parameter-store/get";
|
|
|
11
11
|
|
|
12
12
|
export * from "./resources/athena/client";
|
|
13
13
|
export * from "./resources/athena/get";
|
|
14
|
+
export * from "./resources/athena/query";
|
|
15
|
+
export * from "./resources/athena/transform";
|
|
16
|
+
export * from "./resources/athena/types";
|
|
17
|
+
export * from "./resources/athena/wait";
|
|
14
18
|
|
|
15
19
|
export * from "./resources/kinesis/client";
|
|
16
20
|
export * from "./resources/kinesis/put";
|
|
@@ -1,171 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AthenaClient,
|
|
3
|
-
GetQueryExecutionCommand,
|
|
4
3
|
GetQueryResultsCommand,
|
|
5
4
|
GetQueryResultsCommandOutput,
|
|
6
|
-
ResultSet,
|
|
7
|
-
StartQueryExecutionCommand,
|
|
8
5
|
} from "@aws-sdk/client-athena";
|
|
9
6
|
import { APIGatewayProxyResult } from "aws-lambda";
|
|
10
7
|
import { StatusCode } from "@xube/kit-constants";
|
|
11
|
-
import { XubeLog } from "@xube/kit-log";
|
|
12
8
|
import { z } from "zod";
|
|
13
9
|
import { getAthenaClient } from "./client";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
queryExecutionId: string,
|
|
18
|
-
log: XubeLog = XubeLog.getInstance(),
|
|
19
|
-
maxWaitTimeMs: number = 300000,
|
|
20
|
-
pollIntervalMs: number = 500
|
|
21
|
-
): Promise<boolean> => {
|
|
22
|
-
const startTime = Date.now();
|
|
23
|
-
let attempts = 0;
|
|
24
|
-
const maxAttempts = Math.ceil(maxWaitTimeMs / pollIntervalMs);
|
|
25
|
-
|
|
26
|
-
while (attempts < maxAttempts) {
|
|
27
|
-
attempts++;
|
|
28
|
-
|
|
29
|
-
const queryExecution = await athena.send(
|
|
30
|
-
new GetQueryExecutionCommand({
|
|
31
|
-
QueryExecutionId: queryExecutionId,
|
|
32
|
-
})
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const state = queryExecution.QueryExecution?.Status?.State;
|
|
36
|
-
if (state === "SUCCEEDED") {
|
|
37
|
-
log.info(
|
|
38
|
-
`Query completed successfully after ${attempts} attempts (${
|
|
39
|
-
Date.now() - startTime
|
|
40
|
-
}ms)`
|
|
41
|
-
);
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
if (state === "FAILED" || state === "CANCELLED") {
|
|
45
|
-
log.error(
|
|
46
|
-
`Query failed with state: ${state} after ${attempts} attempts (${
|
|
47
|
-
Date.now() - startTime
|
|
48
|
-
}ms)`
|
|
49
|
-
);
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (Date.now() - startTime >= maxWaitTimeMs) {
|
|
54
|
-
log.error(
|
|
55
|
-
`Query timeout: exceeded maximum wait time of ${maxWaitTimeMs}ms after ${attempts} attempts`
|
|
56
|
-
);
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
log.error(
|
|
64
|
-
`Query timeout: exceeded maximum attempts (${maxAttempts}) after ${
|
|
65
|
-
Date.now() - startTime
|
|
66
|
-
}ms`
|
|
67
|
-
);
|
|
68
|
-
return false;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export const transformAthenaResults = (
|
|
72
|
-
resultSet: ResultSet | undefined,
|
|
73
|
-
isNextPage: boolean
|
|
74
|
-
): Record<string, any>[] => {
|
|
75
|
-
if (!resultSet?.Rows || resultSet.Rows.length < 2) {
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const headers =
|
|
80
|
-
resultSet.ResultSetMetadata?.ColumnInfo?.map((col) => col.Name ?? "") ?? [];
|
|
81
|
-
|
|
82
|
-
return resultSet.Rows.slice(isNextPage ? 0 : 1).map((row: any) => {
|
|
83
|
-
const normalizedRow: Record<string, any> = {};
|
|
84
|
-
row.Data.forEach((col: any, index: number) => {
|
|
85
|
-
const header = headers?.[index];
|
|
86
|
-
if (header) {
|
|
87
|
-
try {
|
|
88
|
-
normalizedRow[header] = JSON.parse(col.VarCharValue);
|
|
89
|
-
} catch (error) {
|
|
90
|
-
normalizedRow[header] = col.VarCharValue;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
return normalizedRow;
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
interface PaginationParams {
|
|
99
|
-
isNextPage: boolean;
|
|
100
|
-
queryExecutionId?: string;
|
|
101
|
-
nextToken?: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
type FetchDataRequest = {
|
|
105
|
-
from: number;
|
|
106
|
-
to?: number;
|
|
107
|
-
nextToken?: string;
|
|
108
|
-
queryExecutionId?: string;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const buildQuery = (
|
|
112
|
-
request: FetchDataRequest,
|
|
113
|
-
glueDatabaseName: string,
|
|
114
|
-
glueTableName: string
|
|
115
|
-
): string => {
|
|
116
|
-
const { from, to } = request;
|
|
117
|
-
|
|
118
|
-
if (to) {
|
|
119
|
-
return `
|
|
120
|
-
SELECT * FROM "${glueDatabaseName}"."${glueTableName}"
|
|
121
|
-
WHERE "s" BETWEEN ${from} AND ${to}
|
|
122
|
-
ORDER BY "s" DESC
|
|
123
|
-
`;
|
|
124
|
-
} else {
|
|
125
|
-
return `
|
|
126
|
-
SELECT * FROM "${glueDatabaseName}"."${glueTableName}"
|
|
127
|
-
WHERE "s" >= ${from}
|
|
128
|
-
ORDER BY "s" DESC
|
|
129
|
-
`;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const executeNewQuery = async (
|
|
134
|
-
query: string,
|
|
135
|
-
glueDatabaseName: string,
|
|
136
|
-
s3BucketName: string,
|
|
137
|
-
s3BucketAthenaResultsPrefix: string
|
|
138
|
-
): Promise<string> => {
|
|
139
|
-
const athenaClient = getAthenaClient();
|
|
140
|
-
|
|
141
|
-
const queryExecution = await athenaClient.send(
|
|
142
|
-
new StartQueryExecutionCommand({
|
|
143
|
-
QueryString: query,
|
|
144
|
-
QueryExecutionContext: {
|
|
145
|
-
Database: glueDatabaseName,
|
|
146
|
-
},
|
|
147
|
-
WorkGroup: "primary",
|
|
148
|
-
ResultConfiguration: {
|
|
149
|
-
OutputLocation: `s3://${s3BucketName}/${s3BucketAthenaResultsPrefix}`,
|
|
150
|
-
},
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
if (!queryExecution.QueryExecutionId) {
|
|
155
|
-
throw new Error("No QueryExecutionId returned");
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const queryCompleted = await waitForQueryCompletion(
|
|
159
|
-
athenaClient,
|
|
160
|
-
queryExecution.QueryExecutionId
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
if (!queryCompleted) {
|
|
164
|
-
throw new Error(`Query execution failed`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return queryExecution.QueryExecutionId;
|
|
168
|
-
};
|
|
10
|
+
import { transformAthenaResults } from "./transform";
|
|
11
|
+
import { PaginationParams, FetchDataRequest } from "./types";
|
|
12
|
+
import { buildQuery, executeNewQuery } from "./query";
|
|
169
13
|
|
|
170
14
|
export const handlePagination = async (
|
|
171
15
|
request: FetchDataRequest,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { StartQueryExecutionCommand } from "@aws-sdk/client-athena";
|
|
2
|
+
import { getAthenaClient } from "./client";
|
|
3
|
+
import { waitForQueryCompletion } from "./wait";
|
|
4
|
+
import { FetchDataRequest } from "./types";
|
|
5
|
+
|
|
6
|
+
export const buildQuery = (
|
|
7
|
+
request: FetchDataRequest,
|
|
8
|
+
glueDatabaseName: string,
|
|
9
|
+
glueTableName: string
|
|
10
|
+
): string => {
|
|
11
|
+
const { from, to } = request;
|
|
12
|
+
|
|
13
|
+
if (to) {
|
|
14
|
+
return `
|
|
15
|
+
SELECT * FROM "${glueDatabaseName}"."${glueTableName}"
|
|
16
|
+
WHERE "s" BETWEEN ${from} AND ${to}
|
|
17
|
+
ORDER BY "s" DESC
|
|
18
|
+
`;
|
|
19
|
+
} else {
|
|
20
|
+
return `
|
|
21
|
+
SELECT * FROM "${glueDatabaseName}"."${glueTableName}"
|
|
22
|
+
WHERE "s" >= ${from}
|
|
23
|
+
ORDER BY "s" DESC
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const executeNewQuery = async (
|
|
29
|
+
query: string,
|
|
30
|
+
glueDatabaseName: string,
|
|
31
|
+
s3BucketName: string,
|
|
32
|
+
s3BucketAthenaResultsPrefix: string
|
|
33
|
+
): Promise<string> => {
|
|
34
|
+
const athenaClient = getAthenaClient();
|
|
35
|
+
|
|
36
|
+
const queryExecution = await athenaClient.send(
|
|
37
|
+
new StartQueryExecutionCommand({
|
|
38
|
+
QueryString: query,
|
|
39
|
+
QueryExecutionContext: {
|
|
40
|
+
Database: glueDatabaseName,
|
|
41
|
+
},
|
|
42
|
+
WorkGroup: "primary",
|
|
43
|
+
ResultConfiguration: {
|
|
44
|
+
OutputLocation: `s3://${s3BucketName}/${s3BucketAthenaResultsPrefix}`,
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (!queryExecution.QueryExecutionId) {
|
|
50
|
+
throw new Error("No QueryExecutionId returned");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const queryCompleted = await waitForQueryCompletion(
|
|
54
|
+
athenaClient,
|
|
55
|
+
queryExecution.QueryExecutionId
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (!queryCompleted) {
|
|
59
|
+
throw new Error(`Query execution failed`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return queryExecution.QueryExecutionId;
|
|
63
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ResultSet } from "@aws-sdk/client-athena";
|
|
2
|
+
|
|
3
|
+
export const transformAthenaResults = (
|
|
4
|
+
resultSet: ResultSet | undefined,
|
|
5
|
+
isNextPage: boolean
|
|
6
|
+
): Record<string, any>[] => {
|
|
7
|
+
if (!resultSet?.Rows || resultSet.Rows.length < 2) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const headers =
|
|
12
|
+
resultSet.ResultSetMetadata?.ColumnInfo?.map((col) => col.Name ?? "") ?? [];
|
|
13
|
+
|
|
14
|
+
return resultSet.Rows.slice(isNextPage ? 0 : 1).map((row: any) => {
|
|
15
|
+
const normalizedRow: Record<string, any> = {};
|
|
16
|
+
row.Data.forEach((col: any, index: number) => {
|
|
17
|
+
const header = headers?.[index];
|
|
18
|
+
if (header) {
|
|
19
|
+
try {
|
|
20
|
+
normalizedRow[header] = JSON.parse(col.VarCharValue);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
normalizedRow[header] = col.VarCharValue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return normalizedRow;
|
|
27
|
+
});
|
|
28
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AthenaClient, GetQueryExecutionCommand } from "@aws-sdk/client-athena";
|
|
2
|
+
import { XubeLog } from "@xube/kit-log";
|
|
3
|
+
|
|
4
|
+
export const waitForQueryCompletion = async (
|
|
5
|
+
athena: AthenaClient,
|
|
6
|
+
queryExecutionId: string,
|
|
7
|
+
log: XubeLog = XubeLog.getInstance(),
|
|
8
|
+
maxWaitTimeMs: number = 300000,
|
|
9
|
+
pollIntervalMs: number = 500
|
|
10
|
+
): Promise<boolean> => {
|
|
11
|
+
const startTime = Date.now();
|
|
12
|
+
let attempts = 0;
|
|
13
|
+
const maxAttempts = Math.ceil(maxWaitTimeMs / pollIntervalMs);
|
|
14
|
+
|
|
15
|
+
while (attempts < maxAttempts) {
|
|
16
|
+
attempts++;
|
|
17
|
+
|
|
18
|
+
const queryExecution = await athena.send(
|
|
19
|
+
new GetQueryExecutionCommand({
|
|
20
|
+
QueryExecutionId: queryExecutionId,
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const state = queryExecution.QueryExecution?.Status?.State;
|
|
25
|
+
if (state === "SUCCEEDED") {
|
|
26
|
+
log.info(
|
|
27
|
+
`Query completed successfully after ${attempts} attempts (${
|
|
28
|
+
Date.now() - startTime
|
|
29
|
+
}ms)`
|
|
30
|
+
);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (state === "FAILED" || state === "CANCELLED") {
|
|
34
|
+
log.error(
|
|
35
|
+
`Query failed with state: ${state} after ${attempts} attempts (${
|
|
36
|
+
Date.now() - startTime
|
|
37
|
+
}ms)`
|
|
38
|
+
);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (Date.now() - startTime >= maxWaitTimeMs) {
|
|
43
|
+
log.error(
|
|
44
|
+
`Query timeout: exceeded maximum wait time of ${maxWaitTimeMs}ms after ${attempts} attempts`
|
|
45
|
+
);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
log.error(
|
|
53
|
+
`Query timeout: exceeded maximum attempts (${maxAttempts}) after ${
|
|
54
|
+
Date.now() - startTime
|
|
55
|
+
}ms`
|
|
56
|
+
);
|
|
57
|
+
return false;
|
|
58
|
+
};
|