squilo 0.2.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squilo",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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/douglasdasilvasousa/Squilo.git"
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(), `${hash}.json`);
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 { AppendError, CleanErrors, type ErrorType } from '../../utils/append-error';
5
- import { LoadEnv } from '../../utils/load-env';
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 = async (dc: DatabaseConnection): Promise<void> => {
24
- const opened = await dc.connection;
25
- const transaction = opened.transaction()
26
- try {
27
- await transaction.begin();
28
- await fn(transaction, dc.database, input);
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 { LoadEnv } from "../../utils/load-env";
7
- import { AppendError, CleanErrors, type ErrorType } from "../../utils/append-error";
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 = async (dc: DatabaseConnection) => {
26
- const opened = await dc.connection;
27
- const transaction = opened.transaction();
28
- try {
29
- await transaction.begin();
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
- if (Bun.env.NODE_ENV !== 'test') {
36
- singlerBar.increment(1, { database: dc.database });
37
- }
38
- } catch (error) {
39
- await transaction.rollback();
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 = (maxErrors?: number) => {
17
+ const ENV = LoadEnv();
18
+ const limit = maxErrors ?? 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
- throw new Error(err.message);
29
+ delete POOL[database];
30
+ throw err;
30
31
  });
31
-
32
- POOL[database] = pool.connect();
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,73 @@
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
- const BASE_FILENAME = process.argv[1]?.replace(/\.(?:js|ts)/, '');
6
- const ERROR_LOG_PATH = `${BASE_FILENAME}-last-errors.json`;
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 (await Bun.file(ERROR_LOG_PATH).exists()) {
10
- await Bun.file(ERROR_LOG_PATH).delete()
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
- export const AppendError = async (database: string, error: ErrorType) => {
15
- const errorFile = Bun.file(ERROR_LOG_PATH);
16
- const errorContent = await errorFile.exists()
17
- ? await errorFile.json()
18
- : {};
19
-
20
- // Create a serializable error object
21
- const serializableError = {
22
- name: error.name,
23
- message: error.message,
24
- stack: error.stack,
25
- code: (error as any).code || undefined,
26
- number: (error as any).number || undefined,
27
- state: (error as any).state || undefined,
28
- class: (error as any).class || undefined,
29
- serverName: (error as any).serverName || undefined,
30
- procName: (error as any).procName || undefined,
31
- lineNumber: (error as any).lineNumber || undefined
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
- // Update error content with new error for database
35
- const updatedContent = {
36
- ...errorContent,
37
- [database]: serializableError
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
- // Write updated error content to file
41
- await Bun.write(errorFile, JSON.stringify(updatedContent, null, 2));
72
+ writer.write(JSON.stringify(content) + '\n');
42
73
  }