squilo 0.2.3 → 0.3.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 +43 -0
- package/dist/index.js +270 -0
- package/dist/index.js.map +19 -0
- package/dist/pipes/auth/strategies/index.d.ts +8 -0
- package/dist/pipes/auth/strategies/index.js +133 -0
- package/dist/pipes/auth/strategies/index.js.map +11 -0
- package/dist/pipes/output/strategies/index.d.ts +12 -0
- package/dist/pipes/output/strategies/index.js +126 -0
- package/dist/pipes/output/strategies/index.js.map +13 -0
- package/dist/shared/chunk-s3vw82k6.js +23 -0
- package/dist/shared/chunk-s3vw82k6.js.map +9 -0
- package/package.json +31 -12
- package/src/.DS_Store +0 -0
- package/src/index.ts +0 -3
- package/src/pipes/auth/index.ts +0 -14
- package/src/pipes/auth/strategies/index.ts +0 -2
- package/src/pipes/auth/strategies/msal.ts +0 -140
- package/src/pipes/auth/strategies/userAndPassword.ts +0 -10
- package/src/pipes/auth/types.ts +0 -12
- package/src/pipes/connect/index.ts +0 -61
- package/src/pipes/connect/types.ts +0 -25
- package/src/pipes/execute/index.ts +0 -42
- package/src/pipes/index.ts +0 -1
- package/src/pipes/input/index.ts +0 -14
- package/src/pipes/input/types.ts +0 -7
- package/src/pipes/output/index.ts +0 -5
- package/src/pipes/output/strategies/console.ts +0 -7
- package/src/pipes/output/strategies/index.ts +0 -4
- package/src/pipes/output/strategies/json.spec.ts +0 -48
- package/src/pipes/output/strategies/json.ts +0 -20
- package/src/pipes/output/strategies/merge.spec.ts +0 -51
- package/src/pipes/output/strategies/merge.ts +0 -11
- package/src/pipes/output/strategies/xls.spec.ts +0 -93
- package/src/pipes/output/strategies/xls.ts +0 -111
- package/src/pipes/output/types.ts +0 -1
- package/src/pipes/retrieve/index.ts +0 -61
- package/src/pipes/retrieve/types.ts +0 -5
- package/src/pipes/server/index.ts +0 -6
- package/src/pipes/server/types.ts +0 -8
- package/src/pipes/shared/transaction-runner.ts +0 -58
- package/src/pipes/types.ts +0 -1
- package/src/pool/index.ts +0 -49
- package/src/utils/append-error.spec.ts +0 -100
- package/src/utils/append-error.ts +0 -74
- package/src/utils/load-env.ts +0 -18
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { JsonOutputStrategy } from "./index";
|
|
3
|
-
|
|
4
|
-
describe("JsonOutputStrategy", () => {
|
|
5
|
-
test("should write JSON data to file and return filename", async () => {
|
|
6
|
-
const mockData = new ReadableStream({
|
|
7
|
-
start(controller) {
|
|
8
|
-
controller.enqueue({ "database1": [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }] });
|
|
9
|
-
controller.enqueue({ "database2": [{ id: 3, name: "Charlie" }] });
|
|
10
|
-
controller.close();
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const strategy = JsonOutputStrategy();
|
|
15
|
-
const result = await strategy(mockData);
|
|
16
|
-
|
|
17
|
-
// Verify file exists and contains correct data
|
|
18
|
-
const fileContent = await Bun.file(result).text();
|
|
19
|
-
const parsedData = JSON.parse(fileContent);
|
|
20
|
-
expect(parsedData).toEqual({
|
|
21
|
-
"database1": [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }],
|
|
22
|
-
"database2": [{ id: 3, name: "Charlie" }]
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Clean up
|
|
26
|
-
await Bun.$`rm ${result}`;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("should handle empty data and create file", async () => {
|
|
30
|
-
const mockData = new ReadableStream({
|
|
31
|
-
start(controller) {
|
|
32
|
-
controller.enqueue({});
|
|
33
|
-
controller.close();
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const strategy = JsonOutputStrategy();
|
|
38
|
-
const result = await strategy(mockData);
|
|
39
|
-
|
|
40
|
-
// Verify file exists and contains empty object
|
|
41
|
-
const fileContent = await Bun.file(result).text();
|
|
42
|
-
const parsedData = JSON.parse(fileContent);
|
|
43
|
-
expect(parsedData).toEqual({});
|
|
44
|
-
|
|
45
|
-
// Clean up
|
|
46
|
-
await Bun.$`rm ${result}`;
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { OutputStrategy } from '../types';
|
|
2
|
-
|
|
3
|
-
export const JsonOutputStrategy = <TData>(): OutputStrategy<TData, string> => async (result) => {
|
|
4
|
-
const data: Record<string, TData[]> = {};
|
|
5
|
-
|
|
6
|
-
for await (const item of result) {
|
|
7
|
-
Object.assign(data, item);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let filename = process.argv[1]?.replace(/\.(?:js|ts)/, '')
|
|
11
|
-
filename = `${filename}-${Date.now()}.json`;
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
await Bun.write(filename, JSON.stringify(data, null, 2));
|
|
15
|
-
} catch (error) {
|
|
16
|
-
console.error('Error writing JSON file:', error);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return filename;
|
|
20
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { MergeOutputStrategy } from "./index";
|
|
3
|
-
|
|
4
|
-
describe("MergeOutputStrategy", () => {
|
|
5
|
-
test("should merge array data from multiple sources", async () => {
|
|
6
|
-
const mockData = new ReadableStream({
|
|
7
|
-
start(controller) {
|
|
8
|
-
controller.enqueue({"database1": [1, 2, 3, 4, 5]});
|
|
9
|
-
controller.enqueue({"database2": [6, 7, 8, 9, 10]});
|
|
10
|
-
controller.close();
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const strategy = MergeOutputStrategy<number[]>();
|
|
15
|
-
const result = await strategy(mockData);
|
|
16
|
-
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test("should handle empty arrays", async () => {
|
|
20
|
-
const mockData = new ReadableStream({
|
|
21
|
-
start(controller) {
|
|
22
|
-
controller.enqueue({"database1": []});
|
|
23
|
-
controller.enqueue({"database2": []});
|
|
24
|
-
controller.close();
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const strategy = MergeOutputStrategy<number[]>();
|
|
29
|
-
const result = await strategy(mockData);
|
|
30
|
-
|
|
31
|
-
expect(result).toEqual([]);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("should merge object arrays", async () => {
|
|
35
|
-
const mockData = new ReadableStream({
|
|
36
|
-
start(controller) {
|
|
37
|
-
controller.enqueue({"database1": [{ id: 1, name: "Alice" }]});
|
|
38
|
-
controller.enqueue({"database2": [{ id: 2, name: "Bob" }]});
|
|
39
|
-
controller.close();
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const strategy = MergeOutputStrategy<{ id: number; name: string }[]>();
|
|
44
|
-
const result = await strategy(mockData);
|
|
45
|
-
|
|
46
|
-
expect(result).toEqual([
|
|
47
|
-
{ id: 1, name: "Alice" },
|
|
48
|
-
{ id: 2, name: "Bob" }
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { OutputStrategy } from '../types';
|
|
2
|
-
|
|
3
|
-
export const MergeOutputStrategy = <TData extends Array<unknown>, TMerged = TData extends Array<infer TItem> ? TItem : TData>(): OutputStrategy<TData, TMerged[]> => async (result) => {
|
|
4
|
-
const data: TMerged[] = [];
|
|
5
|
-
for await (const item of result) {
|
|
6
|
-
Object.values(item).forEach((value) => {
|
|
7
|
-
data.push(...value as TMerged[]);
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
return data;
|
|
11
|
-
};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { XlsOutputStrategy } from "./index";
|
|
3
|
-
import * as XLSX from "xlsx";
|
|
4
|
-
|
|
5
|
-
describe("XlsOutputStrategy", () => {
|
|
6
|
-
test("should generate XLS file with separate sheets", async () => {
|
|
7
|
-
const mockData = new ReadableStream({
|
|
8
|
-
start(controller) {
|
|
9
|
-
controller.enqueue({
|
|
10
|
-
"database1": [{ id: 1, name: "Alice", age: 30 }, { id: 2, name: "Bob", age: 25 }],
|
|
11
|
-
"database2": [{ id: 3, name: "Charlie", age: 35 }]
|
|
12
|
-
});
|
|
13
|
-
controller.close();
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const strategy = XlsOutputStrategy(false);
|
|
18
|
-
const filename = await strategy(mockData);
|
|
19
|
-
|
|
20
|
-
expect(await Bun.file(filename).exists()).toBe(true);
|
|
21
|
-
|
|
22
|
-
// Clean up
|
|
23
|
-
await Bun.file(filename).delete();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("should generate XLS file with combined sheet when unique=true", async () => {
|
|
27
|
-
const mockData = new ReadableStream({
|
|
28
|
-
start(controller) {
|
|
29
|
-
controller.enqueue({
|
|
30
|
-
"database1": [{ id: 1, name: "Alice", age: 30 }],
|
|
31
|
-
"database2": [{ id: 2, name: "Bob", age: 25 }]
|
|
32
|
-
});
|
|
33
|
-
controller.close();
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const strategy = XlsOutputStrategy(true);
|
|
38
|
-
const filename = await strategy(mockData);
|
|
39
|
-
|
|
40
|
-
expect(await Bun.file(filename).exists()).toBe(true);
|
|
41
|
-
|
|
42
|
-
// Clean up
|
|
43
|
-
await Bun.file(filename).delete();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("should create empty sheet when no data provided", async () => {
|
|
47
|
-
const mockData = new ReadableStream({
|
|
48
|
-
start(controller) {
|
|
49
|
-
controller.enqueue({});
|
|
50
|
-
controller.close();
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const strategy = XlsOutputStrategy(false);
|
|
55
|
-
const filename = await strategy(mockData);
|
|
56
|
-
|
|
57
|
-
expect(await Bun.file(filename).exists()).toBe(true);
|
|
58
|
-
|
|
59
|
-
// Clean up
|
|
60
|
-
await Bun.file(filename).delete();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("Should group rows by database when unique=true", async () => {
|
|
64
|
-
const mockData = new ReadableStream({
|
|
65
|
-
start(controller) {
|
|
66
|
-
controller.enqueue({
|
|
67
|
-
"database1": [{ id: 1, name: "Alice", age: 30 }, { id: 2, name: "Bob", age: 25 }],
|
|
68
|
-
"database2": [{ id: 1, name: "Charlie", age: 35 }, { id: 2, name: "Dave", age: 40 }]
|
|
69
|
-
});
|
|
70
|
-
controller.close();
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const strategy = XlsOutputStrategy(true);
|
|
75
|
-
const filename = await strategy(mockData);
|
|
76
|
-
|
|
77
|
-
const file = Bun.file(filename);
|
|
78
|
-
|
|
79
|
-
expect(await file.exists()).toBe(true);
|
|
80
|
-
|
|
81
|
-
const workbook = XLSX.read(await file.arrayBuffer(), { cellStyles: true });
|
|
82
|
-
const worksheet = workbook.Sheets["Combined"];
|
|
83
|
-
expect(worksheet).toBeDefined();
|
|
84
|
-
|
|
85
|
-
const rows = worksheet!['!rows']!;
|
|
86
|
-
// Check if row grouping is set correctly
|
|
87
|
-
expect(rows[1]!.level).toEqual(1); // Summary row for database1
|
|
88
|
-
expect(rows[3]!.level).toEqual(1); // Summary row for database2
|
|
89
|
-
|
|
90
|
-
// Clean up
|
|
91
|
-
await Bun.file(filename).delete();
|
|
92
|
-
});
|
|
93
|
-
});
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import * as XLSX from 'xlsx';
|
|
2
|
-
import type { OutputStrategy } from '../types';
|
|
3
|
-
|
|
4
|
-
async function processSeparateSheets<TData>(
|
|
5
|
-
result: ReadableStream<Record<string, TData>>,
|
|
6
|
-
workbook: XLSX.WorkBook
|
|
7
|
-
): Promise<boolean> {
|
|
8
|
-
let hasData = false;
|
|
9
|
-
|
|
10
|
-
for await (const dbResult of result) {
|
|
11
|
-
for (const [database, data] of Object.entries(dbResult)) {
|
|
12
|
-
let sheetData: unknown[] = [];
|
|
13
|
-
if (Array.isArray(data)) {
|
|
14
|
-
sheetData = data;
|
|
15
|
-
} else if (data && typeof data === 'object') {
|
|
16
|
-
sheetData = [data];
|
|
17
|
-
} else {
|
|
18
|
-
sheetData = [{ value: data }];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (sheetData.length > 0) {
|
|
22
|
-
const worksheet = XLSX.utils.json_to_sheet(sheetData);
|
|
23
|
-
XLSX.utils.book_append_sheet(workbook, worksheet, database);
|
|
24
|
-
hasData = true;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return hasData;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function processCombinedSheet<TData>(
|
|
33
|
-
result: ReadableStream<Record<string, TData>>,
|
|
34
|
-
workbook: XLSX.WorkBook
|
|
35
|
-
): Promise<boolean> {
|
|
36
|
-
const allData: unknown[] = [];
|
|
37
|
-
const databaseGroups: { [database: string]: { startRow: number, endRow: number } } = {};
|
|
38
|
-
let currentRow = 1; // Start from row 1 (header is row 0)
|
|
39
|
-
|
|
40
|
-
for await (const dbResult of result) {
|
|
41
|
-
for (const [database, data] of Object.entries(dbResult)) {
|
|
42
|
-
let sheetData: unknown[] = [];
|
|
43
|
-
if (Array.isArray(data)) {
|
|
44
|
-
sheetData = data.map(item => ({ database, ...item }));
|
|
45
|
-
} else if (data && typeof data === 'object') {
|
|
46
|
-
sheetData = [{ database, ...data }];
|
|
47
|
-
} else {
|
|
48
|
-
sheetData = [{ database, value: data }];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Track the start row for this database group
|
|
52
|
-
databaseGroups[database] ??= { startRow: currentRow, endRow: currentRow };
|
|
53
|
-
|
|
54
|
-
// Update the end row for this database group
|
|
55
|
-
databaseGroups[database].endRow = currentRow + sheetData.length - 1;
|
|
56
|
-
currentRow += sheetData.length;
|
|
57
|
-
|
|
58
|
-
allData.push(...sheetData);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (allData.length === 0) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const worksheet = XLSX.utils.json_to_sheet(allData);
|
|
67
|
-
|
|
68
|
-
worksheet['!rows'] ??= [];
|
|
69
|
-
|
|
70
|
-
// Set row grouping for each database
|
|
71
|
-
for (const [_, { startRow, endRow }] of Object.entries(databaseGroups)) {
|
|
72
|
-
// Set level 1 for all rows in this database group
|
|
73
|
-
for (let i = startRow; i <= endRow; i++) {
|
|
74
|
-
worksheet['!rows'][i] ??= {hpx: 20};
|
|
75
|
-
worksheet['!rows'][i]!.level = i === endRow ? 0 : 1;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
XLSX.utils.book_append_sheet(workbook, worksheet, "Combined");
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export const XlsOutputStrategy = <TData>(unique: boolean = false): OutputStrategy<TData, string> => async (result) => {
|
|
84
|
-
const workbook = XLSX.utils.book_new();
|
|
85
|
-
|
|
86
|
-
if (unique) {
|
|
87
|
-
const hasData = await processCombinedSheet(result, workbook);
|
|
88
|
-
if (!hasData) {
|
|
89
|
-
const emptyWorksheet = XLSX.utils.json_to_sheet([{ message: "No data available" }]);
|
|
90
|
-
XLSX.utils.book_append_sheet(workbook, emptyWorksheet, "Empty");
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
const hasData = await processSeparateSheets(result, workbook);
|
|
94
|
-
if (!hasData) {
|
|
95
|
-
const emptyWorksheet = XLSX.utils.json_to_sheet([{ message: "No data available" }]);
|
|
96
|
-
XLSX.utils.book_append_sheet(workbook, emptyWorksheet, "Empty");
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
let filename = process.argv[1]?.replace(/\.(?:js|ts)/, '');
|
|
101
|
-
filename = `${filename}-${Date.now()}.xlsx`;
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx', cellStyles: true });
|
|
105
|
-
await Bun.write(filename, buffer);
|
|
106
|
-
return filename;
|
|
107
|
-
} catch (error) {
|
|
108
|
-
console.error('Error writing Excel file');
|
|
109
|
-
throw error;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type OutputStrategy<TReturn, TOutput = void> = (data: ReadableStream<Record<string, TReturn>>) => Promise<TOutput>;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { Transaction } from "mssql";
|
|
2
|
-
import { Output } from "../output"
|
|
3
|
-
import type { DatabaseConnection } from "../connect/types";
|
|
4
|
-
import type { RetrieveChain } from "./types";
|
|
5
|
-
import { Presets, SingleBar } from "cli-progress";
|
|
6
|
-
import { CleanErrors, type ErrorType } from "../../utils/append-error";
|
|
7
|
-
import { TransactionRunner } from "../shared/transaction-runner";
|
|
8
|
-
|
|
9
|
-
export const Retrieve = <TParam>(
|
|
10
|
-
connections$: (databases: string[]) => Generator<DatabaseConnection[]>,
|
|
11
|
-
databases$: Promise<string[]>,
|
|
12
|
-
input: TParam
|
|
13
|
-
) => {
|
|
14
|
-
return <TReturn>(fn: (transaction: Transaction, database: string, params: TParam) => Promise<TReturn>): RetrieveChain<TReturn> => {
|
|
15
|
-
const { readable, writable } = new TransformStream<Record<string, TReturn>, Record<string, TReturn>>();
|
|
16
|
-
const writer = writable.getWriter();
|
|
17
|
-
|
|
18
|
-
const singlerBar = new SingleBar({
|
|
19
|
-
format: `{bar} {percentage}% | {value}/{total} | {database}`
|
|
20
|
-
}, Presets.shades_classic);
|
|
21
|
-
|
|
22
|
-
const executeFn = (dc: DatabaseConnection) => TransactionRunner()({
|
|
23
|
-
connection: dc,
|
|
24
|
-
input,
|
|
25
|
-
fn,
|
|
26
|
-
onSuccess: async (result) => {
|
|
27
|
-
await writer.write({ [dc.database]: result });
|
|
28
|
-
},
|
|
29
|
-
onError: async (error) => {
|
|
30
|
-
await writable.abort(error as ErrorType);
|
|
31
|
-
},
|
|
32
|
-
singleBar: singlerBar,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Process all connections and close the stream when done
|
|
36
|
-
(async () => {
|
|
37
|
-
await CleanErrors();
|
|
38
|
-
const databases = await databases$;
|
|
39
|
-
|
|
40
|
-
if (Bun.env.NODE_ENV !== 'test') {
|
|
41
|
-
singlerBar.start(databases.length, 0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for await (const connectionBatch of connections$(databases)) {
|
|
45
|
-
const executions = connectionBatch.map(executeFn);
|
|
46
|
-
await Promise.allSettled(executions);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (Bun.env.NODE_ENV !== 'test') {
|
|
50
|
-
singlerBar.stop();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Close the writer when all connections are processed
|
|
54
|
-
await writer.close();
|
|
55
|
-
})();
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
Output: Output(readable)
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { config } from "mssql";
|
|
2
|
-
import type { AuthStrategy, AuthenticationChain } from "../auth/types";
|
|
3
|
-
|
|
4
|
-
export type ServerConfig = Omit<config, "authentication" | "user" | "password">;
|
|
5
|
-
|
|
6
|
-
export type ServerChain = {
|
|
7
|
-
Auth(strategy: AuthStrategy): AuthenticationChain;
|
|
8
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { Transaction } from 'mssql';
|
|
2
|
-
import type { DatabaseConnection } from '../connect/types';
|
|
3
|
-
import { SingleBar } from 'cli-progress';
|
|
4
|
-
import { AppendError, type ErrorType } from '../../utils/append-error';
|
|
5
|
-
import { LoadEnv } from '../../utils/load-env';
|
|
6
|
-
|
|
7
|
-
export interface TransactionRunnerOptions<TParam, TReturn> {
|
|
8
|
-
connection: DatabaseConnection;
|
|
9
|
-
input: TParam;
|
|
10
|
-
fn: (transaction: Transaction, database: string, params: TParam) => Promise<TReturn>;
|
|
11
|
-
onSuccess?: (result: TReturn) => Promise<void> | void;
|
|
12
|
-
onError?: (error: any) => Promise<void> | void;
|
|
13
|
-
singleBar?: SingleBar;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const TransactionRunner = () => {
|
|
17
|
-
const ENV = LoadEnv();
|
|
18
|
-
const limit = ENV.MAX_ERRORS;
|
|
19
|
-
let errorsCount = 0;
|
|
20
|
-
|
|
21
|
-
return async <TParam, TReturn>({
|
|
22
|
-
connection: dc,
|
|
23
|
-
input,
|
|
24
|
-
fn,
|
|
25
|
-
onSuccess,
|
|
26
|
-
onError,
|
|
27
|
-
singleBar,
|
|
28
|
-
}: TransactionRunnerOptions<TParam, TReturn>): Promise<void> => {
|
|
29
|
-
return dc.connection
|
|
30
|
-
.then(opened => opened.transaction())
|
|
31
|
-
.then(tran => tran.begin()
|
|
32
|
-
.then(() => fn(tran, dc.database, input))
|
|
33
|
-
.then(async (result) => {
|
|
34
|
-
if (onSuccess) {
|
|
35
|
-
await onSuccess(result);
|
|
36
|
-
}
|
|
37
|
-
return result;
|
|
38
|
-
})
|
|
39
|
-
.then(() => tran.commit())
|
|
40
|
-
.then(() => {
|
|
41
|
-
if (singleBar && Bun.env.NODE_ENV !== 'test') {
|
|
42
|
-
singleBar.increment(1, { database: dc.database });
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
.catch(error => tran.rollback().then(() => { throw error }))
|
|
46
|
-
)
|
|
47
|
-
.catch(async error => {
|
|
48
|
-
AppendError(dc.database, error as ErrorType);
|
|
49
|
-
if (++errorsCount > limit) {
|
|
50
|
-
if (onError) {
|
|
51
|
-
await onError(error);
|
|
52
|
-
}
|
|
53
|
-
console.error('Max errors reached, exiting...');
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
};
|
package/src/pipes/types.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./auth/types"
|
package/src/pool/index.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { type config, ConnectionPool } from 'mssql';
|
|
2
|
-
export type Pool = {
|
|
3
|
-
connect: (partialConfig: Partial<config>) => Promise<ConnectionPool>;
|
|
4
|
-
closeAll: () => Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function Pool(poolConfig: config): Pool {
|
|
8
|
-
const POOL: Record<string, Promise<ConnectionPool>> = {};
|
|
9
|
-
|
|
10
|
-
return {
|
|
11
|
-
connect: (partialConfig: Partial<config>) => {
|
|
12
|
-
const config = { ...poolConfig, ...partialConfig };
|
|
13
|
-
const database = config.database;
|
|
14
|
-
|
|
15
|
-
if (!database) {
|
|
16
|
-
throw new Error('Database name is required');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (!(database in POOL)) {
|
|
20
|
-
const pool = new ConnectionPool(config);
|
|
21
|
-
const close = pool.close.bind(pool);
|
|
22
|
-
|
|
23
|
-
pool.close = async () => {
|
|
24
|
-
delete POOL[database];
|
|
25
|
-
return await close();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
pool.on('error', err => {
|
|
29
|
-
delete POOL[database];
|
|
30
|
-
throw err;
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
POOL[database] = pool
|
|
34
|
-
.connect()
|
|
35
|
-
.catch(err => {
|
|
36
|
-
delete POOL[database];
|
|
37
|
-
throw err;
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return POOL[database]!;
|
|
42
|
-
},
|
|
43
|
-
closeAll: async () => {
|
|
44
|
-
const closes = Object.values(POOL).map(pool => pool.then(p => p.close()));
|
|
45
|
-
await Promise.all(closes);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, afterEach, beforeAll } from "bun:test";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { AppendError, CleanErrors, GetErrors, SetLogPath } from "./append-error";
|
|
4
|
-
|
|
5
|
-
describe("Append Error Utils", () => {
|
|
6
|
-
let errorLogPath: string;
|
|
7
|
-
|
|
8
|
-
beforeAll(() => {
|
|
9
|
-
// Calculate the error log path based on test file name
|
|
10
|
-
errorLogPath = `${import.meta.path.replace(/\.(?:js|ts)/, '')}-last-errors.json`;
|
|
11
|
-
SetLogPath(errorLogPath);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterEach(async () => {
|
|
15
|
-
await CleanErrors();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe("Error logging functionality", () => {
|
|
19
|
-
test("Should create error log file when AppendError is called", async () => {
|
|
20
|
-
const testError = new Error("Test error");
|
|
21
|
-
AppendError("TestDB1", testError);
|
|
22
|
-
|
|
23
|
-
expect(existsSync(errorLogPath)).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("Should store error details correctly in log file", async () => {
|
|
27
|
-
const testError = new Error("Test error message");
|
|
28
|
-
testError.stack = "Test stack trace";
|
|
29
|
-
|
|
30
|
-
AppendError("TestDB1", testError);
|
|
31
|
-
|
|
32
|
-
const logs = await GetErrors();
|
|
33
|
-
|
|
34
|
-
expect(logs.length).toBe(1);
|
|
35
|
-
expect(logs[0].database).toBe("TestDB1");
|
|
36
|
-
expect(logs[0].error.message).toBe("Test error message");
|
|
37
|
-
expect(logs[0].error.stack).toBe("Test stack trace");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("Should accumulate errors for multiple databases", async () => {
|
|
41
|
-
const error1 = new Error("Error in DB1");
|
|
42
|
-
const error2 = new Error("Error in DB2");
|
|
43
|
-
|
|
44
|
-
AppendError("TestDB1", error1);
|
|
45
|
-
AppendError("TestDB2", error2);
|
|
46
|
-
|
|
47
|
-
const logs = await GetErrors();
|
|
48
|
-
expect(logs.length).toBe(2);
|
|
49
|
-
expect(logs[0].database).toBe("TestDB1");
|
|
50
|
-
expect(logs[0].error.message).toBe("Error in DB1");
|
|
51
|
-
expect(logs[1].database).toBe("TestDB2");
|
|
52
|
-
expect(logs[1].error.message).toBe("Error in DB2");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("Should overwrite existing error for same database", async () => {
|
|
56
|
-
const initialError = new Error("Initial error");
|
|
57
|
-
const updatedError = new Error("Updated error");
|
|
58
|
-
|
|
59
|
-
AppendError("TestDB1", initialError);
|
|
60
|
-
AppendError("TestDB1", updatedError);
|
|
61
|
-
|
|
62
|
-
const logContent = await GetErrors();
|
|
63
|
-
expect(logContent.length).toBe(2);
|
|
64
|
-
expect(logContent[0].database).toBe("TestDB1");
|
|
65
|
-
expect(logContent[0].error.message).toBe("Initial error");
|
|
66
|
-
expect(logContent[1].database).toBe("TestDB1");
|
|
67
|
-
expect(logContent[1].error.message).toBe("Updated error");
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe("Error log persistence and cleanup", () => {
|
|
72
|
-
test("Should persist error log across operations", async () => {
|
|
73
|
-
const testError = new Error("Persistent error");
|
|
74
|
-
AppendError("TestDB1", testError);
|
|
75
|
-
|
|
76
|
-
const logContent = await GetErrors();
|
|
77
|
-
expect(logContent.length).toBe(1);
|
|
78
|
-
expect(logContent[0].database).toBe("TestDB1");
|
|
79
|
-
expect(logContent[0].error.message).toBe("Persistent error");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("Should handle concurrent error logging", async () => {
|
|
83
|
-
const errors: Error[] = [
|
|
84
|
-
new Error("Error 1"),
|
|
85
|
-
new Error("Error 2"),
|
|
86
|
-
new Error("Error 3")
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
// Log errors concurrently
|
|
90
|
-
await Promise.all([
|
|
91
|
-
AppendError("TestDB1", errors[0]!),
|
|
92
|
-
AppendError("TestDB2", errors[1]!),
|
|
93
|
-
AppendError("TestDB3", errors[2]!)
|
|
94
|
-
]);
|
|
95
|
-
|
|
96
|
-
const logContent = await GetErrors();
|
|
97
|
-
expect(logContent.length).toBeGreaterThanOrEqual(1);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
});
|