@stilyng94/athena-query-client 1.0.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/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/athena.d.ts +20 -0
- package/dist/athena.js +92 -0
- package/dist/athena.js.map +1 -0
- package/dist/demo.d.ts +1 -0
- package/dist/demo.js +15 -0
- package/dist/demo.js.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/json-file-appender.d.ts +24 -0
- package/dist/json-file-appender.js +105 -0
- package/dist/json-file-appender.js.map +1 -0
- package/dist/query-results-processor.d.ts +37 -0
- package/dist/query-results-processor.js +231 -0
- package/dist/query-results-processor.js.map +1 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2024] [Paul Osei Kofi]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
**athena-query-client**
|
|
2
|
+
|
|
3
|
+
# @stilyng94/athena-query-client
|
|
4
|
+
|
|
5
|
+
A lightweight client library for executing Amazon Athena queries.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @stilyng94/athena-query-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### !!Notice
|
|
14
|
+
|
|
15
|
+
Ensure aws `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` envs are set
|
|
16
|
+
|
|
17
|
+
### Usage
|
|
18
|
+
|
|
19
|
+
## Get query results via S3 and write to JsonFile
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import {
|
|
23
|
+
AthenaQueryClient,
|
|
24
|
+
S3QueryResultProcessor,
|
|
25
|
+
JsonFileAppender,
|
|
26
|
+
} from "@stilyng94/athena-query-client";
|
|
27
|
+
|
|
28
|
+
const query = "SELECT * from api_logs";
|
|
29
|
+
|
|
30
|
+
const execute = async (query) => {
|
|
31
|
+
const athena = new AthenaQueryClient({
|
|
32
|
+
Catalog: "catalog",
|
|
33
|
+
Database: "logs",
|
|
34
|
+
ClientConfig: { region: "us-west-2" },
|
|
35
|
+
WorkGroup: "primary",
|
|
36
|
+
s3OutputLocation: "s3://results/queries",
|
|
37
|
+
s3Region: "us-west-2",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const queryExecutionId = await athena.query();
|
|
41
|
+
|
|
42
|
+
const jsonFileAppender = new JsonFileAppender({
|
|
43
|
+
directory: "",
|
|
44
|
+
fileName: "",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const s3QueryResultProcessor = new S3QueryResultProcessor({
|
|
48
|
+
s3OutputLocation: "s3://results/queries",
|
|
49
|
+
s3Region: "us-west-2",
|
|
50
|
+
batchSize: 999,
|
|
51
|
+
onData: async (data) => {
|
|
52
|
+
await jsonFileAppender.flush(data);
|
|
53
|
+
},
|
|
54
|
+
onComplete: async () => {
|
|
55
|
+
await jsonFileAppender.closeFileWithBracket();
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
s3QueryResultProcessor
|
|
59
|
+
.processResults(queryExecutionId)
|
|
60
|
+
.catch((error) => console.error(error));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
execute().catch(console.error);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Get query results and map to Js object
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import {
|
|
70
|
+
AthenaQueryClient,
|
|
71
|
+
MappedQueryResultProcessor,
|
|
72
|
+
} from "@stilyng94/athena-query-client";
|
|
73
|
+
|
|
74
|
+
const query = "SELECT * from api_logs";
|
|
75
|
+
|
|
76
|
+
const execute = async (query) => {
|
|
77
|
+
const athena = new AthenaQueryClient({
|
|
78
|
+
Catalog: "catalog",
|
|
79
|
+
Database: "logs",
|
|
80
|
+
ClientConfig: { region: "us-west-2" },
|
|
81
|
+
WorkGroup: "primary",
|
|
82
|
+
s3OutputLocation: "s3://results/queries",
|
|
83
|
+
s3Region: "us-west-2",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const queryExecutionId = await athena.query();
|
|
87
|
+
|
|
88
|
+
const mappedQueryResultProcessor = new MappedQueryResultProcessor({
|
|
89
|
+
athenaClient: athena,
|
|
90
|
+
});
|
|
91
|
+
mappedQueryResultProcessor.processResults(queryExecutionId).then((data) => {
|
|
92
|
+
console.log(data);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
execute().catch(console.error);
|
|
97
|
+
```
|
package/dist/athena.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AthenaQueryClientConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Client for executing queries against AWS Athena and retrieving results
|
|
4
|
+
* @class AthenaQueryClient
|
|
5
|
+
*/
|
|
6
|
+
export declare class AthenaQueryClient {
|
|
7
|
+
#private;
|
|
8
|
+
/**
|
|
9
|
+
* Creates an instance of AthenaQueryClient
|
|
10
|
+
* @param {AthenaQueryClientConfig} config - Configuration object for the client
|
|
11
|
+
*/
|
|
12
|
+
constructor(config: AthenaQueryClientConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Executes an SQL query in Athena and returns the QueryExecutionId
|
|
15
|
+
* @param {string} sqlQuery - The SQL query string to execute
|
|
16
|
+
* @returns {Promise<string>} Promise resolving to QueryExecutionId
|
|
17
|
+
* @throws {Error} If query execution fails or encounters an unknown state
|
|
18
|
+
*/
|
|
19
|
+
query(sqlQuery: string): Promise<string>;
|
|
20
|
+
}
|
package/dist/athena.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AthenaClient, GetQueryExecutionCommand, QueryExecutionState, StartQueryExecutionCommand, } from '@aws-sdk/client-athena';
|
|
2
|
+
import { EMPTY, defer, firstValueFrom, from, of, throwError, timer, } from 'rxjs';
|
|
3
|
+
import { expand, filter, switchMap, take } from 'rxjs/operators';
|
|
4
|
+
import { AthenaQueryError } from './errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Client for executing queries against AWS Athena and retrieving results
|
|
7
|
+
* @class AthenaQueryClient
|
|
8
|
+
*/
|
|
9
|
+
export class AthenaQueryClient {
|
|
10
|
+
#database;
|
|
11
|
+
#client;
|
|
12
|
+
#catalog;
|
|
13
|
+
#workGroup;
|
|
14
|
+
#resultReuseConfiguration;
|
|
15
|
+
#s3OutputLocation;
|
|
16
|
+
/**
|
|
17
|
+
* Creates an instance of AthenaQueryClient
|
|
18
|
+
* @param {AthenaQueryClientConfig} config - Configuration object for the client
|
|
19
|
+
*/
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.#client = new AthenaClient({
|
|
22
|
+
...config.ClientConfig,
|
|
23
|
+
region: config.s3Region,
|
|
24
|
+
});
|
|
25
|
+
this.#database = config.Database;
|
|
26
|
+
this.#catalog = config.Catalog;
|
|
27
|
+
this.#workGroup = config.WorkGroup || 'primary';
|
|
28
|
+
this.#resultReuseConfiguration = config.ResultReuseConfiguration || {
|
|
29
|
+
ResultReuseByAgeConfiguration: { Enabled: true, MaxAgeInMinutes: 60 },
|
|
30
|
+
};
|
|
31
|
+
this.#s3OutputLocation = config.s3OutputLocation;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Executes an SQL query in Athena and returns the QueryExecutionId
|
|
35
|
+
* @param {string} sqlQuery - The SQL query string to execute
|
|
36
|
+
* @returns {Promise<string>} Promise resolving to QueryExecutionId
|
|
37
|
+
* @throws {Error} If query execution fails or encounters an unknown state
|
|
38
|
+
*/
|
|
39
|
+
async query(sqlQuery) {
|
|
40
|
+
console.log('Executing query:', sqlQuery);
|
|
41
|
+
const queryExecutionInput = {
|
|
42
|
+
QueryString: sqlQuery,
|
|
43
|
+
QueryExecutionContext: {
|
|
44
|
+
Database: this.#database,
|
|
45
|
+
Catalog: this.#catalog,
|
|
46
|
+
},
|
|
47
|
+
WorkGroup: this.#workGroup,
|
|
48
|
+
ResultReuseConfiguration: this.#resultReuseConfiguration,
|
|
49
|
+
ResultConfiguration: { OutputLocation: this.#s3OutputLocation },
|
|
50
|
+
};
|
|
51
|
+
const { QueryExecutionId } = await this.#client.send(new StartQueryExecutionCommand(queryExecutionInput));
|
|
52
|
+
console.log('Query execution started with ID:', QueryExecutionId);
|
|
53
|
+
if (!QueryExecutionId) {
|
|
54
|
+
throw new AthenaQueryError('Failed to start query: QueryExecutionId is undefined');
|
|
55
|
+
}
|
|
56
|
+
return await firstValueFrom(this.#checkQueryExecutionState(QueryExecutionId)).catch((error) => {
|
|
57
|
+
console.error('Query execution failed:', error);
|
|
58
|
+
throw new AthenaQueryError(`Query execution failed: ${error.message}`);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Polls the Athena query execution state until completion
|
|
63
|
+
* @param {string} QueryExecutionId - The ID of the query being executed
|
|
64
|
+
* @returns {Observable<string>} Observable emitting QueryExecutionId on success
|
|
65
|
+
*/
|
|
66
|
+
#checkQueryExecutionState(QueryExecutionId) {
|
|
67
|
+
return defer(() => from(this.#client.send(new GetQueryExecutionCommand({ QueryExecutionId })))).pipe(switchMap((response) => {
|
|
68
|
+
const state = response.QueryExecution?.Status?.State;
|
|
69
|
+
console.log(`Query execution state: ${state}`);
|
|
70
|
+
switch (state) {
|
|
71
|
+
case QueryExecutionState.SUCCEEDED:
|
|
72
|
+
return of(QueryExecutionId);
|
|
73
|
+
case QueryExecutionState.FAILED: {
|
|
74
|
+
const errorMessage = response.QueryExecution?.Status?.StateChangeReason ||
|
|
75
|
+
'Unknown reason';
|
|
76
|
+
console.error('Query failed:', errorMessage);
|
|
77
|
+
return throwError(() => new Error(`Query failed: ${errorMessage}`));
|
|
78
|
+
}
|
|
79
|
+
case QueryExecutionState.CANCELLED:
|
|
80
|
+
return throwError(() => new Error('Query was cancelled'));
|
|
81
|
+
case QueryExecutionState.QUEUED:
|
|
82
|
+
case QueryExecutionState.RUNNING:
|
|
83
|
+
return of(null); // Continue polling
|
|
84
|
+
default:
|
|
85
|
+
return throwError(() => new Error(`Unknown query state: ${state}`));
|
|
86
|
+
}
|
|
87
|
+
}), expand((result) => result === null
|
|
88
|
+
? timer(1000).pipe(switchMap(() => this.#checkQueryExecutionState(QueryExecutionId)))
|
|
89
|
+
: EMPTY), filter((result) => result !== null), take(1));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=athena.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"athena.js","sourceRoot":"","sources":["../src/athena.ts"],"names":[],"mappings":"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"}
|
package/dist/demo.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/demo.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AthenaQueryClient } from "./athena.js";
|
|
2
|
+
const client = new AthenaQueryClient({
|
|
3
|
+
Catalog: "",
|
|
4
|
+
s3OutputLocation: "s3://sjka/s",
|
|
5
|
+
s3Region: "us-east-1",
|
|
6
|
+
Database: "",
|
|
7
|
+
ClientConfig: {
|
|
8
|
+
region: "us-east-1",
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
client
|
|
12
|
+
.query("select * from db")
|
|
13
|
+
.then((d) => console.log(d))
|
|
14
|
+
.then((e) => console.error({ e }));
|
|
15
|
+
//# sourceMappingURL=demo.js.map
|
package/dist/demo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC;IACnC,OAAO,EAAE,EAAE;IACX,gBAAgB,EAAE,aAAa;IAC/B,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,EAAE;IACZ,YAAY,EAAE;QACZ,MAAM,EAAE,WAAW;KACpB;CACF,CAAC,CAAC;AACH,MAAM;KACH,KAAK,CAAC,kBAAkB,CAAC;KACzB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAC3B,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC"}
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAGvB;IAFlB,YACE,OAAe,EACC,gBAAyB;QAEzC,KAAK,CAAC,OAAO,CAAC,CAAA;QAFE,qBAAgB,GAAhB,gBAAgB,CAAS;QAGzC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAA;IAChC,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AthenaQueryClient } from './athena.js';
|
|
2
|
+
export { AthenaQueryError } from './errors.js';
|
|
3
|
+
export { MappedQueryResultProcessor, S3QueryResultProcessor, } from './query-results-processor.js';
|
|
4
|
+
export type { AthenaQueryClientConfig, MappedQueryResultProcessorParams, QueryResultProcessor, S3QueryResultProcessorParams, JsonFileAppenderOptions, } from './types.js';
|
|
5
|
+
export { JsonFileAppender } from './json-file-appender.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AthenaQueryClient } from './athena.js';
|
|
2
|
+
export { AthenaQueryError } from './errors.js';
|
|
3
|
+
export { MappedQueryResultProcessor, S3QueryResultProcessor, } from './query-results-processor.js';
|
|
4
|
+
export { JsonFileAppender } from './json-file-appender.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,8BAA8B,CAAA;AAQrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JsonFileAppenderOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
*@description Handles appending JSON data to files with proper formatting
|
|
4
|
+
*/
|
|
5
|
+
export declare class JsonFileAppender {
|
|
6
|
+
#private;
|
|
7
|
+
private readonly options;
|
|
8
|
+
/**
|
|
9
|
+
*@description Creates a new JsonFileAppender instance
|
|
10
|
+
* @param {AppendOptions} options - Configuration options
|
|
11
|
+
*/
|
|
12
|
+
constructor(options: JsonFileAppenderOptions);
|
|
13
|
+
/**
|
|
14
|
+
*@description Flushes the current batch of data to the file
|
|
15
|
+
* @param {unknown[]} currentBatch - Array of items to write
|
|
16
|
+
* @returns {Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
flush(currentBatch: unknown[]): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* @description Adds closing bracket to JSON array file
|
|
21
|
+
* @returns {void}
|
|
22
|
+
*/
|
|
23
|
+
closeFileWithBracket(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs';
|
|
2
|
+
import { access, mkdir, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
*@description Handles appending JSON data to files with proper formatting
|
|
6
|
+
*/
|
|
7
|
+
export class JsonFileAppender {
|
|
8
|
+
options;
|
|
9
|
+
#isFirstWrite = true;
|
|
10
|
+
#filePath;
|
|
11
|
+
/**
|
|
12
|
+
*@description Creates a new JsonFileAppender instance
|
|
13
|
+
* @param {AppendOptions} options - Configuration options
|
|
14
|
+
*/
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.#filePath = path.join(options.directory, options.fileName);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
*@description Flushes the current batch of data to the file
|
|
21
|
+
* @param {unknown[]} currentBatch - Array of items to write
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*/
|
|
24
|
+
async flush(currentBatch) {
|
|
25
|
+
console.log(`Flushing batch of ${currentBatch.length} items to ${this.options.fileName}`);
|
|
26
|
+
await this.#ensureDirectoryExists(this.options.directory);
|
|
27
|
+
const filePath = path.join(this.options.directory, this.options.fileName);
|
|
28
|
+
await this.#initializeFile();
|
|
29
|
+
const writeStream = createWriteStream(filePath, {
|
|
30
|
+
flags: 'a',
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
for (let i = 0; i < currentBatch.length; i++) {
|
|
34
|
+
const item = currentBatch[i];
|
|
35
|
+
// Add comma if not first item and not first write
|
|
36
|
+
if (!this.#isFirstWrite || i > 0) {
|
|
37
|
+
writeStream.write(',\n');
|
|
38
|
+
}
|
|
39
|
+
writeStream.write(JSON.stringify(item, null));
|
|
40
|
+
}
|
|
41
|
+
this.#isFirstWrite = false;
|
|
42
|
+
console.log(`Successfully flushed ${currentBatch.length} items`);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error('Error while flushing data:', error);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
writeStream.end();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @description Adds closing bracket to JSON array file
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
56
|
+
closeFileWithBracket() {
|
|
57
|
+
console.log(`Closing JSON array in file: ${this.#filePath}`);
|
|
58
|
+
const writeStream = createWriteStream(this.#filePath, { flags: 'a' });
|
|
59
|
+
try {
|
|
60
|
+
writeStream.write('\n]');
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error('Error while closing JSON array:', error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
writeStream.end();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
*@description Initializes the file with proper JSON array structure
|
|
72
|
+
* @returns {Promise<void>}
|
|
73
|
+
*/
|
|
74
|
+
async #initializeFile() {
|
|
75
|
+
try {
|
|
76
|
+
await access(this.#filePath);
|
|
77
|
+
// File exists, check if it's empty or has content
|
|
78
|
+
const statistics = await stat(this.#filePath);
|
|
79
|
+
this.#isFirstWrite = statistics.size === 0;
|
|
80
|
+
console.log(`File exists. Size: ${statistics.size} bytes. First write: ${this.#isFirstWrite}`);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// File doesn't exist, create it with initial array bracket
|
|
84
|
+
console.log(`Creating new file: ${this.#filePath}`);
|
|
85
|
+
await writeFile(this.#filePath, '[\n', 'utf-8');
|
|
86
|
+
this.#isFirstWrite = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
*@description Ensures the specified directory exists, creates if it doesn't
|
|
91
|
+
* @param {string} directory - Directory path to check/create
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async #ensureDirectoryExists(directory) {
|
|
95
|
+
try {
|
|
96
|
+
await access(directory);
|
|
97
|
+
console.log(`Directory ${directory} exists`);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
await mkdir(directory, { recursive: true });
|
|
101
|
+
console.log(`Created directory ${directory}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=json-file-appender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-file-appender.js","sourceRoot":"","sources":["../src/json-file-appender.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type MappedQueryResultProcessorParams, type QueryResultProcessor, type S3QueryResultProcessorParams } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Processes Athena query results stored in S3
|
|
4
|
+
*/
|
|
5
|
+
export declare class S3QueryResultProcessor implements QueryResultProcessor {
|
|
6
|
+
#private;
|
|
7
|
+
private readonly config;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new S3QueryResultProcessor
|
|
10
|
+
* @param {S3QueryResultProcessorParams} config - Configuration parameters
|
|
11
|
+
*/
|
|
12
|
+
constructor(config: S3QueryResultProcessorParams);
|
|
13
|
+
/**
|
|
14
|
+
* Processes query results from S3
|
|
15
|
+
* @param {string} queryExecutionId - The ID of the query execution
|
|
16
|
+
* @returns {Promise<void>} Promise that resolves when processing is complete
|
|
17
|
+
*/
|
|
18
|
+
processResults(queryExecutionId: string): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Processes Athena query results by mapping them to objects
|
|
22
|
+
*/
|
|
23
|
+
export declare class MappedQueryResultProcessor implements QueryResultProcessor {
|
|
24
|
+
#private;
|
|
25
|
+
private readonly config;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new MappedQueryResultProcessor
|
|
28
|
+
* @param {MappedQueryResultProcessorParams} config - Configuration parameters
|
|
29
|
+
*/
|
|
30
|
+
constructor(config: MappedQueryResultProcessorParams);
|
|
31
|
+
/**
|
|
32
|
+
* Processes query results by mapping them to objects
|
|
33
|
+
* @param {string} queryExecutionId - The ID of the query execution
|
|
34
|
+
* @returns {Promise<Record<string, string>[]>} Promise resolving to array of mapped objects
|
|
35
|
+
*/
|
|
36
|
+
processResults(queryExecutionId: string): Promise<Record<string, string>[]>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { finished } from 'node:stream/promises';
|
|
2
|
+
import { GetQueryResultsCommand, } from '@aws-sdk/client-athena';
|
|
3
|
+
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
4
|
+
import { parse } from 'csv-parse';
|
|
5
|
+
import { MAX_BATCH_SIZE, } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Processes Athena query results stored in S3
|
|
8
|
+
*/
|
|
9
|
+
export class S3QueryResultProcessor {
|
|
10
|
+
config;
|
|
11
|
+
#batchSize = MAX_BATCH_SIZE;
|
|
12
|
+
#s3Client;
|
|
13
|
+
#s3OutputLocation;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new S3QueryResultProcessor
|
|
16
|
+
* @param {S3QueryResultProcessorParams} config - Configuration parameters
|
|
17
|
+
*/
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.#batchSize = this.#validateBatchSize(config.batchSize);
|
|
21
|
+
this.#s3OutputLocation = config.s3OutputLocation;
|
|
22
|
+
this.#s3Client = new S3Client({ region: config.s3Region });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Processes query results from S3
|
|
26
|
+
* @param {string} queryExecutionId - The ID of the query execution
|
|
27
|
+
* @returns {Promise<void>} Promise that resolves when processing is complete
|
|
28
|
+
*/
|
|
29
|
+
async processResults(queryExecutionId) {
|
|
30
|
+
// Get the S3 location of results
|
|
31
|
+
const s3Location = `${this.#s3OutputLocation}/${queryExecutionId}.csv`;
|
|
32
|
+
const { Bucket, Key } = this.#parseS3Url(s3Location);
|
|
33
|
+
console.log('Fetching query results from S3:', s3Location);
|
|
34
|
+
const responseStream = await this.#fetchS3Object(Bucket, Key);
|
|
35
|
+
return this.#processStreamingResults(responseStream);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Processes streaming results from S3
|
|
39
|
+
* @param {Readable} stream - The readable stream of results
|
|
40
|
+
* @returns {Promise<void>} Promise that resolves when processing is complete
|
|
41
|
+
*/
|
|
42
|
+
async #processStreamingResults(stream) {
|
|
43
|
+
const batch = [];
|
|
44
|
+
let totalProcessed = 0;
|
|
45
|
+
const parser = stream.pipe(parse({ ...(this.config.csvParseOptions ?? {}) }));
|
|
46
|
+
try {
|
|
47
|
+
parser.on('readable', async () => {
|
|
48
|
+
const record = parser.read();
|
|
49
|
+
while (record !== null) {
|
|
50
|
+
// Work with each record
|
|
51
|
+
batch.push(record);
|
|
52
|
+
if (batch.length >= this.#batchSize) {
|
|
53
|
+
console.log(`Processing batch of ${batch.length} records...`);
|
|
54
|
+
await this.#processBatch(batch);
|
|
55
|
+
totalProcessed += batch.length;
|
|
56
|
+
batch.length = 0; // Clear array efficiently
|
|
57
|
+
console.log(`Total records processed: ${totalProcessed}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
// Wait for parsing to complete
|
|
62
|
+
await finished(parser);
|
|
63
|
+
// Process remaining records
|
|
64
|
+
if (batch.length > 0) {
|
|
65
|
+
console.log(`Processing final batch of ${batch.length} records...`);
|
|
66
|
+
await this.#processBatch(batch);
|
|
67
|
+
totalProcessed += batch.length;
|
|
68
|
+
console.log(`Final total records processed: ${totalProcessed}`);
|
|
69
|
+
}
|
|
70
|
+
await this.config?.onComplete?.();
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new Error(`Error processing results: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Processes a batch of records
|
|
78
|
+
* @param {unknown[]} batch - Array of records to process
|
|
79
|
+
* @returns {Promise<void>} Promise that resolves when batch is processed
|
|
80
|
+
*/
|
|
81
|
+
async #processBatch(batch) {
|
|
82
|
+
try {
|
|
83
|
+
await this.config.onData(batch);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw new Error(`Error processing batch: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validates the batch size configuration
|
|
91
|
+
* @param {number} [batchSize] - The batch size to validate
|
|
92
|
+
* @returns {number} The validated batch size
|
|
93
|
+
* @throws {Error} If batch size exceeds maximum allowed
|
|
94
|
+
*/
|
|
95
|
+
#validateBatchSize(batchSize) {
|
|
96
|
+
if (!batchSize)
|
|
97
|
+
return MAX_BATCH_SIZE;
|
|
98
|
+
if (batchSize > MAX_BATCH_SIZE) {
|
|
99
|
+
throw new Error(`Batch size cannot be greater than ${MAX_BATCH_SIZE}`);
|
|
100
|
+
}
|
|
101
|
+
return batchSize;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Fetches an object from S3
|
|
105
|
+
* @param {string} Bucket - The S3 bucket name
|
|
106
|
+
* @param {string} Key - The S3 object key
|
|
107
|
+
* @returns {Promise<Readable>} Promise resolving to readable stream
|
|
108
|
+
* @throws {Error} If object fetch fails
|
|
109
|
+
*/
|
|
110
|
+
async #fetchS3Object(Bucket, Key) {
|
|
111
|
+
const response = await this.#s3Client.send(new GetObjectCommand({ Bucket, Key }));
|
|
112
|
+
if (!response.Body) {
|
|
113
|
+
throw new Error(`Failed to fetch file: ${Bucket}/${Key}`);
|
|
114
|
+
}
|
|
115
|
+
console.log('S3 response metadata:', response.$metadata);
|
|
116
|
+
return response.Body;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parses an S3 URL into bucket and key components
|
|
120
|
+
* @param {string} url - The S3 URL to parse
|
|
121
|
+
* @returns {{ Bucket: string; Key: string }} Object containing bucket and key
|
|
122
|
+
* @throws {Error} If URL is invalid or missing bucket name
|
|
123
|
+
*/
|
|
124
|
+
#parseS3Url(url) {
|
|
125
|
+
try {
|
|
126
|
+
const parsedUrl = new URL(url);
|
|
127
|
+
const bucket = parsedUrl.hostname.split('.')[0];
|
|
128
|
+
if (!bucket) {
|
|
129
|
+
throw new Error('Invalid S3 URL: missing bucket name');
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
Bucket: bucket,
|
|
133
|
+
Key: parsedUrl.pathname.slice(1),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw new Error(`Invalid S3 URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Processes Athena query results by mapping them to objects
|
|
143
|
+
*/
|
|
144
|
+
export class MappedQueryResultProcessor {
|
|
145
|
+
config;
|
|
146
|
+
/**
|
|
147
|
+
* Creates a new MappedQueryResultProcessor
|
|
148
|
+
* @param {MappedQueryResultProcessorParams} config - Configuration parameters
|
|
149
|
+
*/
|
|
150
|
+
constructor(config) {
|
|
151
|
+
this.config = config;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Processes query results by mapping them to objects
|
|
155
|
+
* @param {string} queryExecutionId - The ID of the query execution
|
|
156
|
+
* @returns {Promise<Record<string, string>[]>} Promise resolving to array of mapped objects
|
|
157
|
+
*/
|
|
158
|
+
async processResults(queryExecutionId) {
|
|
159
|
+
console.log('Fetching results for QueryExecutionId:', queryExecutionId);
|
|
160
|
+
try {
|
|
161
|
+
const resultSet = await this.#fetchQueryResults(queryExecutionId);
|
|
162
|
+
return this.#extractRows(resultSet);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
throw new Error(`Error processing results: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Fetches query results from Athena
|
|
170
|
+
* @param {string} queryExecutionId - The ID of the query execution
|
|
171
|
+
* @returns {Promise<ResultSet>} Promise resolving to Athena ResultSet
|
|
172
|
+
* @throws {Error} If results are empty or undefined
|
|
173
|
+
*/
|
|
174
|
+
async #fetchQueryResults(queryExecutionId) {
|
|
175
|
+
const response = await this.config.athenaClient.send(new GetQueryResultsCommand({ QueryExecutionId: queryExecutionId }));
|
|
176
|
+
if (!response.ResultSet) {
|
|
177
|
+
throw new Error('Query results are empty or undefined');
|
|
178
|
+
}
|
|
179
|
+
return response.ResultSet;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extracts column headers from result rows
|
|
183
|
+
* @param {Row[]} rows - Array of result rows
|
|
184
|
+
* @returns {string[]} Array of header names
|
|
185
|
+
* @throws {Error} If no headers are found
|
|
186
|
+
*/
|
|
187
|
+
#extractHeaders(rows) {
|
|
188
|
+
const headers = rows[0]?.Data?.map((column) => column.VarCharValue || '');
|
|
189
|
+
if (!headers || headers.length === 0) {
|
|
190
|
+
throw new Error('No headers found in the result set');
|
|
191
|
+
}
|
|
192
|
+
return headers;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Maps a row to an object using column headers as keys
|
|
196
|
+
* @param {Row} row - The row to map
|
|
197
|
+
* @param {string[]} headers - Array of column headers
|
|
198
|
+
* @returns {Record<string, string>} Object with header-value pairs
|
|
199
|
+
*/
|
|
200
|
+
#mapRowToObject(row, headers) {
|
|
201
|
+
const mappedRow = {};
|
|
202
|
+
row.Data?.forEach((value, index) => {
|
|
203
|
+
const header = headers[index];
|
|
204
|
+
if (header) {
|
|
205
|
+
mappedRow[header] = value.VarCharValue || '';
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
return mappedRow;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Extracts Athena ResultSet data into an array of key-value objects
|
|
212
|
+
* Extracts Athena ResultSet data into an array of key-value objects
|
|
213
|
+
* @private
|
|
214
|
+
* @param {ResultSet} resultSet - The ResultSet object returned by Athena
|
|
215
|
+
* @returns {Record<string, string>[]} Array of objects representing query rows
|
|
216
|
+
* @throws {Error} If no headers are found
|
|
217
|
+
*/
|
|
218
|
+
#extractRows(resultSet) {
|
|
219
|
+
const { Rows } = resultSet;
|
|
220
|
+
if (!Rows || Rows.length === 0) {
|
|
221
|
+
console.error('No rows found in result set');
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
const headers = this.#extractHeaders(Rows);
|
|
225
|
+
if (!headers.length) {
|
|
226
|
+
throw new Error('No headers found in the result set');
|
|
227
|
+
}
|
|
228
|
+
return Rows.slice(1).map((row) => this.#mapRowToObject(row, headers));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=query-results-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-results-processor.js","sourceRoot":"","sources":["../src/query-results-processor.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { AthenaClient, AthenaClientConfig, ResultReuseConfiguration } from '@aws-sdk/client-athena';
|
|
2
|
+
import type { Options } from 'csv-parse';
|
|
3
|
+
/** Maximum number of records to process in a single batch */
|
|
4
|
+
export declare const MAX_BATCH_SIZE: number;
|
|
5
|
+
/**
|
|
6
|
+
* Configuration interface for AthenaQueryClient
|
|
7
|
+
* @interface AthenaQueryClientConfig
|
|
8
|
+
*/
|
|
9
|
+
export interface AthenaQueryClientConfig {
|
|
10
|
+
/** AWS SDK Athena client configuration */
|
|
11
|
+
ClientConfig: AthenaClientConfig;
|
|
12
|
+
/** AWS Athena database name. The database must exist in the catalog */
|
|
13
|
+
Database: string;
|
|
14
|
+
/** AWS Athena data source catalog name */
|
|
15
|
+
Catalog: string;
|
|
16
|
+
/** The name of the workgroup in which the query is being started. Optional, defaults to 'primary' */
|
|
17
|
+
WorkGroup?: string;
|
|
18
|
+
/** Configuration for result reuse in Athena */
|
|
19
|
+
ResultReuseConfiguration?: ResultReuseConfiguration;
|
|
20
|
+
/** AWS region for S3 operations */
|
|
21
|
+
s3Region: string;
|
|
22
|
+
/** S3 location where query results will be stored */
|
|
23
|
+
s3OutputLocation: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Interface for processing Athena query results
|
|
27
|
+
*/
|
|
28
|
+
export interface QueryResultProcessor {
|
|
29
|
+
/**
|
|
30
|
+
* Processes the results of an Athena query execution
|
|
31
|
+
* @param {string} queryExecutionId - The ID of the query execution
|
|
32
|
+
* @returns {Promise<unknown>} Promise resolving to the processed results
|
|
33
|
+
*/
|
|
34
|
+
processResults(queryExecutionId: string): Promise<unknown>;
|
|
35
|
+
}
|
|
36
|
+
export interface S3QueryResultProcessorParams {
|
|
37
|
+
/**
|
|
38
|
+
* Callback function to process each batch of data
|
|
39
|
+
*/
|
|
40
|
+
onData: (data: unknown[]) => Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Optional Callback function to call when processing is done
|
|
43
|
+
* @param data Optional data to pass to the callback function
|
|
44
|
+
* @returns Promise resolving to void
|
|
45
|
+
*/
|
|
46
|
+
onComplete?: (data?: unknown) => Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Optional: The maximum number of records to process in a single batch
|
|
49
|
+
* @default 999
|
|
50
|
+
*/
|
|
51
|
+
batchSize?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Options for CSV parsing
|
|
54
|
+
* @default { columns: true }
|
|
55
|
+
*/
|
|
56
|
+
csvParseOptions?: Options;
|
|
57
|
+
/** S3 location where query results are being stored */
|
|
58
|
+
s3OutputLocation: string;
|
|
59
|
+
/** AWS region for S3 operations */
|
|
60
|
+
s3Region: string;
|
|
61
|
+
}
|
|
62
|
+
export interface MappedQueryResultProcessorParams {
|
|
63
|
+
/**
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
athenaClient: AthenaClient;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
*@description Options for configuring the JsonFileAppender
|
|
70
|
+
*/
|
|
71
|
+
export interface JsonFileAppenderOptions {
|
|
72
|
+
/** Name of the file to append to */
|
|
73
|
+
fileName: string;
|
|
74
|
+
/** Directory path where the file is located */
|
|
75
|
+
directory: string;
|
|
76
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAOA,6DAA6D;AAC7D,MAAM,CAAC,MAAM,cAAc,GAAW,GAAG,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stilyng94/athena-query-client",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "An Athena client to query data from Athena utilizing Aws-S3 or direct results from Athena query",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"homepage": "https://github.com/stilyng94/athena-query-client",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/stilyng94/athena-query-client"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"athena",
|
|
13
|
+
"s3",
|
|
14
|
+
"aws"
|
|
15
|
+
],
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "Paul Osei Kofi",
|
|
18
|
+
"email": "oseipaulkofi@gmail.com"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/stilyng94/athena-query-client.git"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@aws-sdk/client-athena": "^3.696.0",
|
|
30
|
+
"@aws-sdk/client-s3": "^3.717.0",
|
|
31
|
+
"csv-parse": "^5.6.0",
|
|
32
|
+
"rxjs": "^7.8.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@arethetypeswrong/cli": "^0.17.2",
|
|
36
|
+
"@biomejs/biome": "1.9.4",
|
|
37
|
+
"@changesets/cli": "^2.27.11",
|
|
38
|
+
"@total-typescript/tsconfig": "^1.0.4",
|
|
39
|
+
"@types/node": "^22.10.2",
|
|
40
|
+
"typescript": "^5.6.3"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"local-release": "changeset version && changeset publish",
|
|
46
|
+
"format-and-fix": "biome format --write .",
|
|
47
|
+
"lint-and-fix": "biome lint --write .",
|
|
48
|
+
"check": "biome check --write .",
|
|
49
|
+
"check-exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
|
|
50
|
+
"pre-ci": "pnpm format-and-fix && pnpm lint-and-fix && pnpm check",
|
|
51
|
+
"ci": "pnpm build && pnpm check-exports"
|
|
52
|
+
}
|
|
53
|
+
}
|