squilo 0.1.2 → 0.2.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.
Files changed (40) hide show
  1. package/README.md +265 -36
  2. package/package.json +17 -12
  3. package/src/.DS_Store +0 -0
  4. package/src/index.ts +3 -1
  5. package/src/pipes/auth/index.ts +2 -12
  6. package/src/pipes/auth/strategies/msal.ts +14 -7
  7. package/src/pipes/auth/strategies/userAndPassword.ts +1 -1
  8. package/src/pipes/auth/types.ts +12 -0
  9. package/src/pipes/connect/index.ts +25 -42
  10. package/src/pipes/connect/types.ts +25 -0
  11. package/src/pipes/execute/index.ts +36 -4
  12. package/src/pipes/index.ts +1 -1
  13. package/src/pipes/input/index.ts +6 -11
  14. package/src/pipes/input/types.ts +7 -0
  15. package/src/pipes/output/index.ts +4 -3
  16. package/src/pipes/output/strategies/console.ts +1 -1
  17. package/src/pipes/output/strategies/json.spec.ts +16 -6
  18. package/src/pipes/output/strategies/json.ts +14 -3
  19. package/src/pipes/output/strategies/merge.ts +1 -1
  20. package/src/pipes/output/strategies/xls.spec.ts +44 -15
  21. package/src/pipes/output/strategies/xls.ts +42 -10
  22. package/src/pipes/output/types.ts +1 -0
  23. package/src/pipes/retrieve/index.ts +48 -19
  24. package/src/pipes/retrieve/types.ts +5 -0
  25. package/src/pipes/server/index.ts +2 -6
  26. package/src/pipes/server/types.ts +5 -0
  27. package/src/pipes/types.ts +1 -0
  28. package/src/utils/append-error.ts +42 -0
  29. package/src/utils/load-env.ts +18 -0
  30. package/biome.json +0 -34
  31. package/test/connect.spec.ts +0 -119
  32. package/test/container/container.spec.ts +0 -33
  33. package/test/container/container.ts +0 -22
  34. package/test/container/setup/databases.spec.ts +0 -24
  35. package/test/container/setup/databases.ts +0 -77
  36. package/test/container/setup/users.spec.ts +0 -25
  37. package/test/container/setup/users.ts +0 -54
  38. package/test/index.spec.ts +0 -68
  39. package/test/input.spec.ts +0 -64
  40. package/tsconfig.json +0 -28
@@ -1,19 +1,14 @@
1
- import type { Transaction } from "mssql";
2
1
  import { Execute } from "../execute";
3
- import type { DatabaseConnection } from "../connect";
4
- import { Retrieve, type RetrieveChain } from "../retrieve";
2
+ import type { DatabaseConnection } from "../connect/types";
3
+ import { Retrieve } from "../retrieve";
4
+ import type { InputChain } from "./types";
5
5
 
