@xube/kit-aws 0.0.112 → 0.0.113

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 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";
@@ -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;AAEvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AAExC,cAAc,6BAA6B,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 { AthenaClient, GetQueryResultsCommandOutput, ResultSet } from "@aws-sdk/client-athena";
1
+ import { GetQueryResultsCommandOutput } from "@aws-sdk/client-athena";
2
2
  import { APIGatewayProxyResult } from "aws-lambda";
3
- import { XubeLog } from "@xube/kit-log";
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,EACL,YAAY,EAGZ,4BAA4B,EAC5B,SAAS,EAEV,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAIxC,eAAO,MAAM,sBAAsB,WACzB,YAAY,oBACF,MAAM,QACnB,OAAO,kBACG,MAAM,mBACL,MAAM,KACrB,OAAO,CAAC,OAAO,CAgDjB,CAAC;AAEF,eAAO,MAAM,sBAAsB,cACtB,SAAS,GAAG,SAAS,cACpB,OAAO,KAClB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAsBrB,CAAC;AAEF,UAAU,gBAAgB;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AA6DF,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
+ {"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;AAK7D,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,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatResponse = exports.fetchQueryResults = exports.handlePagination = exports.transformAthenaResults = exports.waitForQueryCompletion = void 0;
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 waitForQueryCompletion = async (athena, queryExecutionId, log = kit_log_1.XubeLog.getInstance(), maxWaitTimeMs = 300000, pollIntervalMs = 500) => {
10
- const startTime = Date.now();
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");
10
+ // helpers and types moved to separate modules
97
11
  const handlePagination = async (request, glueDatabaseName, glueTableName, s3BucketName, s3BucketAthenaResultsPrefix) => {
98
12
  const paginationParams = {
99
13
  isNextPage: !!request.nextToken && !!request.queryExecutionId,
@@ -104,8 +18,8 @@ const handlePagination = async (request, glueDatabaseName, glueTableName, s3Buck
104
18
  return request.queryExecutionId;
105
19
  }
106
20
  else {
107
- const query = buildQuery(request, glueDatabaseName, glueTableName);
108
- return await executeNewQuery(query, glueDatabaseName, s3BucketName, s3BucketAthenaResultsPrefix);
21
+ const query = (0, query_1.buildQuery)(request, glueDatabaseName, glueTableName);
22
+ return await (0, query_1.executeNewQuery)(query, glueDatabaseName, s3BucketName, s3BucketAthenaResultsPrefix);
109
23
  }
110
24
  };
111
25
  exports.handlePagination = handlePagination;
@@ -125,7 +39,7 @@ const fetchQueryResults = async (queryExecutionId, paginationParams, limit) => {
125
39
  exports.fetchQueryResults = fetchQueryResults;
126
40
  const formatResponse = (resultsResponse, queryExecutionId, paginationParams) => {
127
41
  const DataListSchema = zod_1.z.array(zod_1.z.record(zod_1.z.string(), zod_1.z.any()));
128
- const data = DataListSchema.parse((0, exports.transformAthenaResults)(resultsResponse.ResultSet, paginationParams.isNextPage));
42
+ const data = DataListSchema.parse((0, transform_1.transformAthenaResults)(resultsResponse.ResultSet, paginationParams.isNextPage));
129
43
  const hasNextPage = !!resultsResponse.NextToken;
130
44
  return {
131
45
  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,3 @@
1
+ import { ResultSet } from "@aws-sdk/client-athena";
2
+ export declare const transformAthenaResults: (resultSet: ResultSet | undefined, isNextPage: boolean) => Record<string, any>[];
3
+ //# sourceMappingURL=transform.d.ts.map
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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.112",
3
+ "version": "0.0.113",
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.112"
21
+ "@xube/kit-build": "^0.0.113"
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.112",
33
- "@xube/kit-log": "^0.0.112",
34
- "@xube/kit-request": "^0.0.112",
35
- "@xube/kit-schema": "^0.0.112",
32
+ "@xube/kit-aws-schema": "^0.0.113",
33
+ "@xube/kit-log": "^0.0.113",
34
+ "@xube/kit-request": "^0.0.113",
35
+ "@xube/kit-schema": "^0.0.113",
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,17 @@
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";
10
+ import { transformAthenaResults } from "./transform";
11
+ import { PaginationParams, FetchDataRequest } from "./types";
12
+ import { buildQuery, executeNewQuery } from "./query";
14
13
 
15
- export const waitForQueryCompletion = async (
16
- athena: AthenaClient,
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
- };
14
+ // helpers and types moved to separate modules
169
15
 
170
16
  export const handlePagination = async (
171
17
  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,12 @@
1
+ export interface PaginationParams {
2
+ isNextPage: boolean;
3
+ queryExecutionId?: string;
4
+ nextToken?: string;
5
+ }
6
+
7
+ export type FetchDataRequest = {
8
+ from: number;
9
+ to?: number;
10
+ nextToken?: string;
11
+ queryExecutionId?: string;
12
+ };
@@ -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
+ };