Squilo is a Bun-first library for orchestrating SQL Server connections, authentication, and script execution with modern TypeScript patterns. It's designed for scenarios where you need to:
- Execute operations across multiple databases (multi-tenant applications, client databases, sharded systems)
- Orchestrate complex database workflows with connection pooling and automatic resource management
- Release database connections quickly while performing expensive post-processing
- Produce multiple output formats (JSON, Excel, CSV, custom) with merged results
- Support various authentication methods (SQL auth, Azure AD, custom strategies)
Key Features:
- ✅ Bun-first: Built for Bun runtime with optimal performance
- ✅ Pipeline architecture: Chainable methods:
Server → Auth → Connect → Retrieve/Execute → Transform (optional) → Output - ✅ Connection pooling: Automatic pool management with configurable concurrency
- ✅ Multi-database orchestration: Process hundreds of databases efficiently
- ✅ Early connection release: Transform pipe closes DB connections before expensive operations
- ✅ Flexible output: Built-in JSON, Excel, Console, Merge + custom strategies
- ✅ Type-safe: Full TypeScript support with proper generics
- ✅ Production-ready: Error handling, progress bars (in non-test env), transaction support
Use Cases:
- Multi-tenant SaaS applications with per-client databases
- Scheduled batch jobs across multiple database instances
- Data migrations and synchronization scripts
- Report generation and analytics pipelines
- Database maintenance operations
- Bun v1.2.20+
- SQL Server (local, on-premise, or Azure)
- TypeScript knowledge
# Initialize Bun project (if you haven't already)
bun init
# Add Squilo as dependency
bun add squiloHere's a minimal example to get you started:
// scripts/get-active-users.ts
import { Server } from "squilo";
import { UserAndPassword } from "squilo/auth/strategies";
import { MergeOutputStrategy } from "squilo/output/strategies";
const LocalServer = Server({
server: "localhost",
port: 1433,
options: { encrypt: false }
}).Auth(UserAndPassword("sa", "your-password"));
const [errors, activeUsers] = await LocalServer
.Connect("YourDatabase")
.Retrieve(async (connection) => {
const result = await connection.query`
SELECT Id, Name, Email FROM Users WHERE Active = 1
`;
return result.recordset;
})
.Output(MergeOutputStrategy());
if (errors.length > 0) {
console.error('Failed to retrieve users:', errors);
process.exit(1);
}
console.log(`Found ${activeUsers.length} active users`);Run it:
bun run scripts/get-active-users.tsimport { Server } from "squilo";
import { UserAndPassword } from "squilo/auth/strategies";
const LocalServer = Server({
server: "localhost",
port: 1433,
options: { encrypt: false }
}).Auth(UserAndPassword("sa", "your-password"));
// Simple connection to one database
const connectionChain = LocalServer.Connect("YourDatabase");const errors = await LocalServer
.Connect("YourDatabase")
.Execute(async (connection) => {
await connection.query`
UPDATE Users
SET Email = LTRIM(RTRIM(Email))
WHERE Email LIKE ' %' OR Email LIKE '% '
`;
});
if (errors.length > 0) {
console.error('Email cleanup failed:', errors);
} else {
console.log('Email formatting fixed successfully');
}import { MergeOutputStrategy } from "squilo/output/strategies";
const [errors, results] = await LocalServer
.Connect("YourDatabase")
.Retrieve(async (connection) => {
const result = await connection.query<{ activeCount: number }>`
SELECT COUNT(*) as activeCount FROM Users WHERE Active = 1
`;
return result.recordset;
})
.Output(MergeOutputStrategy());
if (errors.length === 0 && results.length > 0) {
console.log(`Active users: ${results[0].activeCount}`);
}Squilo accepts all mssql configuration options except authentication, user, and password (which are managed by auth strategies):
// Production config with full options
const Production = Server({
server: process.env.DB_SERVER!, // Required: server hostname
port: Number(process.env.DB_PORT) || 1433,
database: "master", // Default database (overridden by Connect)
options: {
encrypt: true, // SSL encryption
trustServerCertificate: false, // Validate certificate
enableArithAbort: true
},
pool: {
max: 50, // Max connections in pool
min: 0, // Min connections to keep
idleTimeoutMillis: 30000 // 30 second idle timeout
},
requestTimeout: 300000, // 5 minute query timeout
cancelTimeout: 5000 // 5 second cancel timeout
});Squilo supports three ways to specify target databases:
const singleDb = LocalServer.Connect("MyDatabase");
const [errors, data] = await singleDb
.Retrieve(async (conn) => {
return await conn.query`SELECT * FROM Users`;
})
.Output(MergeOutputStrategy());const databases = ["Client1", "Client2", "Client3"];
// Process all databases (concurrent by default)
const [errors, allData] = await LocalServer
.Connect(databases) // No concurrent limit = process all at once
.Retrieve(async (conn, db) => {
const result = await conn.query`SELECT * FROM ${db}.Users`;
return result.recordset;
})
.Output(MergeOutputStrategy());
// Process with concurrency limit (e.g., 5 at a time)
const [errors, limitedData] = await LocalServer
.Connect(databases, 5) // Process 5 databases simultaneously
.Retrieve(async (conn, db) => {
return await conn.query`SELECT * FROM ${db}.Users`;
})
.Output(MergeOutputStrategy());interface DiscoveredDB {
Database: string;
IsActive: boolean;
Region: string;
}
const [errors, data] = await LocalServer
.Connect<DiscoveredDB>({
database: "ManagerDB",
query: `SELECT DatabaseName AS [Database], Active AS IsActive, Region
FROM ActiveClients
WHERE Status = 'Active' ORDER BY Priority`
})
.Retrieve(async (conn, dbInfo) => {
// dbInfo contains the Database (required) plus any extra properties from query
console.log(`Processing ${dbInfo.Database} in ${dbInfo.Region} (Active: ${dbInfo.IsActive})`);
const result = await conn.query`
SELECT Id, Name, Email, CreatedAt
FROM Users
WHERE Active = 1
`;
// Add database metadata to each record for downstream processing
return result.recordset.map(user => ({
...user,
Database: dbInfo.Database,
DatabaseRegion: dbInfo.Region,
IsActiveTenant: dbInfo.IsActive
}));
})
.Output(MergeOutputStrategy());
// Returns enriched user data with Database, DatabaseRegion, IsActiveTenant fieldsSquilo provides built-in authentication strategies:
import { UserAndPassword } from "squilo/auth/strategies";
const ServerWithAuth = Server(config)
.Auth(UserAndPassword("username", "password"));import { ActiveDirectoryAccessToken } from "squilo/auth/strategies";
const AzureServer = Server({
server: "your-server.database.windows.net",
options: { encrypt: true }
}).Auth(await ActiveDirectoryAccessToken({
clientId: process.env.AZURE_CLIENT_ID!,
clientSecret: process.env.AZURE_CLIENT_SECRET!,
authority: "https://login.microsoftonline.com/your-tenant-id"
}));Implement the AuthStrategy type: (config: ServerConfig) => config
import type { AuthStrategy } from "squilo/auth/strategies";
// Certificate-based authentication
const CertificateAuth = (certPath: string, keyPath: string): AuthStrategy => {
return (config) => ({
...config,
authentication: {
type: 'certificate',
options: {
cert: Bun.file(certPath),
key: Bun.file(keyPath)
}
}
});
};
// Custom token from secure vault
const VaultTokenAuth = async (): AuthStrategy => {
const token = await fetchTokenFromVault();
return (config) => ({
...config,
authentication: {
type: 'azure-active-directory-access-token',
options: { token }
}
});
};
// Use custom auth
const CustomServer = Server(config).Auth(CertificateAuth("./cert.pem", "./key.pem"));const [errors, users] = await LocalServer
.Connect("UsersDB")
.Retrieve(async (connection) => {
// connection is ConnectionPoolWrapper with .query() method
const result = await connection.query`
SELECT Id, Name, Email, CreatedAt
FROM Users
WHERE Active = 1
ORDER BY CreatedAt DESC
`;
return result.recordset; // Returns array of rows
})
.Output(MergeOutputStrategy());
// Returns: [ExecutionError[], User[]]Wrap operations in a transaction for atomicity using the await using pattern. Transactions auto-rollback if commit$() is not called:
import { Table, type Request } from "mssql";
const [errors, summary] = await LocalServer
.Connect("AnalyticsDB")
.Retrieve(async (connection) => {
// Create transaction with async disposable pattern
await using transaction = await connection.transaction$();
// Reuse a single request object for all operations
const request = transaction.request();
// Complex query with CTE
const result1 = await request
.query`
WITH MonthlyActive AS (
SELECT user_id, MAX(activity_date) as last_active
FROM user_activities
WHERE activity_date >= DATEADD(month, -1, GETDATE())
GROUP BY user_id
)
SELECT COUNT(*) as TotalActive
FROM MonthlyActive
`;
// Table-Valued Parameter example
const tvp = new Table('UserList');
tvp.columns.add('Id', 'Int');
tvp.columns.add('Name', 'NVarChar(100)');
tvp.rows.add([1, 'Alice'], [2, 'Bob']);
const result2 = await request
.input('Users', tvp)
.query`SELECT * FROM ProcessUsers(@Users)`;
// Additional operations can use the same request
await request.query`
INSERT INTO AuditLog (Action, Details)
VALUES ('MonthlyActiveCalc', 'Processed ${result1.recordset[0]?.TotalActive} users')
`;
// Commit transaction - if we don't call this, it auto-rolls back on dispose
await transaction.commit$();
return {
activeCount: result1.recordset[0]?.TotalActive,
processed: result2.recordset
};
})
.Output(MergeOutputStrategy());For simple operations (formatting, calculated fields), transform directly in the Retrieve callback:
const [errors, users] = await LocalServer
.Connect("UsersDB")
.Retrieve(async (connection) => {
const result = await connection.query`
SELECT Id, Email, CreatedAt FROM Users WHERE Active = 1
`;
// Inline transformation - simple and efficient
return result.recordset.map(user => ({
...user,
Email: user.Email.toLowerCase().trim(),
Initials: `${user.FirstName[0]}${user.LastName[0]}`,
DaysActive: Math.floor((Date.now() - user.CreatedAt.getTime()) / (1000 * 60 * 60 * 24))
}));
})
.Output(MergeOutputStrategy());Use Transform when you need to perform expensive operations that don't require a database connection. The Transform pipe releases DB connections immediately, preventing connection pool exhaustion during lengthy operations.
When to use Transform:
✅ External API calls (geolocation, CRM lookup, payment verification)
✅ File I/O operations (image processing, PDF generation, document parsing)
✅ CPU-intensive calculations (encryption, compression, statistical analysis)
✅ Operations that could block the connection pool
When NOT to use Transform (do in Retrieve instead):
❌ Simple string formatting
❌ Adding fields from existing data
❌ Basic array operations (filter, map, reduce)
❌ Quick calculations
const [errors, enrichedUsers] = await LocalServer
.Connect("UsersDB")
.Retrieve(async (connection) => {
return await connection.query`
SELECT Id, Email, CountryCode FROM Users WHERE Active = 1
`;
})
.Transform(async (users) => {
// DB connection is already released here!
// Enrich with external API calls (can run in parallel)
const enriched = await Promise.allSettled(
users.map(async (user) => {
try {
const geo = await fetch(`https://api.example.com/geo/${user.CountryCode}`)
.then(r => r.json());
const crm = await fetch(`https://crm.example.com/users/${user.Id}`)
.then(r => r.json());
return { ...user, ...geo, crmData: crm };
} catch (error) {
return { ...user, enrichmentFailed: true, error: error.message };
}
})
);
return enriched.filter((result): result is PromiseFulfilledResult<any> =>
result.status === 'fulfilled'
).map(r => r.value);
})
.Output(MergeOutputStrategy());const [_, usersWithAvatars] = await LocalServer
.Connect("UsersDB")
.Retrieve(async (connection) => {
return await connection.query<{ Id: number; AvatarPath: string }>`
SELECT Id, AvatarPath FROM Users WHERE HasAvatar = 1
`;
})
.Transform(async (users) => {
// Process images in parallel without holding DB connections
const processed = await Promise.all(
users.map(async (user) => {
if (!user.AvatarPath) return user;
try {
const imageBuffer = await Bun.file(user.AvatarPath).arrayBuffer();
const thumbnail = await generateThumbnail(imageBuffer, 64, 64);
const compressed = await compressImage(imageBuffer);
return {
...user,
Thumbnail: thumbnail,
CompressedSize: compressed.length,
OriginalSize: imageBuffer.byteLength
};
} catch {
return { ...user, ProcessingFailed: true };
}
})
);
return processed;
})
.Output(MergeOutputStrategy());Execute returns Promise<ExecutionError<T>[]> - an array of errors for any databases that failed:
const errors = await LocalServer
.Connect("UsersDB")
.Execute(async (connection) => {
await connection.query`
UPDATE Users
SET Status = 'Active'
WHERE LastLogin > DATEADD(day, -30, GETDATE())
`;
});
if (errors.length > 0) {
console.error(`Failed on ${errors.length} databases:`, errors);
} else {
console.log('Update completed successfully');
}For operations that need atomicity across multiple statements:
const errors = await LocalServer
.Connect("OrderDB")
.Execute(async (connection) => {
// Create transaction with proper async disposal
await using transaction = await connection.transaction$();
// Reuse a single request object for all operations
const request = transaction.request();
// Multiple operations in single transaction
await request
.query`UPDATE Inventory SET Quantity = Quantity - 1 WHERE ProductId = 123`;
await request
.query`INSERT INTO OrderHistory (ProductId, Quantity) VALUES (123, 1)`;
await request.query`
INSERT INTO AuditLog (Action, Timestamp)
VALUES ('OrderCreated', GETDATE())
`;
// Explicit commit - if omitted, transaction auto-rolls back on dispose
await transaction.commit$();
});When executing across multiple databases, Execute() returns an array of errors for databases that failed. Successful executions are silent:
const allClientDBs = ["Client1", "Client2", "Client3", "Client4"];
const errors = await LocalServer
.Connect(allClientDBs)
.Execute(async (connection, database) => {
await connection.query`
UPDATE Users
SET Status = 'Active'
WHERE LastLogin > DATEADD(day, -30, GETDATE())
`;
// You can also do more operations per database
await connection.query`
UPDATE Settings
SET LastMaintenance = GETDATE()
WHERE DatabaseName = '${database}'
`;
});
if (errors.length === 0) {
console.log(`✅ Successfully updated all ${allClientDBs.length} databases`);
} else {
console.error(`❌ Failed on ${errors.length}/${allClientDBs.length} databases:`);
errors.forEach(err => {
console.error(` - ${err.database}: ${err.error.message}`);
});
// Optionally retry failed databases
// const retryErrors = await retryFailedDatabases(errors);
}The Output() method finalizes your pipeline and returns results. Available strategies:
Merges all results from multiple databases into a single array. Best for further processing in JavaScript/TypeScript.
import { MergeOutputStrategy } from "squilo/output/strategies";
const [errors, mergedData] = await LocalServer
.Connect(["DB1", "DB2", "DB3"])
.Retrieve(async (conn) => {
return await conn.query`SELECT * FROM Users WHERE Active = 1`;
})
.Output(MergeOutputStrategy());
// Returns: [ExecutionError[], TData[]]
// errors: Array of { database: string, error: Error } for failed databases
// mergedData: Combined array of all successful results (flattened)Writes results to a JSON file. Automatically created with timestamp.
import { JsonOutputStrategy } from "squilo/output/strategies";
// Default: returns [errors, filename]
const [errors, filename] = await LocalServer
.Connect("UsersDB")
.Retrieve(async (conn) => {
return await conn.query`SELECT * FROM Users`;
})
.Output(JsonOutputStrategy({
includeEmpty: true, // Include databases with no data (default: true)
includeErrors: false // Errors returned separately (default: false)
}));
// With includeErrors=true: returns filename only (errors included in JSON)
const filenameWithErrors = await LocalServer
.Connect("UsersDB")
.Retrieve(async (conn) => query`SELECT * FROM Users`)
.Output(JsonOutputStrategy({ includeEmpty: true, includeErrors: true }));
// Returns: string (filename)
console.log(`Data saved to: ${filename}`);Parameters:
includeEmpty?: boolean(default:true) - Include databases that returned no dataincludeErrors?: boolean(default:false) - Include error information in the JSON file
Writes results to an Excel file (.xlsx) with options:
import { XlsOutputStrategy } from "squilo/output/strategies";
// Default: separate sheets per database
const [errors, filename] = await LocalServer
.Connect(["DB1", "DB2", "DB3"])
.Retrieve(async (conn, db) => {
return await conn.query`SELECT * FROM ${db}.Users`;
})
.Output(XlsOutputStrategy({
combineSheets: false, // Separate sheets per database (default)
includeEmpty: true, // Include empty results (default: true)
includeErrors: false // Errors returned separately (default: false)
}));
// Combined sheet: all data with Database column
const [errors, combinedFilename] = await LocalServer
.Connect(["DB1", "DB2"])
.Retrieve(async (conn, db) => {
return await conn.query`SELECT * FROM Users`;
})
.Output(XlsOutputStrategy({
combineSheets: true, // All data in one sheet
includeEmpty: true,
includeErrors: false
}));Parameters:
combineSheets?: boolean(default:false) - Combine all data into single sheet (adds Database column)includeEmpty?: boolean(default:true) - Include databases with no dataincludeErrors?: boolean(default:false) - Include errors in an "Errors" sheet
Logs results to console. Returns void. Great for debugging:
import { ConsoleOutputStrategy } from "squilo/output/strategies";
await LocalServer
.Connect("TestDB")
.Retrieve(async (conn) => {
return await conn.query`SELECT TOP 5 * FROM Users`;
})
.Output(ConsoleOutputStrategy());
// Outputs to console, no return valueImplement the OutputStrategy<T, TReturn, TOutput> type:
import type { OutputStrategy, ExecutionResult } from "squilo/output/strategies";
// OutputStrategy: (data: ReadableStream<ExecutionResult<T, TReturn>>) => Promise<TOutput>
// ExecutionResult = { database: T, data?: TReturn, error?: Error }
const CsvOutputStrategy = <T, TData>(): OutputStrategy<T, TData, string> => {
return async (result): Promise<string> => {
let csv = 'Database,Data\n';
const errors: Array<{ database: T; error: Error }> = [];
for await (const { database, data, error } of result) {
if (error) {
errors.push({ database, error });
continue;
}
if (Array.isArray(data)) {
data.forEach(row => {
const csvLine = Object.values(row)
.map(v => `"${String(v).replace(/"/g, '""')}"`)
.join(',');
csv += `${String(database)},${csvLine}\n`;
});
}
}
if (errors.length > 0) {
console.error('CSV export had errors:', errors.length);
}
return csv;
};
};
// Usage
const csvData = await LocalServer
.Connect("UsersDB")
.Retrieve(async (conn) => query`SELECT * FROM Users`)
.Output(CsvOutputStrategy());Custom Strategy Ideas:
- Cloud storage upload: Direct upload to S3/Azure Blob/Google Cloud
- Message queue: Publish to RabbitMQ, Kafka, Azure Service Bus
- HTTP streaming: Push to webhook or API endpoint
- Database sync: Copy to another database with transformations
- Binary formats: Protocol Buffers, MessagePack, Avro
- Email reports: Auto-generate and send email attachments
- Chat notifications: Send to Slack, Teams, Discord
When you use MergeOutputStrategy() with multiple databases, you get a flattened array with one entry per database. You can aggregate this data after retrieval:
Example: Count aggregation across databases
const allDatabases = ["Client1", "Client2", "Client3", "Client4"];
const [errors, results] = await LocalServer
.Connect(allDatabases)
.Retrieve(async (connection, db) => {
const result = await connection.query<{ activeCount: number }>`
SELECT COUNT(*) as activeCount FROM Users WHERE Active = 1
`;
return result.recordset;
})
.Output(MergeOutputStrategy());
// results = [{activeCount: 10}, {activeCount: 15}, ...] - one entry per database
if (errors.length === 0) {
// Aggregate: total sum
const totalActiveUsers = results.reduce((sum, db) => sum + db.activeCount, 0);
console.log(`Total active users: ${totalActiveUsers}`);
// Per-database breakdown
results.forEach((dbResult, idx) => {
console.log(` ${allDatabases[idx]}: ${dbResult.activeCount}`);
});
}Example: Flatten and combine user data
interface UserData {
Id: number;
Name: string;
Email: string;
}
const [errors, allUsers] = await LocalServer
.Connect(allDatabases)
.Retrieve(async (connection, db) => {
const result = await connection.query<UserData>`
SELECT Id, Name, Email FROM Users WHERE Active = 1
`;
return result.recordset;
})
.Output(MergeOutputStrategy());
// allUsers = flat array: [user1_db1, user2_db1, user1_db2, user2_db2, ...]
console.log(`Total users across all databases: ${allUsers.length}`);
// Group by database (if you added database identifier in query)
const byDatabase = allUsers.reduce((acc, user) => {
// Assuming you included database name in SELECT
const dbName = (user as any).Database;
acc[dbName] ??= [];
acc[dbName].push(user);
return acc;
}, {} as Record<string, UserData[]>);Aggregation in Custom Output Strategy
For more complex aggregations (averages, statistical analysis, grouping), create a custom output strategy:
const AggregatingOutputStrategy = <T, TData>(): OutputStrategy<T, TData, {
totalCount: number;
averages: Record<string, number>;
errors: ExecutionError<T>[]
}> => {
return async (result): Promise<{
totalCount: number;
averages: Record<string, number>;
errors: ExecutionError<T>[];
}> => {
let totalCount = 0;
const sums: Record<string, number> = {};
const counts: Record<string, number> = {};
const errors: ExecutionError<T>[] = [];
for await (const { database, data, error } of result) {
if (error) {
errors.push({ database, error });
continue;
}
if (Array.isArray(data) && data.length > 0) {
totalCount += data.length;
// Example: calculate averages for numeric fields
data.forEach(row => {
for (const [key, value] of Object.entries(row)) {
if (typeof value === 'number') {
sums[key] = (sums[key] || 0) + value;
counts[key] = (counts[key] || 0) + 1;
}
}
});
}
}
const averages: Record<string, number> = {};
for (const key of Object.keys(sums)) {
averages[key] = sums[key] / counts[key];
}
return { totalCount, averages, errors };
};
};
// Usage
const aggregated = await LocalServer
.Connect(allDatabases)
.Retrieve(async (conn) => {
return await conn.query<{
salary: number;
age: number;
department: string
}>`SELECT * FROM Employees`;
})
.Output(AggregatingOutputStrategy());
console.log(`Total employees: ${aggregated.totalCount}`);
console.log(`Average salary: $${aggregated.averages.salary.toFixed(2)}`);
console.log(`Average age: ${aggregated.averages.age.toFixed(1)}`);Some strategies support includeErrors: true which changes return type:
Without includeErrors (default):
const [errors, result] = await chain.Output(JsonOutputStrategy());
// Type: [ExecutionError[], string] (errors separate, result is filename)With includeErrors: true
const filename = await chain.Output(JsonOutputStrategy({ includeErrors: true }));
// Type: string (errors embedded in JSON file, only filename returned)Why would you use includeErrors?
- When you want a single return value (the file) and don't need programmatic error handling
- When errors should be logged/archived with the output file itself
- For simple batch scripts where any errors can be reviewed later
Understand when to use Transform() vs doing work in Retrieve():
| Aspect | Inline in Retrieve | Transform Pipe |
|---|---|---|
| DB Connection | Held during processing | Released before processing |
| Use Case | Simple, fast operations | Expensive async operations |
| Examples | Formatting, calculations, mapping | API calls, file I/O, CPU-intensive |
| Connection Pool | Blocks until complete | Released immediately |
Example: Both Approaches, Same Result
// Approach 1: Inline (simple, efficient)
const [_, users1] = await LocalServer
.Connect("DB")
.Retrieve(async (conn) => {
const result = await conn.query`SELECT * FROM Users`;
return result.recordset.map(u => ({ ...u, Email: u.Email.toLowerCase() }));
})
.Output(MergeOutputStrategy());
// Approach 2: Transform (releases DB connection early)
const [_, users2] = await LocalServer
.Connect("DB")
.Retrieve(async (conn) => query`SELECT * FROM Users`)
.Transform(users => users.map(u => ({ ...u, Email: u.Email.toLowerCase() })))
.Output(MergeOutputStrategy());
// Same result, but Transform releases connection before mappingChoose inline for simple operations: No need for extra abstraction.
Choose Transform for expensive ops: API calls, file processing, etc.
Squilo uses mssql config but excludes authentication fields (managed by auth strategies):
interface ServerConfig {
server: string; // Server hostname (required)
port?: number; // Default: 1433
database?: string; // Default database
options?: {
encrypt?: boolean;
trustServerCertificate?: boolean;
enableArithAbort?: boolean;
// ... all other mssql options
};
pool?: {
max?: number; // Default: 10
min?: number; // Default: 0
idleTimeoutMillis?: number;
};
requestTimeout?: number; // Default: 15000ms
cancelTimeout?: number; // Default: 5000ms
}-
SAFE_GUARD- Maximum number of database failures to tolerate before skipping all remaining databases (default: 1). When the failure count reaches this limit, subsequent database operations are skipped to save time/resources:SAFE_GUARD=5 bun run scripts/process-clients.ts
This is useful for preventing wasted effort when there's a systemic issue affecting many databases. For example, if you're processing 100 databases and the first 5 fail, the remaining 95 will be skipped.
-
NODE_ENV- Set totestto disable progress bars:NODE_ENV=test bun test
src/
├── pipes/
│ ├── auth/
│ │ ├── strategies/
│ │ │ ├── types.ts # AuthStrategy definition
│ │ │ ├── userAndPassword.ts # Simple SQL auth
│ │ │ └── msal.ts # Azure AD authentication
│ │ └── index.ts # Auth pipe implementation
│ ├── output/
│ │ ├── strategies/
│ │ │ ├── types.ts # OutputStrategy definition
│ │ │ ├── json.ts # JSON file output
│ │ │ ├── xls.ts # Excel output
│ │ │ ├── console.ts # Console logging
│ │ │ └── merge.ts # In-memory merge
│ │ └── index.ts # Output pipe implementation
│ ├── connect/ # Database connection handling
│ ├── retrieve/ # Data retrieval operations
│ ├── execute/ # Data modification operations
│ ├── transform/ # Post-processing pipeline
│ ├── server/ # Server configuration
│ └── shared/runner/ # Connection execution engine
Key types you may need for custom implementations:
// From squilo/auth/strategies
import type { AuthStrategy } from "squilo/auth/strategies";
// From squilo/output/strategies
import type { OutputStrategy, ExecutionResult, ExecutionError } from "squilo/output/strategies";
// From mssql (re-exported as SQL)
import { SQL, type ConnectionPool, type Transaction, type Request } from "squilo";Run the test suite to see real-world examples:
bun testThe test/ directory contains comprehensive examples of:
- Multi-database orchestration patterns
- Error handling and recovery
- Transaction management
- Transform pipeline usage
- All output strategies
This project was created using bun init in Bun v1.2.20. Bun is a fast all-in-one JavaScript runtime.