@slingts/adapters
v0.1.8
Published
Service adapters for Sling framework
Readme
@slingts/adapters
Service adapters for the Sling framework.
Installation
npm install @slingts/adapters
# or
pnpm add @slingts/adaptersDatabase Adapters
SQLite Adapter
The SQLite adapter provides a full-featured database interface using better-sqlite3.
Basic Usage
import { createSQLiteAdapter, type DatabaseAdapter } from '@slingts/adapters';
import { globalAdapterRegistry } from '@slingts/core';
// Create and register the adapter
const dbAdapter = createSQLiteAdapter();
globalAdapterRegistry.register('db', dbAdapter, {
adapter: 'sqlite',
config: {
filename: './dev.db', // or ':memory:' for in-memory
verbose: false,
wal: true, // Enable WAL mode for better concurrency
foreignKeys: true, // Enable foreign key constraints
},
});
// Get the database client
const db = await globalAdapterRegistry.getClient<DatabaseAdapter>('db');Raw SQL Queries
# @slingts/adapters
Service adapters for the Sling framework.
## Installation
```bash
npm install @slingts/adapters
# or
pnpm add @slingts/adaptersDatabase Adapters
SQLite Adapter
The SQLite adapter provides a full-featured database interface using better-sqlite3.
Basic Usage
import { createSQLiteAdapter, type DatabaseAdapter } from '@slingts/adapters';
import { globalAdapterRegistry } from '@slingts/core';
// Create and register the adapter
const dbAdapter = createSQLiteAdapter();
globalAdapterRegistry.register('db', dbAdapter, {
adapter: 'sqlite',
config: {
filename: './dev.db', // or ':memory:' for in-memory
verbose: false,
wal: true, // Enable WAL mode for better concurrency
foreignKeys: true, // Enable foreign key constraints
},
});
// Get the database client
const db = await globalAdapterRegistry.getClient<DatabaseAdapter>('db');Raw SQL Queries
// Execute a query
await db.execute(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
`);Storage Adapters
Local Storage Adapter
The local storage adapter provides file storage using the local filesystem, perfect for development.
Basic Usage
import { createLocalStorageAdapter, type StorageAdapter } from '@slingts/adapters';
import { globalAdapterRegistry } from '@slingts/core';
// Create and register the adapter
const storageAdapter = createLocalStorageAdapter();
globalAdapterRegistry.register('storage', storageAdapter, {
adapter: 'local-storage',
config: {
basePath: './uploads', // Directory for storing files
publicUrl: 'http://localhost:3000/uploads', // Base URL for public files
createIfMissing: true, // Create directory if it doesn't exist
},
});
// Get the storage client
const storage = await globalAdapterRegistry.getClient<StorageAdapter>('storage');Upload Files
// Upload from buffer
const result = await storage.upload({
key: 'uploads/avatar.jpg',
file: Buffer.from(imageData),
contentType: 'image/jpeg',
acl: 'public-read',
metadata: {
userId: '123',
uploadedAt: new Date().toISOString(),
},
});
console.log(result.url); // Public URL if acl is 'public-read'
// Upload from stream
import { createReadStream } from 'fs';
const stream = createReadStream('./local-file.pdf');
await storage.upload({
key: 'documents/file.pdf',
file: stream,
contentType: 'application/pdf',
});Download Files
// Download as buffer
const content = await storage.download('uploads/avatar.jpg');
// Download as stream
const stream = await storage.downloadStream('documents/large-file.pdf');List Files
// List all files
const allFiles = await storage.list();
// List with prefix
const uploads = await storage.list('uploads/');
uploads.forEach((file) => {
console.log(file.key, file.size, file.lastModified);
});Delete Files
// Delete single file
await storage.delete('uploads/old-file.jpg');
// Delete multiple files
await storage.deleteMany(['file1.txt', 'file2.txt', 'file3.txt']);URL Generation
// Get public URL
const publicUrl = storage.getPublicUrl('uploads/avatar.jpg');
// Get signed URL (temporary access)
const signedUrl = await storage.getSignedUrl('private/document.pdf', 3600); // 1 hourCheck File Existence
const exists = await storage.exists('uploads/avatar.jpg');
if (exists) {
const metadata = await storage.getMetadata('uploads/avatar.jpg');
console.log(metadata.size, metadata.contentType);
}Features
- Path Sanitization: Automatic protection against path traversal attacks
- Metadata Storage: Store custom metadata alongside files
- Nested Directories: Automatic directory creation
- Signed URLs: Generate temporary access URLs
- Streaming Support: Upload and download large files efficiently
Future Adapters
The following storage adapters are planned for future releases:
- AWS S3: Enterprise-grade object storage
- Cloudflare R2: S3-compatible with zero egress fees
- Google Cloud Storage: GCP object storage
- Azure Blob Storage: Microsoft Azure storage
// Query multiple rows const users = await db.query('SELECT * FROM users WHERE age > ?', [18]);
// Query single row const user = await db.queryOne('SELECT * FROM users WHERE email = ?', ['[email protected]']);
#### Table Client (ORM-Style)
```typescript
interface User {
id: number;
name: string;
email: string;
age?: number;
}
// Create a record
const user = await db.table<User>('users').create({
name: 'Alice',
email: '[email protected]',
age: 30,
});
// Find one record
const found = await db.table<User>('users').findOne({ email: '[email protected]' });
// Find multiple records
const users = await db.table<User>('users').findMany({ age: 30 });
// Find all records
const allUsers = await db.table<User>('users').findMany();
// Update a record
const updated = await db.table<User>('users').update(user.id, { age: 31 });
// Delete a record
await db.table<User>('users').delete(user.id);
// Count records
const count = await db.table<User>('users').count({ age: 30 });Query Builder
// Complex queries with chaining
const users = await db
.table<User>('users')
.where({ active: true })
.orderBy('createdAt', 'desc')
.limit(10)
.offset(20)
.execute();
// Get first result
const user = await db.table<User>('users').where({ email: '[email protected]' }).first();Transactions
// Execute multiple operations in a transaction
await db.transaction(async (tx) => {
const user = await tx.table<User>('users').create({
name: 'Bob',
email: '[email protected]',
});
await tx.table<Post>('posts').create({
userId: user.id,
title: 'First Post',
content: 'Hello World!',
});
// If any operation fails, the entire transaction is rolled back
});
// Return values from transactions
const userId = await db.transaction(async (tx) => {
const user = await tx.table<User>('users').create({
name: 'Charlie',
email: '[email protected]',
});
return user.id;
});Using in Sling Functions
import { defineFunction, z } from '@slingts/core';
import type { DatabaseAdapter } from '@slingts/adapters';
export const createUser = defineFunction({
validate: z.object({
name: z.string(),
email: z.string().email(),
}),
handler: async (params, ctx) => {
const db = ctx.services.db as DatabaseAdapter;
const user = await db.table<User>('users').create({
name: params.name,
email: params.email,
});
return { success: true, user };
},
});Features
SQLite Adapter Features
- ✅ Raw SQL queries with parameterization
- ✅ ORM-style table client
- ✅ Query builder with filtering, sorting, pagination
- ✅ Transaction support with automatic rollback
- ✅ In-memory and file-based databases
- ✅ WAL mode for better concurrency
- ✅ Foreign key constraints
- ✅ Full TypeScript support
- ✅ Health checks
- ✅ Graceful shutdown
API Reference
DatabaseAdapter
Main interface for database operations.
interface DatabaseAdapter {
// Raw queries
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
queryOne<T>(sql: string, params?: unknown[]): Promise<T | null>;
execute(sql: string, params?: unknown[]): Promise<void>;
// Table access
table<T>(name: string): TableClient<T>;
// Transactions
transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T>;
// Lifecycle
connect(): Promise<void>;
disconnect(): Promise<void>;
healthCheck(): Promise<boolean>;
}TableClient
ORM-style interface for table operations.
interface TableClient<T> {
create(data: Partial<T>): Promise<T>;
findOne(where: Partial<T>): Promise<T | null>;
findMany(where?: Partial<T>): Promise<T[]>;
update(id: string | number, data: Partial<T>): Promise<T | null>;
delete(id: string | number): Promise<boolean>;
count(where?: Partial<T>): Promise<number>;
where(condition: Partial<T>): QueryBuilder<T>;
}QueryBuilder
Chainable query builder for complex queries.
interface QueryBuilder<T> {
where(condition: Partial<T>): QueryBuilder<T>;
orderBy(field: keyof T, direction?: 'asc' | 'desc'): QueryBuilder<T>;
limit(count: number): QueryBuilder<T>;
offset(count: number): QueryBuilder<T>;
execute(): Promise<T[]>;
first(): Promise<T | null>;
}License
MIT
