squilo 0.2.0 → 0.2.2
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/package.json +2 -2
- package/src/pipes/auth/strategies/msal.ts +1 -1
- package/src/pipes/execute/index.ts +8 -27
- package/src/pipes/retrieve/index.ts +13 -28
- package/src/pipes/shared/transaction-runner.ts +58 -0
- package/src/pool/index.ts +16 -10
- package/src/utils/append-error.spec.ts +100 -0
- package/src/utils/append-error.ts +62 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squilo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/
|
|
11
|
+
"url": "git+https://github.com/dodevs/Squilo.git"
|
|
12
12
|
},
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Douglas da Silva Sousa",
|
|
@@ -18,7 +18,7 @@ import { cwd } from "process";
|
|
|
18
18
|
const SCOPES = ["https://database.windows.net//.default"];
|
|
19
19
|
|
|
20
20
|
const cacheAccess = (hash: string) => {
|
|
21
|
-
const cacheFilePath = path.join(cwd(),
|
|
21
|
+
const cacheFilePath = path.join(cwd(), `./.active-directory-cache/${hash}.json`);
|
|
22
22
|
|
|
23
23
|
const before = async (cacheContext: TokenCacheContext) => {
|
|
24
24
|
try {
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { Transaction } from 'mssql';
|
|
2
2
|
import type { DatabaseConnection } from "../connect/types";
|
|
3
3
|
import { Presets, SingleBar } from 'cli-progress';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const ENV = LoadEnv();
|
|
8
|
-
let ERRORS_COUNT = 0;
|
|
9
|
-
|
|
4
|
+
import { CleanErrors } from '../../utils/append-error';
|
|
5
|
+
import { TransactionRunner } from '../shared/transaction-runner';
|
|
10
6
|
|
|
11
7
|
export const Execute = <TParam>(
|
|
12
8
|
connections$: (databases: string[]) => Generator<DatabaseConnection[]>,
|
|
@@ -20,27 +16,12 @@ export const Execute = <TParam>(
|
|
|
20
16
|
format: `{bar} {percentage}% | {value}/{total} | {database}`
|
|
21
17
|
}, Presets.shades_classic);
|
|
22
18
|
|
|
23
|
-
const executeFn =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
await transaction.commit();
|
|
30
|
-
if (Bun.env.NODE_ENV !== 'test') {
|
|
31
|
-
singlerBar.increment(1, { database: dc.database });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
await transaction.rollback();
|
|
36
|
-
await AppendError(dc.database, error as ErrorType);
|
|
37
|
-
|
|
38
|
-
if (++ERRORS_COUNT > ENV.MAX_ERRORS) {
|
|
39
|
-
console.error('Max errors reached, exiting...');
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
};
|
|
19
|
+
const executeFn = (dc: DatabaseConnection) => TransactionRunner()({
|
|
20
|
+
connection: dc,
|
|
21
|
+
input,
|
|
22
|
+
fn,
|
|
23
|
+
singleBar: singlerBar,
|
|
24
|
+
});
|
|
44
25
|
|
|
45
26
|
await CleanErrors();
|
|
46
27
|
const databases = await databases$;
|
|
@@ -3,11 +3,8 @@ import { Output } from "../output"
|
|
|
3
3
|
import type { DatabaseConnection } from "../connect/types";
|
|
4
4
|
import type { RetrieveChain } from "./types";
|
|
5
5
|
import { Presets, SingleBar } from "cli-progress";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const ENV = LoadEnv();
|
|
10
|
-
let ERRORS_COUNT = 0;
|
|
6
|
+
import { CleanErrors, type ErrorType } from "../../utils/append-error";
|
|
7
|
+
import { TransactionRunner } from "../shared/transaction-runner";
|
|
11
8
|
|
|
12
9
|
export const Retrieve = <TParam>(
|
|
13
10
|
connections$: (databases: string[]) => Generator<DatabaseConnection[]>,
|
|
@@ -22,30 +19,18 @@ export const Retrieve = <TParam>(
|
|
|
22
19
|
format: `{bar} {percentage}% | {value}/{total} | {database}`
|
|
23
20
|
}, Presets.shades_classic);
|
|
24
21
|
|
|
25
|
-
const executeFn =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const result = await fn(transaction, dc.database, input);
|
|
31
|
-
transaction.commit();
|
|
32
|
-
|
|
22
|
+
const executeFn = (dc: DatabaseConnection) => TransactionRunner()({
|
|
23
|
+
connection: dc,
|
|
24
|
+
input,
|
|
25
|
+
fn,
|
|
26
|
+
onSuccess: async (result) => {
|
|
33
27
|
await writer.write({ [dc.database]: result });
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await AppendError(dc.database, error as ErrorType);
|
|
41
|
-
|
|
42
|
-
if (++ERRORS_COUNT > ENV.MAX_ERRORS) {
|
|
43
|
-
await writable.abort(error as ErrorType);
|
|
44
|
-
console.error('Max errors reached, exiting...');
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
};
|
|
28
|
+
},
|
|
29
|
+
onError: async (error) => {
|
|
30
|
+
await writable.abort(error as ErrorType);
|
|
31
|
+
},
|
|
32
|
+
singleBar: singlerBar,
|
|
33
|
+
});
|
|
49
34
|
|
|
50
35
|
// Process all connections and close the stream when done
|
|
51
36
|
(async () => {
|
|
@@ -0,0 +1,58 @@
|
|
|
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/pool/index.ts
CHANGED
|
@@ -11,33 +11,39 @@ export function Pool(poolConfig: config): Pool {
|
|
|
11
11
|
connect: (partialConfig: Partial<config>) => {
|
|
12
12
|
const config = { ...poolConfig, ...partialConfig };
|
|
13
13
|
const database = config.database;
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
if (!database) {
|
|
16
16
|
throw new Error('Database name is required');
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
if(!(database in POOL)) {
|
|
18
|
+
|
|
19
|
+
if (!(database in POOL)) {
|
|
20
20
|
const pool = new ConnectionPool(config);
|
|
21
21
|
const close = pool.close.bind(pool);
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
pool.close = async () => {
|
|
24
24
|
delete POOL[database];
|
|
25
25
|
return await close();
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
pool.on('error', err => {
|
|
29
|
-
|
|
29
|
+
delete POOL[database];
|
|
30
|
+
throw err;
|
|
30
31
|
});
|
|
31
|
-
|
|
32
|
-
POOL[database] = pool
|
|
32
|
+
|
|
33
|
+
POOL[database] = pool
|
|
34
|
+
.connect()
|
|
35
|
+
.catch(err => {
|
|
36
|
+
delete POOL[database];
|
|
37
|
+
throw err;
|
|
38
|
+
});
|
|
33
39
|
}
|
|
34
|
-
|
|
40
|
+
|
|
35
41
|
return POOL[database]!;
|
|
36
42
|
},
|
|
37
43
|
closeAll: async () => {
|
|
38
44
|
const closes = Object.values(POOL).map(pool => pool.then(p => p.close()));
|
|
39
45
|
await Promise.all(closes);
|
|
40
46
|
}
|
|
41
|
-
|
|
47
|
+
|
|
42
48
|
}
|
|
43
49
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
});
|
|
@@ -1,42 +1,74 @@
|
|
|
1
|
+
import type { FileSink } from "bun";
|
|
1
2
|
import { ConnectionError, PreparedStatementError, RequestError, TransactionError } from "mssql";
|
|
2
3
|
|
|
3
4
|
export type ErrorType = Error | ConnectionError | TransactionError | RequestError | PreparedStatementError;
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
let customLogPath: string | null = null;
|
|
7
|
+
|
|
8
|
+
export const SetLogPath = (path: string) => {
|
|
9
|
+
customLogPath = path;
|
|
10
|
+
if (logWriter) {
|
|
11
|
+
logWriter.end();
|
|
12
|
+
logWriter = null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const getLogPath = () => {
|
|
17
|
+
if (customLogPath) return customLogPath;
|
|
18
|
+
const base = process.argv[1]?.replace(/\.(?:js|ts)/, '');
|
|
19
|
+
return `${base}-last-errors.json`;
|
|
20
|
+
}
|
|
7
21
|
|
|
8
22
|
export const CleanErrors = async () => {
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
23
|
+
if (logWriter) {
|
|
24
|
+
logWriter.end();
|
|
25
|
+
logWriter = null;
|
|
26
|
+
}
|
|
27
|
+
const path = getLogPath();
|
|
28
|
+
if (await Bun.file(path).exists()) {
|
|
29
|
+
await Bun.file(path).delete()
|
|
30
|
+
}
|
|
12
31
|
}
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
let logWriter: FileSink | null = null;
|
|
34
|
+
|
|
35
|
+
const getWriter = () => {
|
|
36
|
+
if (!logWriter) {
|
|
37
|
+
logWriter = Bun.file(getLogPath()).writer();
|
|
38
|
+
}
|
|
39
|
+
return logWriter;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const GetErrors = async () => {
|
|
43
|
+
const writer = getWriter();
|
|
44
|
+
writer.end();
|
|
45
|
+
logWriter = null;
|
|
46
|
+
|
|
47
|
+
return Bun.file(getLogPath())
|
|
48
|
+
.text()
|
|
49
|
+
.then(text => text.split('\n').filter(line => line !== ''))
|
|
50
|
+
.then(lines => lines.map(line => JSON.parse(line)));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const AppendError = (database: string, error: ErrorType) => {
|
|
54
|
+
const writer = getWriter();
|
|
33
55
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
const content = {
|
|
57
|
+
database,
|
|
58
|
+
error: {
|
|
59
|
+
name: error.name,
|
|
60
|
+
message: error.message,
|
|
61
|
+
stack: error.stack,
|
|
62
|
+
code: (error as any).code || undefined,
|
|
63
|
+
number: (error as any).number || undefined,
|
|
64
|
+
state: (error as any).state || undefined,
|
|
65
|
+
class: (error as any).class || undefined,
|
|
66
|
+
serverName: (error as any).serverName || undefined,
|
|
67
|
+
procName: (error as any).procName || undefined,
|
|
68
|
+
lineNumber: (error as any).lineNumber || undefined
|
|
69
|
+
}
|
|
38
70
|
};
|
|
39
71
|
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
writer.write(JSON.stringify(content) + '\n');
|
|
73
|
+
writer.flush();
|
|
42
74
|
}
|