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.
- package/README.md +265 -36
- package/package.json +17 -12
- package/src/.DS_Store +0 -0
- package/src/index.ts +3 -1
- package/src/pipes/auth/index.ts +2 -12
- package/src/pipes/auth/strategies/msal.ts +14 -7
- package/src/pipes/auth/strategies/userAndPassword.ts +1 -1
- package/src/pipes/auth/types.ts +12 -0
- package/src/pipes/connect/index.ts +25 -42
- package/src/pipes/connect/types.ts +25 -0
- package/src/pipes/execute/index.ts +36 -4
- package/src/pipes/index.ts +1 -1
- package/src/pipes/input/index.ts +6 -11
- package/src/pipes/input/types.ts +7 -0
- package/src/pipes/output/index.ts +4 -3
- package/src/pipes/output/strategies/console.ts +1 -1
- package/src/pipes/output/strategies/json.spec.ts +16 -6
- package/src/pipes/output/strategies/json.ts +14 -3
- package/src/pipes/output/strategies/merge.ts +1 -1
- package/src/pipes/output/strategies/xls.spec.ts +44 -15
- package/src/pipes/output/strategies/xls.ts +42 -10
- package/src/pipes/output/types.ts +1 -0
- package/src/pipes/retrieve/index.ts +48 -19
- package/src/pipes/retrieve/types.ts +5 -0
- package/src/pipes/server/index.ts +2 -6
- package/src/pipes/server/types.ts +5 -0
- package/src/pipes/types.ts +1 -0
- package/src/utils/append-error.ts +42 -0
- package/src/utils/load-env.ts +18 -0
- package/biome.json +0 -34
- package/test/connect.spec.ts +0 -119
- package/test/container/container.spec.ts +0 -33
- package/test/container/container.ts +0 -22
- package/test/container/setup/databases.spec.ts +0 -24
- package/test/container/setup/databases.ts +0 -77
- package/test/container/setup/users.spec.ts +0 -25
- package/test/container/setup/users.ts +0 -54
- package/test/index.spec.ts +0 -68
- package/test/input.spec.ts +0 -64
- package/tsconfig.json +0 -28
package/README.md
CHANGED
|
@@ -20,64 +20,293 @@ bun init
|
|
|
20
20
|
bun add squilo
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
## Usage
|
|
23
|
+
## Usage Examples
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### Server Configuration with Azure AD Authentication
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
|
-
//
|
|
28
|
+
// servers/production.ts
|
|
29
29
|
import { Server } from "squilo";
|
|
30
|
+
import { ActiveDirectoryAccessToken } from "squilo/auth/strategies";
|
|
30
31
|
|
|
31
|
-
export const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
export const Production = Server({
|
|
33
|
+
server: "your-server.database.windows.net",
|
|
34
|
+
options: {
|
|
35
|
+
encrypt: true,
|
|
36
|
+
trustedConnection: true,
|
|
37
|
+
trustServerCertificate: false
|
|
38
|
+
},
|
|
39
|
+
requestTimeout: 300000
|
|
40
|
+
}).Auth(await ActiveDirectoryAccessToken({
|
|
41
|
+
clientId: "your-client-id",
|
|
42
|
+
clientSecret: "your-client-secret",
|
|
43
|
+
authority: "https://login.microsoftonline.com/your-tenant-id"
|
|
44
|
+
}));
|
|
45
|
+
```
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
47
|
+
### Dynamic Database Connections
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// databases/all-active.ts
|
|
51
|
+
import { Production } from "../servers/production";
|
|
52
|
+
import { Qas } from "../servers/qas";
|
|
53
|
+
|
|
54
|
+
// Connect to multiple databases dynamically from Production
|
|
55
|
+
export const AllActiveDatabases = Production
|
|
56
|
+
.Connect({
|
|
57
|
+
database: "ManagerDatabase",
|
|
58
|
+
query: "SELECT [Database] FROM Client WHERE Active = 1"
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Same pattern for QAS environment
|
|
62
|
+
export const AllActiveQasDatabases = Qas
|
|
63
|
+
.Connect({
|
|
64
|
+
database: "ManagerDatabase",
|
|
65
|
+
query: "SELECT [Database] FROM Client WHERE Active = 1"
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Simple database connection
|
|
69
|
+
export const TestDatabase = Production.Connect("TestDatabase");
|
|
42
70
|
```
|
|
43
71
|
|
|
44
|
-
###
|
|
72
|
+
### Data Retrieval with Output Strategies
|
|
45
73
|
|
|
46
74
|
```ts
|
|
47
|
-
//
|
|
48
|
-
import {
|
|
75
|
+
// scripts/user-report.ts
|
|
76
|
+
import { JsonOutputStrategy, XlsOutputStrategy } from "squilo/output/strategies";
|
|
77
|
+
import { Production } from "../servers/production";
|
|
78
|
+
import { AllActiveDatabases } from "../databases/all-active";
|
|
79
|
+
|
|
80
|
+
type UserData = {
|
|
81
|
+
Id: number;
|
|
82
|
+
Email: string;
|
|
83
|
+
CreatedAt: Date;
|
|
84
|
+
}
|
|
49
85
|
|
|
50
|
-
|
|
51
|
-
|
|
86
|
+
// Retrieve data from multiple databases (uses Production by default)
|
|
87
|
+
await AllActiveDatabases
|
|
88
|
+
.Retrieve(async (transaction, database) => {
|
|
89
|
+
const result = await transaction
|
|
90
|
+
.request()
|
|
91
|
+
.query<UserData>`
|
|
92
|
+
SELECT Id, Email, CreatedAt
|
|
93
|
+
FROM Users
|
|
94
|
+
WHERE Active = 1
|
|
95
|
+
ORDER BY CreatedDate DESC
|
|
96
|
+
`;
|
|
97
|
+
return result.recordset;
|
|
98
|
+
})
|
|
99
|
+
.Output(JsonOutputStrategy());
|
|
52
100
|
```
|
|
53
101
|
|
|
54
|
-
###
|
|
102
|
+
### Execute Operations Across Multiple Databases
|
|
55
103
|
|
|
56
104
|
```ts
|
|
57
|
-
// scripts/
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
60
|
-
|
|
61
|
-
await
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
105
|
+
// scripts/update-campaign-status.ts
|
|
106
|
+
import { AllActiveDatabases } from "../databases/all-active";
|
|
107
|
+
import { Production } from "../servers/production";
|
|
108
|
+
|
|
109
|
+
await AllActiveDatabases
|
|
110
|
+
.Execute(async (transaction, database) => {
|
|
111
|
+
await transaction.request().query`
|
|
112
|
+
UPDATE Campaigns
|
|
113
|
+
SET Status = 'Active'
|
|
114
|
+
WHERE EndDate IS NULL AND StartDate <= GETDATE()
|
|
115
|
+
`;
|
|
116
|
+
})
|
|
117
|
+
.then(() => process.exit(0));
|
|
68
118
|
```
|
|
69
119
|
|
|
70
|
-
###
|
|
120
|
+
### Complex Queries with TypeScript Types
|
|
71
121
|
|
|
72
|
-
```
|
|
73
|
-
|
|
122
|
+
```ts
|
|
123
|
+
// scripts/analytics-report.ts
|
|
124
|
+
import { JsonOutputStrategy } from "squilo/output/strategies";
|
|
125
|
+
import { Production } from "../servers/production";
|
|
126
|
+
|
|
127
|
+
type AnalyticsResult = {
|
|
128
|
+
MaxConnectedStates: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await Production
|
|
132
|
+
.Connect("AnalyticsDatabase")
|
|
133
|
+
.Retrieve(async (transaction) => {
|
|
134
|
+
const result = await transaction.request().query<AnalyticsResult>`
|
|
135
|
+
-- Complex analytical query with CTEs and cursors
|
|
136
|
+
WITH ActiveStates AS (
|
|
137
|
+
SELECT DISTINCT State.Id
|
|
138
|
+
FROM State
|
|
139
|
+
INNER JOIN FlowState ON FlowState.StateId = State.Id
|
|
140
|
+
WHERE Flow.EndDate IS NULL
|
|
141
|
+
)
|
|
142
|
+
SELECT MAX(ConnectedCount) As MaxConnectedStates
|
|
143
|
+
FROM (
|
|
144
|
+
SELECT COUNT(*) as ConnectedCount
|
|
145
|
+
FROM ActiveStates
|
|
146
|
+
GROUP BY StateGroup
|
|
147
|
+
) AS StateCounts;
|
|
148
|
+
`;
|
|
149
|
+
return result.recordset;
|
|
150
|
+
})
|
|
151
|
+
.Output(JsonOutputStrategy());
|
|
74
152
|
```
|
|
75
153
|
|
|
76
154
|
## Advanced Usage
|
|
77
155
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
156
|
+
### Authentication Strategies
|
|
157
|
+
|
|
158
|
+
Squilo supports multiple authentication methods:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
// User and Password authentication
|
|
162
|
+
import { UserAndPassword } from "squilo/auth/strategies";
|
|
163
|
+
const auth = UserAndPassword("username", "password");
|
|
164
|
+
|
|
165
|
+
// Active Directory Access Token for Azure SQL
|
|
166
|
+
import { ActiveDirectoryAccessToken } from "squilo/auth/strategies";
|
|
167
|
+
const azureAuth = await ActiveDirectoryAccessToken({
|
|
168
|
+
clientId: "your-client-id",
|
|
169
|
+
clientSecret: "your-client-secret",
|
|
170
|
+
authority: "https://login.microsoftonline.com/your-tenant-id"
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Input and Retrieval
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
// Retrieve data with typed results
|
|
178
|
+
type ConfigData = {
|
|
179
|
+
Id: number;
|
|
180
|
+
Setting: string;
|
|
181
|
+
Value: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const results = await Production
|
|
185
|
+
.Connect("ConfigDatabase")
|
|
186
|
+
.Retrieve(async (transaction) => {
|
|
187
|
+
const result = await transaction
|
|
188
|
+
.request()
|
|
189
|
+
.query<ConfigData>`
|
|
190
|
+
SELECT Id, Setting, Value
|
|
191
|
+
FROM Configuration
|
|
192
|
+
ORDER BY CreatedDate
|
|
193
|
+
`;
|
|
194
|
+
return result.recordset;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Execute with database context
|
|
198
|
+
await AllActiveDatabases
|
|
199
|
+
.Execute(async (transaction, database) => {
|
|
200
|
+
console.log(`Processing database: ${database}`);
|
|
201
|
+
await transaction.request().query`
|
|
202
|
+
UPDATE Settings SET LastProcessed = GETDATE()
|
|
203
|
+
`;
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Output Strategies
|
|
208
|
+
|
|
209
|
+
Squilo provides multiple output formats:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// JSON output for data export
|
|
213
|
+
import { JsonOutputStrategy } from "squilo/output/strategies";
|
|
214
|
+
await AllActiveDatabases
|
|
215
|
+
.Retrieve(async (transaction) => {
|
|
216
|
+
const result = await transaction.request().query`
|
|
217
|
+
SELECT Id, Name, Status FROM Reports
|
|
218
|
+
`;
|
|
219
|
+
return result.recordset;
|
|
220
|
+
})
|
|
221
|
+
.Output(JsonOutputStrategy());
|
|
222
|
+
|
|
223
|
+
// Excel output for reports
|
|
224
|
+
import { XlsOutputStrategy } from "squilo/output/strategies";
|
|
225
|
+
await Production
|
|
226
|
+
.Connect("ReportsDatabase")
|
|
227
|
+
.Retrieve(async (transaction) => {
|
|
228
|
+
const result = await transaction.request().query`
|
|
229
|
+
SELECT * FROM MonthlyStats WHERE Year = 2024
|
|
230
|
+
`;
|
|
231
|
+
return result.recordset;
|
|
232
|
+
})
|
|
233
|
+
.Output(XlsOutputStrategy());
|
|
234
|
+
|
|
235
|
+
// Console output for debugging
|
|
236
|
+
import { ConsoleOutputStrategy } from "squilo/output/strategies";
|
|
237
|
+
await Production
|
|
238
|
+
.Connect("LogsDatabase")
|
|
239
|
+
.Retrieve(async (transaction) => {
|
|
240
|
+
const result = await transaction.request().query`
|
|
241
|
+
SELECT TOP 10 * FROM ErrorLogs ORDER BY Timestamp DESC
|
|
242
|
+
`;
|
|
243
|
+
return result.recordset;
|
|
244
|
+
})
|
|
245
|
+
.Output(ConsoleOutputStrategy());
|
|
246
|
+
|
|
247
|
+
// Merge results from multiple databases
|
|
248
|
+
import { MergeOutputStrategy } from "squilo/output/strategies";
|
|
249
|
+
await AllActiveDatabases
|
|
250
|
+
.Retrieve(async (transaction, database) => {
|
|
251
|
+
const result = await transaction.request().query`
|
|
252
|
+
SELECT '${database}' as DatabaseName, COUNT(*) as UserCount
|
|
253
|
+
FROM Users WHERE Active = 1
|
|
254
|
+
`;
|
|
255
|
+
return result.recordset;
|
|
256
|
+
})
|
|
257
|
+
.Output(MergeOutputStrategy());
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Connection Management
|
|
261
|
+
|
|
262
|
+
Properly manage connections in production scripts:
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
// Always close connections in production scripts
|
|
266
|
+
import { Production } from "../servers/production";
|
|
267
|
+
|
|
268
|
+
await Production
|
|
269
|
+
.Connect("ProductionDatabase")
|
|
270
|
+
.Retrieve(async (transaction) => {
|
|
271
|
+
const result = await transaction.request().query`
|
|
272
|
+
SELECT * FROM ImportantData
|
|
273
|
+
`;
|
|
274
|
+
return result.recordset;
|
|
275
|
+
})
|
|
276
|
+
.Output(JsonOutputStrategy());
|
|
277
|
+
|
|
278
|
+
// Close the connection and exit
|
|
279
|
+
await Production.Close();
|
|
280
|
+
process.exit(0);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Script Organization
|
|
284
|
+
|
|
285
|
+
Organize your scripts in a clear structure:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
project/
|
|
289
|
+
├── servers/
|
|
290
|
+
│ ├── production.ts # Production server config
|
|
291
|
+
│ └── qas.ts # QAS/staging server config
|
|
292
|
+
├── databases/
|
|
293
|
+
│ ├── all-active.ts # Dynamic database connections
|
|
294
|
+
│ └── test.ts # Test database connections
|
|
295
|
+
└── scripts/
|
|
296
|
+
├── reports/ # Report generation scripts
|
|
297
|
+
├── maintenance/ # Database maintenance scripts
|
|
298
|
+
└── analytics/ # Analytics and data processing
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Running Scripts
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
# Run individual scripts
|
|
305
|
+
bun run scripts/reports/user-activity.ts
|
|
306
|
+
|
|
307
|
+
# Run with specific environment
|
|
308
|
+
NODE_ENV=production bun run scripts/maintenance/cleanup.ts
|
|
309
|
+
```
|
|
81
310
|
|
|
82
311
|
## Reference
|
|
83
312
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squilo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -14,9 +14,13 @@
|
|
|
14
14
|
"name": "Douglas da Silva Sousa",
|
|
15
15
|
"email": "douglass.sousa@outlook.com.br"
|
|
16
16
|
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src/**/*"
|
|
19
|
+
],
|
|
17
20
|
"exports": {
|
|
18
21
|
".": "./src/index.ts",
|
|
19
|
-
"./auth": "./src/pipes/auth/strategies/index.ts"
|
|
22
|
+
"./auth/strategies": "./src/pipes/auth/strategies/index.ts",
|
|
23
|
+
"./output/strategies": "./src/pipes/output/strategies/index.ts"
|
|
20
24
|
},
|
|
21
25
|
"scripts": {
|
|
22
26
|
"test": "bun test",
|
|
@@ -24,21 +28,22 @@
|
|
|
24
28
|
"test:debug": "bun test --inspect"
|
|
25
29
|
},
|
|
26
30
|
"devDependencies": {
|
|
27
|
-
"@biomejs/biome": "2.2.
|
|
28
|
-
"@faker-js/faker": "^
|
|
31
|
+
"@biomejs/biome": "2.2.6",
|
|
32
|
+
"@faker-js/faker": "^10.1.0",
|
|
29
33
|
"@types/bun": "latest",
|
|
30
|
-
"@types/
|
|
31
|
-
"testcontainers": "^11.
|
|
34
|
+
"@types/cli-progress": "^3.11.6",
|
|
35
|
+
"testcontainers": "^11.7.1"
|
|
32
36
|
},
|
|
33
37
|
"peerDependencies": {
|
|
34
|
-
"typescript": "^5"
|
|
38
|
+
"typescript": "^5.9.3"
|
|
35
39
|
},
|
|
36
40
|
"dependencies": {
|
|
37
|
-
"@azure/msal-node": "^3.
|
|
38
|
-
"@types/mssql": "^9.1.
|
|
39
|
-
"
|
|
41
|
+
"@azure/msal-node": "^3.8.0",
|
|
42
|
+
"@types/mssql": "^9.1.8",
|
|
43
|
+
"cli-progress": "^3.12.0",
|
|
44
|
+
"mssql": "^12.0.0",
|
|
40
45
|
"open": "^10.2.0",
|
|
41
|
-
"xlsx": "
|
|
46
|
+
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
|
42
47
|
},
|
|
43
48
|
"license": "MIT"
|
|
44
|
-
}
|
|
49
|
+
}
|
package/src/.DS_Store
ADDED
|
Binary file
|
package/src/index.ts
CHANGED
package/src/pipes/auth/index.ts
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
import type { config } from 'mssql';
|
|
2
1
|
import type { ServerConfig } from "../server/types";
|
|
3
|
-
import type { ConnectionOptions } from "../connect";
|
|
4
2
|
import { Pool } from "../../pool";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
export type AuthStrategy = (config: ServerConfig) => config;
|
|
8
|
-
|
|
9
|
-
export type AuthenticationChain = {
|
|
10
|
-
Connect(database: string): ConnectionChain;
|
|
11
|
-
Connect(databases: string[], concurrent?: number): ConnectionChain;
|
|
12
|
-
Connect(options: ConnectionOptions, concurrent?: number): ConnectionChain;
|
|
13
|
-
Close(): Promise<void>;
|
|
14
|
-
}
|
|
3
|
+
import { Connect } from "../connect";
|
|
4
|
+
import type { AuthStrategy, AuthenticationChain } from "./types";
|
|
15
5
|
|
|
16
6
|
export const Auth = (config: ServerConfig) => (strategy: AuthStrategy): AuthenticationChain => {
|
|
17
7
|
const configWithAuth = strategy(config);
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
import * as path from "path";
|
|
14
14
|
import type { ServerConfig } from "../../server/types";
|
|
15
|
-
import type { AuthStrategy } from "
|
|
15
|
+
import type { AuthStrategy } from "../types";
|
|
16
16
|
import { cwd } from "process";
|
|
17
17
|
|
|
18
18
|
const SCOPES = ["https://database.windows.net//.default"];
|
|
@@ -80,12 +80,19 @@ export const GetToken = async (config: NodeAuthOptions) => {
|
|
|
80
80
|
let result: AuthenticationResult | null;
|
|
81
81
|
|
|
82
82
|
if (accounts.length > 0 && accounts[0]) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
try {
|
|
84
|
+
result = await pca.acquireTokenSilent({
|
|
85
|
+
scopes: SCOPES,
|
|
86
|
+
account: accounts[0],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return result?.accessToken;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof Error) {
|
|
92
|
+
console.error('Silent token acquisition failed:', error.message);
|
|
93
|
+
}
|
|
94
|
+
console.log("Proceeding to interactive authentication");
|
|
95
|
+
}
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
const interactiveRequest: InteractiveRequest = {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { config } from "mssql";
|
|
2
|
+
import type { ConnectionChain, ConnectionOptions } from "../connect/types";
|
|
3
|
+
import type { ServerConfig } from "../server/types";
|
|
4
|
+
|
|
5
|
+
export type AuthStrategy = (config: ServerConfig) => config;
|
|
6
|
+
|
|
7
|
+
export type AuthenticationChain = {
|
|
8
|
+
Connect(database: string): ConnectionChain;
|
|
9
|
+
Connect(databases: string[], concurrent?: number): ConnectionChain;
|
|
10
|
+
Connect(options: ConnectionOptions, concurrent?: number): ConnectionChain;
|
|
11
|
+
Close(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -1,48 +1,31 @@
|
|
|
1
|
-
import type { ConnectionPool, Transaction } from "mssql";
|
|
2
1
|
import type { Pool } from "../../pool";
|
|
3
|
-
import {
|
|
2
|
+
import { Input } from "../input";
|
|
4
3
|
import { Execute } from "../execute";
|
|
5
|
-
import {
|
|
4
|
+
import { Retrieve } from "../retrieve";
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
(database: string): ConnectionChain;
|
|
9
|
-
(databases: string[], concurrent?: number): ConnectionChain;
|
|
10
|
-
(options: ConnectionOptions, concurrent?: number): ConnectionChain;
|
|
11
|
-
}
|
|
6
|
+
import type { ConnectOverloads, ConnectionOptions, ConnectionChain, DatabaseConnection } from "./types";
|
|
12
7
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
8
|
+
export const Connect = (pool: Pool): ConnectOverloads => (param: string | string[] | ConnectionOptions, concurrent?: number): ConnectionChain => {
|
|
9
|
+
process.on("exit", async () => {
|
|
10
|
+
await pool.closeAll();
|
|
11
|
+
});
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
database: string;
|
|
20
|
-
connection: Promise<ConnectionPool>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type ConnectionChain = {
|
|
24
|
-
Execute(fn: (transaction: Transaction, database: string) => Promise<void>): Promise<void>;
|
|
25
|
-
Retrieve<TResult>(fn: (transaction: Transaction, database: string) => Promise<TResult>): RetrieveChain<TResult>;
|
|
26
|
-
Input<TParam>(fn: () => TParam): InputChain<TParam>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const Connect = (pool: Pool): ConnectOverloads => (param: string | string[] | ConnectionOptions, concurrent?: number): ConnectionChain => {
|
|
30
|
-
let connections$: AsyncGenerator<DatabaseConnection[]>;
|
|
13
|
+
let connections$: (databases: string[]) => Generator<DatabaseConnection[]>;
|
|
31
14
|
let databases$: Promise<string[]>;
|
|
32
15
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
16
|
+
function connections(concurrent: number = Number.MAX_VALUE): (databases: string[]) => Generator<DatabaseConnection[]> {
|
|
17
|
+
return function *(databases: string[]) {
|
|
18
|
+
const databases_result_chunks = Array.from(
|
|
19
|
+
{length: Math.ceil(databases.length / concurrent)},
|
|
20
|
+
(_, i) => databases.slice(i * concurrent, (i + 1) * concurrent)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
for (const databases_result_chunk of databases_result_chunks) {
|
|
24
|
+
yield databases_result_chunk.map(database => ({
|
|
25
|
+
database,
|
|
26
|
+
connection: pool.connect({ database })
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
46
29
|
}
|
|
47
30
|
}
|
|
48
31
|
|
|
@@ -68,11 +51,11 @@ export const Connect = (pool: Pool): ConnectOverloads => (param: string | string
|
|
|
68
51
|
throw new Error("Invalid parameter");
|
|
69
52
|
}
|
|
70
53
|
|
|
71
|
-
connections$ = connections(
|
|
54
|
+
connections$ = connections(concurrent);
|
|
72
55
|
|
|
73
56
|
return {
|
|
74
|
-
Input: Input(connections$),
|
|
75
|
-
Execute: Execute(connections$, null),
|
|
76
|
-
Retrieve: Retrieve(connections$, null)
|
|
57
|
+
Input: Input(connections$, databases$),
|
|
58
|
+
Execute: Execute(connections$, databases$, null),
|
|
59
|
+
Retrieve: Retrieve(connections$, databases$, null)
|
|
77
60
|
}
|
|
78
61
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ConnectionPool, Transaction } from "mssql";
|
|
2
|
+
import type { InputChain } from "../input/types";
|
|
3
|
+
import type { RetrieveChain } from "../retrieve/types";
|
|
4
|
+
|
|
5
|
+
export type ConnectOverloads = {
|
|
6
|
+
(database: string): ConnectionChain;
|
|
7
|
+
(databases: string[], concurrent?: number): ConnectionChain;
|
|
8
|
+
(options: ConnectionOptions, concurrent?: number): ConnectionChain;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ConnectionOptions = {
|
|
12
|
+
database: string;
|
|
13
|
+
query: `SELECT${string}FROM${string}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type DatabaseConnection = {
|
|
17
|
+
database: string;
|
|
18
|
+
connection: Promise<ConnectionPool>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ConnectionChain = {
|
|
22
|
+
Execute(fn: (transaction: Transaction, database: string) => Promise<void>): Promise<void>;
|
|
23
|
+
Retrieve<TResult>(fn: (transaction: Transaction, database: string) => Promise<TResult>): RetrieveChain<TResult>;
|
|
24
|
+
Input<TParam>(fn: () => TParam): InputChain<TParam>;
|
|
25
|
+
}
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import type { Transaction } from 'mssql';
|
|
2
|
-
import type { DatabaseConnection } from "../connect";
|
|
2
|
+
import type { DatabaseConnection } from "../connect/types";
|
|
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
|
+
|
|
3
10
|
|
|
4
11
|
export const Execute = <TParam>(
|
|
5
|
-
connections$:
|
|
12
|
+
connections$: (databases: string[]) => Generator<DatabaseConnection[]>,
|
|
13
|
+
databases$: Promise<string[]>,
|
|
6
14
|
input: TParam
|
|
7
15
|
) => {
|
|
8
16
|
return async (
|
|
9
17
|
fn: (transaction: Transaction, database: string, params: TParam) => Promise<void>
|
|
10
18
|
): Promise<void> => {
|
|
19
|
+
const singlerBar = new SingleBar({
|
|
20
|
+
format: `{bar} {percentage}% | {value}/{total} | {database}`
|
|
21
|
+
}, Presets.shades_classic);
|
|
22
|
+
|
|
11
23
|
const executeFn = async (dc: DatabaseConnection): Promise<void> => {
|
|
12
24
|
const opened = await dc.connection;
|
|
13
25
|
const transaction = opened.transaction()
|
|
@@ -15,15 +27,35 @@ export const Execute = <TParam>(
|
|
|
15
27
|
await transaction.begin();
|
|
16
28
|
await fn(transaction, dc.database, input);
|
|
17
29
|
await transaction.commit();
|
|
18
|
-
|
|
30
|
+
if (Bun.env.NODE_ENV !== 'test') {
|
|
31
|
+
singlerBar.increment(1, { database: dc.database });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
19
34
|
catch (error) {
|
|
20
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
|
+
}
|
|
21
42
|
}
|
|
22
43
|
};
|
|
23
44
|
|
|
24
|
-
|
|
45
|
+
await CleanErrors();
|
|
46
|
+
const databases = await databases$;
|
|
47
|
+
|
|
48
|
+
if (Bun.env.NODE_ENV !== 'test') {
|
|
49
|
+
singlerBar.start(databases.length, 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for await (const connectionBatch of connections$(databases)) {
|
|
25
53
|
const executions = connectionBatch.map(executeFn);
|
|
26
54
|
await Promise.allSettled(executions);
|
|
27
55
|
}
|
|
56
|
+
|
|
57
|
+
if (Bun.env.NODE_ENV !== 'test') {
|
|
58
|
+
singlerBar.stop();
|
|
59
|
+
}
|
|
28
60
|
};
|
|
29
61
|
};
|
package/src/pipes/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { Server } from './server/index'
|
|
1
|
+
export { Server } from './server/index'
|