6
- export type InputChain<TParam> = {
7
- Execute(fn: (transaction: Transaction, database: string, params: TParam) => Promise<void>): Promise<void>;
8
- Retrieve<TResult>(fn: (transaction: Transaction, database: string, params: TParam) => Promise<TResult>): RetrieveChain<TResult>;
9
- }
10
-
11
- export const Input = (connections: AsyncGenerator<DatabaseConnection[]>) => {
6
+ export const Input = (connections$: (databases: string[]) => Generator<DatabaseConnection[]>, databases$: Promise<string[]>) => {
12
7
  return <TParam>(fn: () => TParam): InputChain<TParam> => {
13
8
  const params = fn();
14
9
  return {
15
- Execute: Execute(connections, params),
16
- Retrieve: Retrieve(connections, params)
10
+ Execute: Execute(connections$, databases$, params),
11
+ Retrieve: Retrieve(connections$, databases$, params)
17
12
  }
18
13
  }
19
14
  }
@@ -0,0 +1,7 @@
1
+ import type { Transaction } from "mssql";
2
+ import type { RetrieveChain } from "../retrieve/types";
3
+
4
+ export type InputChain<TParam> = {
5
+ Execute(fn: (transaction: Transaction, database: string, params: TParam) => Promise<void>): Promise<void>;
6
+ Retrieve<TResult>(fn: (transaction: Transaction, database: string, params: TParam) => Promise<TResult>): RetrieveChain<TResult>;
7
+ }
@@ -1,4 +1,5 @@
1
- export type OutputStrategy<TReturn, TOutput = void> = (data: ReadableStream<Record<string, TReturn>>) => Promise<TOutput>;
2
-
3
- export const Output = <TReturn, TOutput = void>(data: ReadableStream<Record<string, TReturn>>) => (strategy: OutputStrategy<TReturn, TOutput>): Promise<TOutput> => strategy(data);
1
+ import type { OutputStrategy } from "./types";
4
2
 
3
+ export const Output =
4
+ <TReturn, TOutput = void>(data: ReadableStream<Record<string, TReturn>>) =>
5
+ (strategy: OutputStrategy<TReturn, TOutput>): Promise<TOutput> => strategy(data);
@@ -1,4 +1,4 @@
1
- import type { OutputStrategy } from '../index';
1
+ import type { OutputStrategy } from '../types';
2
2
 
3
3
  export const ConsoleOutputStrategy = <TData>(): OutputStrategy<TData, void> => async (result) => {
4
4
  for await (const data of result) {
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
  import { JsonOutputStrategy } from "./index";
3
3
 
4
4
  describe("JsonOutputStrategy", () => {
5
- test("should format data as JSON with database keys", async () => {
5
+ test("should write JSON data to file and return filename", async () => {
6
6
  const mockData = new ReadableStream({
7
7
  start(controller) {
8
8
  controller.enqueue({ "database1": [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }] });
@@ -14,14 +14,19 @@ describe("JsonOutputStrategy", () => {
14
14
  const strategy = JsonOutputStrategy();
15
15
  const result = await strategy(mockData);
16
16
 
17
- expect(typeof result).toBe("object");
18
- expect(result).toEqual({
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({
19
21
  "database1": [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }],
20
22
  "database2": [{ id: 3, name: "Charlie" }]
21
23
  });
24
+
25
+ // Clean up
26
+ await Bun.$`rm ${result}`;
22
27
  });
23
28
 
24
- test("should handle empty data", async () => {
29
+ test("should handle empty data and create file", async () => {
25
30
  const mockData = new ReadableStream({
26
31
  start(controller) {
27
32
  controller.enqueue({});
@@ -32,7 +37,12 @@ describe("JsonOutputStrategy", () => {
32
37
  const strategy = JsonOutputStrategy();
33
38
  const result = await strategy(mockData);
34
39
 
35
- expect(typeof result).toBe("object");
36
- expect(result).toEqual({});
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}`;
37
47
  });
38
48
  });
@@ -1,9 +1,20 @@
1
- import type { OutputStrategy } from '../index';
1
+ import type { OutputStrategy } from '../types';
2
2
 
3
- export const JsonOutputStrategy = <TData>(): OutputStrategy<TData, Record<string, TData[]>> => async (result) => {
3
+ export const JsonOutputStrategy = <TData>(): OutputStrategy<TData, string> => async (result) => {
4
4
  const data: Record<string, TData[]> = {};
5
+
5
6
  for await (const item of result) {
6
7
  Object.assign(data, item);
7
8
  }
8
- return data;
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;
9
20
  };
@@ -1,4 +1,4 @@
1
- import type { OutputStrategy } from '../index';
1
+ import type { OutputStrategy } from '../types';
2
2
 
3
3
  export const MergeOutputStrategy = <TData extends Array<unknown>, TMerged = TData extends Array<infer TItem> ? TItem : TData>(): OutputStrategy<TData, TMerged[]> => async (result) => {
4
4
  const data: TMerged[] = [];
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { XlsOutputStrategy } from "./index";
3
+ import * as XLSX from "xlsx";
3
4
 
4
5
  describe("XlsOutputStrategy", () => {
5
6
  test("should generate XLS file with separate sheets", async () => {
@@ -13,14 +14,13 @@ describe("XlsOutputStrategy", () => {
13
14
  }
14
15
  });
15
16
 
16
- const testFile = "/tmp/test-separate.xlsx";
17
- const strategy = XlsOutputStrategy(false, testFile);
18
- await strategy(mockData);
17
+ const strategy = XlsOutputStrategy(false);
18
+ const filename = await strategy(mockData);
19
19
 
20
- expect(await Bun.file(testFile).exists()).toBe(true);
20
+ expect(await Bun.file(filename).exists()).toBe(true);
21
21
 
22
22
  // Clean up
23
- await Bun.file(testFile).delete();
23
+ await Bun.file(filename).delete();
24
24
  });
25
25
 
26
26
  test("should generate XLS file with combined sheet when unique=true", async () => {
@@ -34,14 +34,13 @@ describe("XlsOutputStrategy", () => {
34
34
  }
35
35
  });
36
36
 
37
- const testFile = "/tmp/test-combined.xlsx";
38
- const strategy = XlsOutputStrategy(true, testFile);
39
- await strategy(mockData);
37
+ const strategy = XlsOutputStrategy(true);
38
+ const filename = await strategy(mockData);
40
39
 
41
- expect(await Bun.file(testFile).exists()).toBe(true);
40
+ expect(await Bun.file(filename).exists()).toBe(true);
42
41
 
43
42
  // Clean up
44
- await Bun.file(testFile).delete();
43
+ await Bun.file(filename).delete();
45
44
  });
46
45
 
47
46
  test("should create empty sheet when no data provided", async () => {
@@ -52,13 +51,43 @@ describe("XlsOutputStrategy", () => {
52
51
  }
53
52
  });
54
53
 
55
- const testFile = "/tmp/test-empty.xlsx";
56
- const strategy = XlsOutputStrategy(false, testFile);
57
- await strategy(mockData);
54
+ const strategy = XlsOutputStrategy(false);
55
+ const filename = await strategy(mockData);
58
56
 
59
- expect(await Bun.file(testFile).exists()).toBe(true);
57
+ expect(await Bun.file(filename).exists()).toBe(true);
60
58
 
61
59
  // Clean up
62
- await Bun.file(testFile).delete();
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();
63
92
  });
64
93
  });
@@ -1,5 +1,5 @@
1
1
  import * as XLSX from 'xlsx';
2
- import type { OutputStrategy } from '../index';
2
+ import type { OutputStrategy } from '../types';
3
3
 
4
4
  async function processSeparateSheets<TData>(
5
5
  result: ReadableStream<Record<string, TData>>,
@@ -9,7 +9,7 @@ async function processSeparateSheets<TData>(
9
9
 
10
10
  for await (const dbResult of result) {
11
11
  for (const [database, data] of Object.entries(dbResult)) {
12
- let sheetData: any[] = [];
12
+ let sheetData: unknown[] = [];
13
13
  if (Array.isArray(data)) {
14
14
  sheetData = data;
15
15
  } else if (data && typeof data === 'object') {
@@ -33,18 +33,28 @@ async function processCombinedSheet<TData>(
33
33
  result: ReadableStream<Record<string, TData>>,
34
34
  workbook: XLSX.WorkBook
35
35
  ): Promise<boolean> {
36
- let allData: any[] = [];
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)
37
39
 
38
40
  for await (const dbResult of result) {
39
41
  for (const [database, data] of Object.entries(dbResult)) {
40
- let sheetData: any[] = [];
42
+ let sheetData: unknown[] = [];
41
43
  if (Array.isArray(data)) {
42
- sheetData = data.map(item => ({ ...item, database }));
44
+ sheetData = data.map(item => ({ database, ...item }));
43
45
  } else if (data && typeof data === 'object') {
44
- sheetData = [{ ...data, database }];
46
+ sheetData = [{ database, ...data }];
45
47
  } else {
46
- sheetData = [{ value: data, database }];
48
+ sheetData = [{ database, value: data }];
47
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
+
48
58
  allData.push(...sheetData);
49
59
  }
50
60
  }
@@ -54,11 +64,23 @@ async function processCombinedSheet<TData>(
54
64
  }
55
65
 
56
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
+
57
79
  XLSX.utils.book_append_sheet(workbook, worksheet, "Combined");
58
80
  return true;
59
81
  }
60
82
 
61
- export const XlsOutputStrategy = <TData>(unique: boolean = false, filename: string): OutputStrategy<TData, void> => async (result) => {
83
+ export const XlsOutputStrategy = <TData>(unique: boolean = false): OutputStrategy<TData, string> => async (result) => {
62
84
  const workbook = XLSX.utils.book_new();
63
85
 
64
86
  if (unique) {
@@ -74,6 +96,16 @@ export const XlsOutputStrategy = <TData>(unique: boolean = false, filename: stri
74
96
  XLSX.utils.book_append_sheet(workbook, emptyWorksheet, "Empty");
75
97
  }
76
98
  }
77
-
78
- XLSX.writeFile(workbook, filename);
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
+ }
79
111
  };
@@ -0,0 +1 @@
1
+ export type OutputStrategy<TReturn, TOutput = void> = (data: ReadableStream<Record<string, TReturn>>) => Promise<TOutput>;
@@ -1,43 +1,72 @@
1
1
  import type { Transaction } from "mssql";
2
- import { type OutputStrategy, Output } from "../output";
3
- import type { DatabaseConnection } from "../connect";
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 { LoadEnv } from "../../utils/load-env";
7
+ import { AppendError, CleanErrors, type ErrorType } from "../../utils/append-error";
4
8
 
5
- export type RetrieveChain<TReturn> = {
6
- Output<TOutput>(strategy: OutputStrategy<TReturn, TOutput>): Promise<TOutput>;
7
- }
9
+ const ENV = LoadEnv();
10
+ let ERRORS_COUNT = 0;
8
11
 
9
- export const Retrieve = <TParam>(connections$: AsyncGenerator<DatabaseConnection[]>, input: TParam) => {
12
+ export const Retrieve = <TParam>(
13
+ connections$: (databases: string[]) => Generator<DatabaseConnection[]>,
14
+ databases$: Promise<string[]>,
15
+ input: TParam
16
+ ) => {
10
17
  return <TReturn>(fn: (transaction: Transaction, database: string, params: TParam) => Promise<TReturn>): RetrieveChain<TReturn> => {
11
18
  const { readable, writable } = new TransformStream<Record<string, TReturn>, Record<string, TReturn>>();
12
19
  const writer = writable.getWriter();
13
20
 
21
+ const singlerBar = new SingleBar({
22
+ format: `{bar} {percentage}% | {value}/{total} | {database}`
23
+ }, Presets.shades_classic);
24
+
14
25
  const executeFn = async (dc: DatabaseConnection) => {
15
26
  const opened = await dc.connection;
16
27
  const transaction = opened.transaction();
17
28
  try {
18
29
  await transaction.begin();
19
30
  const result = await fn(transaction, dc.database, input);
20
- await writer.write({ [dc.database]: result });
21
31
  transaction.commit();
32
+
33
+ await writer.write({ [dc.database]: result });
34
+
35
+ if (Bun.env.NODE_ENV !== 'test') {
36
+ singlerBar.increment(1, { database: dc.database });
37
+ }
22
38
  } catch (error) {
23
- // TODO: Append client name and error in a structured json file
24
- transaction.rollback();
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
+ }
25
47
  }
26
48
  };
27
49
 
28
50
  // Process all connections and close the stream when done
29
51
  (async () => {
30
- try {
31
- for await (const connectionBatch of connections$) {
32
- const executions = connectionBatch.map(executeFn);
33
- await Promise.all(executions);
34
- }
35
- // Close the writer when all connections are processed
36
- await writer.close();
37
- } catch (error) {
38
- // If there's an error in processing connections, abort the writer
39
- writer.abort(error);
52
+ await CleanErrors();
53
+ const databases = await databases$;
54
+
55
+ if (Bun.env.NODE_ENV !== 'test') {
56
+ singlerBar.start(databases.length, 0);
57
+ }
58
+
59
+ for await (const connectionBatch of connections$(databases)) {
60
+ const executions = connectionBatch.map(executeFn);
61
+ await Promise.allSettled(executions);
62
+ }
63
+
64
+ if (Bun.env.NODE_ENV !== 'test') {
65
+ singlerBar.stop();
40
66
  }
67
+
68
+ // Close the writer when all connections are processed
69
+ await writer.close();
41
70
  })();
42
71
 
43
72
  return {
@@ -0,0 +1,5 @@
1
+ import type { OutputStrategy } from "../output/types";
2
+
3
+ export type RetrieveChain<TReturn> = {
4
+ Output<TOutput>(strategy: OutputStrategy<TReturn, TOutput>): Promise<TOutput>;
5
+ }
@@ -1,9 +1,5 @@
1
- import { type AuthStrategy, type AuthenticationChain, Auth } from "../auth";
2
- import type { ServerConfig } from "./types";
3
-
4
- type ServerChain = {
5
- Auth(strategy: AuthStrategy): AuthenticationChain;
6
- }
1
+ import { Auth } from "../auth";
2
+ import type { ServerChain, ServerConfig } from "./types";
7
3
 
8
4
  export const Server = (config: ServerConfig): ServerChain => ({
9
5
  Auth: Auth(config),
@@ -1,3 +1,8 @@
1
1
  import type { config } from "mssql";
2
+ import type { AuthStrategy, AuthenticationChain } from "../auth/types";
2
3
 
3
4
  export type ServerConfig = Omit<config, "authentication" | "user" | "password">;
5
+
6
+ export type ServerChain = {
7
+ Auth(strategy: AuthStrategy): AuthenticationChain;
8
+ }
@@ -0,0 +1 @@
1
+ export * from "./auth/types"
@@ -0,0 +1,42 @@
1
+ import { ConnectionError, PreparedStatementError, RequestError, TransactionError } from "mssql";
2
+
3
+ export type ErrorType = Error | ConnectionError | TransactionError | RequestError | PreparedStatementError;
4
+
5
+ const BASE_FILENAME = process.argv[1]?.replace(/\.(?:js|ts)/, '');
6
+ const ERROR_LOG_PATH = `${BASE_FILENAME}-last-errors.json`;
7
+
8
+ export const CleanErrors = async () => {
9
+ if (await Bun.file(ERROR_LOG_PATH).exists()) {
10
+ await Bun.file(ERROR_LOG_PATH).delete()
11
+ }
12
+ }
13
+
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
+
34
+ // Update error content with new error for database
35
+ const updatedContent = {
36
+ ...errorContent,
37
+ [database]: serializableError
38
+ };
39
+
40
+ // Write updated error content to file
41
+ await Bun.write(errorFile, JSON.stringify(updatedContent, null, 2));
42
+ }
@@ -0,0 +1,18 @@
1
+ type Env = {
2
+ MAX_ERRORS: number;
3
+ }
4
+
5
+ type StringEnv = {
6
+ [P in keyof Env]?: string
7
+ }
8
+
9
+ declare module "bun" {
10
+ interface Env extends StringEnv {}
11
+ }
12
+
13
+ export const LoadEnv = (): Env => {
14
+ const MAX_ERRORS = Number.parseInt(Bun.env.MAX_ERRORS || '1', 10);
15
+ return {
16
+ MAX_ERRORS
17
+ }
18
+ }
package/biome.json DELETED
@@ -1,34 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
3
- "vcs": {
4
- "enabled": false,
5
- "clientKind": "git",
6
- "useIgnoreFile": false
7
- },
8
- "files": {
9
- "ignoreUnknown": false
10
- },
11
- "formatter": {
12
- "enabled": true,
13
- "indentStyle": "tab"
14
- },
15
- "linter": {
16
- "enabled": true,
17
- "rules": {
18
- "recommended": true
19
- }
20
- },
21
- "javascript": {
22
- "formatter": {
23
- "quoteStyle": "double"
24
- }
25
- },
26
- "assist": {
27
- "enabled": true,
28
- "actions": {
29
- "source": {
30
- "organizeImports": "on"
31
- }
32
- }
33
- }
34
- }
@@ -1,119 +0,0 @@
1
- import { beforeAll, describe, expect, test, mock, afterEach} from 'bun:test'
2
- import { AzureSqlEdge, SQL_PASSWORD } from './container/container'
3
- import { Server } from '../src';
4
- import { UserAndPassword } from '../src/pipes/auth/strategies';
5
- import { CLIENTS_MANAGER_DATABASE, DATABASES, SetupClientManager, SetupDatabases } from './container/setup/databases';
6
- import { type DatabaseConnection } from '../src/pipes/connect';
7
- import type { Transaction } from 'mssql';
8
-
9
- const mockExecute = mock(<TParam>(connections$: AsyncGenerator<DatabaseConnection[]>) => (fn: (transaction: Transaction, database: string, params: TParam) => Promise<void>) => Promise<void>);
10
- mock.module('../src/pipes/execute', () => ({
11
- Execute: mockExecute,
12
- }))
13
-
14
- describe('Connection overloads', async () => {
15
- const container = await AzureSqlEdge();
16
- const LocalServer = Server({
17
- server: container.getHost(),
18
- port: container.getMappedPort(1433),
19
- options: {
20
- encrypt: false
21
- }
22
- }).Auth(UserAndPassword("sa", SQL_PASSWORD));
23
-
24
- beforeAll(async () => {
25
- await SetupDatabases(container);
26
- await SetupClientManager(container);
27
- });
28
-
29
- afterEach(() => {
30
- mockExecute.mockClear();
31
- })
32
-
33
- test('Connect to unique database', async () => {
34
- const database = DATABASES[0]!;
35
- const Connection = LocalServer.Connect(database);
36
-
37
- expect(Connection).toBeDefined();
38
- expect(mockExecute).toHaveBeenCalledTimes(1);
39
-
40
- const connectionsGenerator = mockExecute.mock.calls[0]![0];
41
- const uniqueConnection = await connectionsGenerator.next();
42
-
43
- expect(uniqueConnection.done).toBe(false);
44
- expect(uniqueConnection.value).toHaveLength(1);
45
- expect(uniqueConnection.value).toEqual([{
46
- database,
47
- connection: expect.any(Promise),
48
- }]);
49
- });
50
-
51
- test('Connect with database list', async () => {
52
- const databases = [DATABASES[0]!, DATABASES[1]!];
53
- LocalServer.Connect(databases);
54
-
55
- expect(mockExecute).toHaveBeenCalledTimes(1);
56
- const connectionsGenerator = mockExecute.mock.calls[0]![0];
57
- const connections = await connectionsGenerator.next();
58
-
59
- expect(connections.done).toBe(false);
60
- expect(connections.value).toHaveLength(2);
61
- expect(connections.value).toEqual([{
62
- database: DATABASES[0]!,
63
- connection: expect.any(Promise),
64
- }, {
65
- database: DATABASES[1]!,
66
- connection: expect.any(Promise),
67
- }]);
68
- });
69
-
70
- test('Connect with limited concurrent database list', async () => {
71
- const databases = [DATABASES[0]!, DATABASES[1]!, DATABASES[2]!, DATABASES[3]!];
72
- LocalServer.Connect(databases, 2);
73
-
74
- expect(mockExecute).toHaveBeenCalledTimes(1);
75
- const connectionsGenerator = mockExecute.mock.calls[0]![0];
76
- const firstConnections = await connectionsGenerator.next();
77
-
78
- expect(firstConnections.done).toBe(false);
79
- expect(firstConnections.value).toHaveLength(2);
80
- expect(firstConnections.value).toEqual([{
81
- database: DATABASES[0]!,
82
- connection: expect.any(Promise),
83
- }, {
84
- database: DATABASES[1]!,
85
- connection: expect.any(Promise),
86
- }]);
87
-
88
- const secondConnections = await connectionsGenerator.next();
89
- expect(secondConnections.done).toBe(false);
90
- expect(secondConnections.value).toHaveLength(2);
91
- expect(secondConnections.value).toEqual([{
92
- database: DATABASES[2]!,
93
- connection: expect.any(Promise),
94
- }, {
95
- database: DATABASES[3]!,
96
- connection: expect.any(Promise),
97
- }]);
98
-
99
- const thirdConnections = await connectionsGenerator.next();
100
- expect(thirdConnections.done).toBe(true);
101
- });
102
-
103
- test('Connect with query', async () => {
104
- LocalServer.Connect({
105
- database: CLIENTS_MANAGER_DATABASE,
106
- query: 'SELECT DatabaseName FROM Clients WHERE Active = 1'
107
- });
108
-
109
- expect(mockExecute).toHaveBeenCalledTimes(1);
110
- const connectionsGenerator = mockExecute.mock.calls[0]![0];
111
- const connections = await connectionsGenerator.next();
112
- expect(connections.done).toBe(false);
113
- expect(connections.value).toHaveLength(5);
114
- expect(connections.value).toEqual(DATABASES.map(database => ({
115
- database,
116
- connection: expect.any(Promise),
117
- })));
118
- })
119
- })