
Security News
TeamPCP and BreachForums Launch $1,000 Contest for Supply Chain Attacks
TeamPCP and BreachForums are promoting a Shai-Hulud supply chain attack contest with a $1,000 prize for the biggest package compromise.
@kysera/core
Advanced tools
Minimal core utilities for Kysera - Database error handling, pagination, types, and logging interface.
Core functionality has been split into focused packages:
# npm
npm install @kysera/core kysely
# pnpm
pnpm add @kysera/core kysely
# bun
bun add @kysera/core kysely
# deno
import * as core from "npm:@kysera/core"
import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'
import {
parseDatabaseError,
UniqueConstraintError,
ForeignKeyError,
paginate,
paginateCursor,
type Executor
} from '@kysera/core'
// Create database connection
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'myapp',
max: 10
})
const db = new Kysely({
dialect: new PostgresDialect({ pool })
})
// Handle database errors
try {
await db.insertInto('users').values({ email: 'duplicate@example.com' }).execute()
} catch (error) {
const dbError = parseDatabaseError(error, 'postgres')
if (dbError instanceof UniqueConstraintError) {
console.error(`Duplicate: ${dbError.columns.join(', ')}`)
}
}
// Paginate results (offset-based)
const users = await paginate(db.selectFrom('users').selectAll().orderBy('created_at', 'desc'), {
page: 1,
limit: 20
})
// Cursor-based pagination for large datasets
const posts = await paginateCursor(db.selectFrom('posts').selectAll(), {
orderBy: [{ column: 'id', direction: 'asc' }],
limit: 20
})
The error handling system provides unified error parsing across PostgreSQL, MySQL, SQLite, and MSSQL with a rich error hierarchy.
DatabaseError (base class)
โโโ UniqueConstraintError
โโโ ForeignKeyError
โโโ NotNullError
โโโ CheckConstraintError
โโโ NotFoundError
โโโ BadRequestError
The parseDatabaseError function automatically detects and parses database-specific errors into a unified format.
| Error Code | Type | Description |
|---|---|---|
23505 | UniqueConstraintError | UNIQUE constraint violation |
23503 | ForeignKeyError | FOREIGN KEY constraint violation |
23502 | NotNullError | NOT NULL constraint violation |
23514 | CheckConstraintError | CHECK constraint violation |
| Error Code | Type | Description |
|---|---|---|
ER_DUP_ENTRY | UniqueConstraintError | Duplicate entry |
ER_DUP_KEY | UniqueConstraintError | Duplicate key |
ER_NO_REFERENCED_ROW | ForeignKeyError | Foreign key violation |
ER_ROW_IS_REFERENCED | ForeignKeyError | Foreign key violation |
ER_BAD_NULL_ERROR | NotNullError | NOT NULL violation |
ER_NO_DEFAULT_FOR_FIELD | NotNullError | Required field missing |
| Message Pattern | Type | Description |
|---|---|---|
UNIQUE constraint failed | UniqueConstraintError | Unique violation |
FOREIGN KEY constraint failed | ForeignKeyError | Foreign key violation |
NOT NULL constraint failed | NotNullError | NOT NULL violation |
CHECK constraint failed | CheckConstraintError | CHECK violation |
| Error Code | Type | Description |
|---|---|---|
2627 | UniqueConstraintError | UNIQUE constraint violation |
2601 | UniqueConstraintError | Duplicate key index |
515 | NotNullError | NOT NULL constraint violation |
547 | ForeignKeyError | FOREIGN KEY violation |
@kysera/core provides a comprehensive, unified error code system used across the entire Kysera ecosystem:
import { ErrorCodes, getErrorCategory, isValidErrorCode } from '@kysera/core'
// All error codes follow the format: CATEGORY_SUBCATEGORY_SPECIFIC
// Examples:
ErrorCodes.DB_CONNECTION_FAILED
ErrorCodes.VALIDATION_UNIQUE_VIOLATION
ErrorCodes.MIGRATION_UP_FAILED
ErrorCodes.PLUGIN_INIT_FAILED
// Utility functions
const category = getErrorCategory('DB_CONNECTION_FAILED') // 'DB'
const isValid = isValidErrorCode('DB_CONNECTION_FAILED') // true
Error Code Categories:
DB_* - Database errors (connection, query, transaction)VALIDATION_* - Validation and constraint errorsRESOURCE_* - Resource errors (not found, conflict)MIGRATION_* - Migration errorsPLUGIN_* - Plugin system errorsAUDIT_* - Audit system errorsCONFIG_* - Configuration errorsFS_* - File system errorsNETWORK_* - Network errorsimport { parseDatabaseError, UniqueConstraintError } from '@kysera/core'
try {
await db.insertInto('users').values({ email: 'existing@example.com', name: 'John' }).execute()
} catch (error) {
const dbError = parseDatabaseError(error, 'postgres')
if (dbError instanceof UniqueConstraintError) {
console.error(`Duplicate value in ${dbError.table}.${dbError.columns.join(', ')}`)
console.error(`Constraint: ${dbError.constraint}`)
}
}
import {
parseDatabaseError,
UniqueConstraintError,
ForeignKeyError,
NotFoundError,
NotNullError,
CheckConstraintError
} from '@kysera/core'
async function createPost(userId: number, title: string) {
try {
return await db
.insertInto('posts')
.values({ user_id: userId, title, content: '...' })
.returningAll()
.executeTakeFirstOrThrow()
} catch (error) {
const dbError = parseDatabaseError(error, 'postgres')
if (dbError instanceof ForeignKeyError) {
throw new Error(`User ${userId} does not exist`)
}
if (dbError instanceof UniqueConstraintError) {
throw new Error(`Post with title "${title}" already exists`)
}
if (dbError instanceof NotNullError) {
throw new Error(`Required field ${dbError.column} is missing`)
}
// Generic database error
throw new Error(`Database error: ${dbError.message}`)
}
}
All error types support JSON serialization for logging and API responses:
const dbError = parseDatabaseError(error, 'postgres')
console.log(JSON.stringify(dbError.toJSON(), null, 2))
// {
// "name": "UniqueConstraintError",
// "message": "UNIQUE constraint violation on users",
// "code": "VALIDATION_UNIQUE_VIOLATION",
// "constraint": "users_email_key",
// "table": "users",
// "columns": ["email"]
// }
import { NotFoundError } from '@kysera/core'
const user = await db.selectFrom('users').selectAll().where('id', '=', userId).executeTakeFirst()
if (!user) {
throw new NotFoundError('User', { id: userId })
}
import { BadRequestError } from '@kysera/core'
if (!email.includes('@')) {
throw new BadRequestError('Invalid email format')
}
Lightweight utility functions for common query patterns.
Lightweight limit/offset pagination without COUNT(*) query (~50% faster than paginate):
import { applyOffset } from '@kysera/core'
// Simple offset pagination
const users = await applyOffset(db.selectFrom('users').selectAll().orderBy('id'), {
limit: 20,
offset: 0
}).execute()
// Infinite scroll pattern
async function loadMore(offset: number) {
const posts = await applyOffset(
db.selectFrom('posts').selectAll().where('published', '=', true).orderBy('created_at', 'desc'),
{ limit: 20, offset }
).execute()
return {
posts,
hasMore: posts.length === 20
}
}
Options:
limit: 1-100 (default: 20, bounded for safety)offset: >= 0 (default: 0)Apply date range filter to a query:
import { applyDateRange } from '@kysera/core'
// Date range filtering
const posts = await applyDateRange(db.selectFrom('posts').selectAll(), 'created_at', {
from: new Date('2024-01-01'),
to: new Date('2024-12-31')
}).execute()
// Combine with offset
const analytics = await applyOffset(
applyDateRange(db.selectFrom('events').selectAll().orderBy('created_at', 'desc'), 'created_at', {
from: startDate,
to: endDate
}),
{ limit: 100 }
).execute()
Options:
from: Start date (inclusive)to: End date (inclusive)Two pagination strategies: offset-based (simple) and cursor-based (scalable).
Best for: Small to medium datasets, UIs with page numbers.
Pagination Bounds:
MAX_PAGE = 1,000,000 (maximum page number)MAX_LIMIT = 10,000 (maximum records per page)import { paginate } from '@kysera/core'
const result = await paginate(db.selectFrom('users').selectAll().orderBy('created_at', 'desc'), {
page: 2,
limit: 20
})
console.log(`Page ${result.pagination.page} of ${result.pagination.totalPages}`)
console.log(`Total records: ${result.pagination.total}`)
console.log(`Has next: ${result.pagination.hasNext}`)
console.log(`Has prev: ${result.pagination.hasPrev}`)
result.data.forEach(user => {
console.log(`${user.id}: ${user.name}`)
})
{
page: 1, // Start from page 1 (max: 1,000,000)
limit: 20 // Default 20 (max: 10,000, min: 1)
}
const result = await paginate(
db
.selectFrom('posts')
.innerJoin('users', 'users.id', 'posts.user_id')
.select(['posts.id', 'posts.title', 'posts.created_at', 'users.name as author'])
.where('posts.published', '=', true)
.orderBy('posts.created_at', 'desc'),
{ page: 1, limit: 10 }
)
Best for: Large datasets, infinite scroll, real-time feeds, APIs.
Pagination Bounds:
MAX_LIMIT = 10,000 records per pageimport { paginateCursor } from '@kysera/core'
// First page
const page1 = await paginateCursor(db.selectFrom('posts').selectAll(), {
orderBy: [{ column: 'id', direction: 'asc' }],
limit: 20
})
console.log(`Loaded ${page1.data.length} posts`)
console.log(`Has next: ${page1.pagination.hasNext}`)
// Next page
if (page1.pagination.nextCursor) {
const page2 = await paginateCursor(db.selectFrom('posts').selectAll(), {
orderBy: [{ column: 'id', direction: 'asc' }],
cursor: page1.pagination.nextCursor,
limit: 20
})
}
const result = await paginateCursor(db.selectFrom('posts').selectAll(), {
orderBy: [
{ column: 'score', direction: 'desc' }, // Primary sort
{ column: 'created_at', direction: 'desc' } // Secondary sort (tie-breaker)
],
limit: 20
})
Cursors are base64-encoded for security and compactness using cross-runtime compatible Base64 encoding (uses browser's btoa/atob in browsers, Node.js Buffer in Node.js):
Single column: base64(column):base64(value)
Multi-column: base64(JSON.stringify({column1: value1, column2: value2}))
// Example cursor decoding (internal):
// "aWQ=:MTA=" โ { id: 10 }
// "eyJzY29yZSI6NTAsImNyZWF0ZWRfYXQiOiIyMDI0LTAxLTAxIn0=" โ { score: 50, created_at: "2024-01-01" }
| Strategy | Query Complexity | Dataset Size | Use Case |
|---|---|---|---|
| Offset | O(n) at high pages | Small-Medium (<100k) | Admin panels, page numbers |
| Cursor | O(log n) with index | Large (millions+) | Feeds, infinite scroll, APIs |
WHERE column > value โ O(log n) with indexIndex Recommendation:
-- For multi-column cursor pagination
CREATE INDEX idx_posts_score_created ON posts(score DESC, created_at DESC);
MSSQL has unique requirements for pagination that are handled automatically when you specify dialect: 'mssql':
MSSQL requires an explicit ORDER BY clause when using OFFSET pagination. The implementation uses the OFFSET ... ROWS FETCH NEXT ... ROWS ONLY syntax:
// MSSQL pagination automatically uses OFFSET/FETCH NEXT
const result = await paginate(
db.selectFrom('users')
.selectAll()
.orderBy('id', 'asc'), // ORDER BY required for MSSQL
{ page: 1, limit: 20, dialect: 'mssql' }
)
// Generated SQL (MSSQL):
// SELECT * FROM users ORDER BY id ASC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY
MSSQL cursor pagination uses the TOP clause for optimal performance:
const result = await paginateCursor(
db.selectFrom('posts').selectAll(),
{
orderBy: [{ column: 'id', direction: 'asc' }],
limit: 20,
dialect: 'mssql'
}
)
// Generated SQL (MSSQL):
// SELECT TOP(21) * FROM posts WHERE id > @cursor ORDER BY id ASC
Important MSSQL Requirements:
dialect: 'mssql' to automatically generate correct syntaxThe Executor<DB> type accepts both Kysely<DB> and Transaction<DB>, enabling dependency injection:
import type { Executor } from '@kysera/core'
class UserRepository {
async findById(executor: Executor<Database>, id: number) {
return await executor.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirst()
}
async create(executor: Executor<Database>, data: NewUser) {
return await executor.insertInto('users').values(data).returningAll().executeTakeFirstOrThrow()
}
}
// Usage with db
const user = await repo.findById(db, 123)
// Usage with transaction
await db.transaction().execute(async trx => {
const user = await repo.findById(trx, 123)
await repo.create(trx, { email: 'new@example.com' })
})
import type { Timestamps, SoftDelete, AuditFields } from '@kysera/core'
import type { Generated } from 'kysely'
interface UsersTable extends Timestamps, SoftDelete, AuditFields {
id: Generated<number>
email: string
name: string
// Timestamps: created_at, updated_at
// SoftDelete: deleted_at
// AuditFields: created_by, updated_by
}
import type { Selectable, Insertable, Updateable } from '@kysely/core'
import type { Generated, ColumnType } from 'kysely'
interface UsersTable {
id: Generated<number>
email: string
name: string
created_at: ColumnType<Date, never, never> // Read-only
}
type User = Selectable<UsersTable>
// { id: number, email: string, name: string, created_at: Date }
type NewUser = Insertable<UsersTable>
// { email: string, name: string }
type UserUpdate = Updateable<UsersTable>
// { email?: string, name?: string }
The QueryMetrics interface is used by debug and infrastructure packages:
import type { QueryMetrics } from '@kysera/core'
interface QueryMetrics {
sql: string
params?: unknown[]
duration: number
timestamp: number
}
Core provides base abstractions for creating Kysera plugins, reducing boilerplate and ensuring consistent behavior across the plugin ecosystem.
Common options interface shared by all Kysera plugins:
import type { BasePluginOptions, BasePluginOptionsWithPrimaryKey } from '@kysera/core'
// Base options for all plugins
interface BasePluginOptions {
logger?: KyseraLogger // Default: silentLogger
tables?: string[] // Whitelist tables (undefined = all)
excludeTables?: string[] // Blacklist tables
}
// Extended options for plugins needing primary key
interface BasePluginOptionsWithPrimaryKey extends BasePluginOptions {
primaryKeyColumn?: string // Default: 'id'
}
Standardizes plugin configuration with sensible defaults:
import { createPluginConfig, type BasePluginOptionsWithPrimaryKey } from '@kysera/core'
interface MyPluginOptions extends BasePluginOptionsWithPrimaryKey {
customOption: string
}
export function myPlugin(options: MyPluginOptions = {}): Plugin {
const config = createPluginConfig('my-plugin', options)
// config.logger - Configured logger (defaults to silentLogger)
// config.tables - Whitelist (undefined = all tables)
// config.excludeTables - Blacklist (defaults to [])
// config.primaryKeyColumn - Primary key (defaults to 'id')
config.logger.debug('Initializing my-plugin')
return {
name: config.name,
// ... plugin implementation
}
}
Creates plugin metadata for dependency management and conflict detection:
import { createPluginMetadata, PLUGIN_PRIORITIES } from '@kysera/core'
const metadata = createPluginMetadata('soft-delete', '0.8.0', {
priority: PLUGIN_PRIORITIES.FILTER,
dependencies: ['executor'],
conflictsWith: ['hard-delete-only']
})
// { name: 'soft-delete', version: '0.8.0', priority: 500, ... }
Recommended priority values for consistent plugin ordering:
import { PLUGIN_PRIORITIES } from '@kysera/core'
// Higher priority = runs first
PLUGIN_PRIORITIES.SECURITY // 1000 - RLS, authentication (run first)
PLUGIN_PRIORITIES.FILTER // 500 - Soft delete, tenant isolation
PLUGIN_PRIORITIES.TRANSFORM // 100 - Timestamps, data transformation
PLUGIN_PRIORITIES.AUDIT // 50 - Audit logging, change tracking
PLUGIN_PRIORITIES.DEFAULT // 0 - Default priority
PLUGIN_PRIORITIES.DEBUG // -100 - Query logging, profiling (run last)
// Resolved plugin configuration
interface ResolvedPluginConfig {
readonly name: string
readonly logger: KyseraLogger
readonly tables: string[] | undefined
readonly excludeTables: string[]
readonly primaryKeyColumn: string
}
// Plugin metadata
interface PluginMetadata {
name: string
version: string
dependencies?: readonly string[]
priority?: number
conflictsWith?: readonly string[]
}
// Priority type
type PluginPriority = 1000 | 500 | 100 | 50 | 0 | -100
Core provides a shared logger interface for consistency across the Kysera ecosystem:
import { type KyseraLogger, consoleLogger, silentLogger, createPrefixedLogger } from '@kysera/core'
// Console logger (default)
const logger = consoleLogger
logger.info('Application started')
logger.warn('High memory usage')
logger.error('Connection failed')
// Silent logger (for production)
const silent = silentLogger
// Prefixed logger
const dbLogger = createPrefixedLogger('database', consoleLogger)
dbLogger.info('Query executed') // [kysera:info] [database] Query executed
// Custom logger implementation
const customLogger: KyseraLogger = {
debug: (msg, ...args) => winston.debug(msg, ...args),
info: (msg, ...args) => winston.info(msg, ...args),
warn: (msg, ...args) => winston.warn(msg, ...args),
error: (msg, ...args) => winston.error(msg, ...args)
}
parseDatabaseError(error: unknown, dialect: DatabaseDialect): DatabaseErrorParse database-specific errors into unified format.
Parameters:
error - Original database errordialect - 'postgres' | 'mysql' | 'sqlite' | 'mssql'Returns: DatabaseError or subclass
class DatabaseError extends ErrorBase error class with serialization support.
Properties:
code: string - Error codedetail?: string - Additional detailstoJSON(): object - Serialize to JSONclass UniqueConstraintError extends DatabaseErrorUNIQUE constraint violation.
Properties:
constraint: string - Constraint nametable: string - Table namecolumns: string[] - Affected columnsclass ForeignKeyError extends DatabaseErrorFOREIGN KEY constraint violation.
Properties:
constraint: string - Constraint nametable: string - Table namereferencedTable: string - Referenced tableclass NotNullError extends DatabaseErrorNOT NULL constraint violation.
Properties:
column: string - Column nametable?: string - Table nameclass CheckConstraintError extends DatabaseErrorCHECK constraint violation.
Properties:
constraint: string - Constraint nametable?: string - Table nameclass NotFoundError extends DatabaseErrorEntity not found.
Constructor:
entity: string - Entity namefilters?: Record<string, unknown> - Search filtersclass BadRequestError extends DatabaseErrorInvalid request/data.
ErrorCodesObject containing all unified error codes.
Examples:
ErrorCodes.DB_CONNECTION_FAILED
ErrorCodes.VALIDATION_UNIQUE_VIOLATION
ErrorCodes.RESOURCE_NOT_FOUND
getErrorCategory(code: string): stringGet error category from error code.
Returns: Category prefix (e.g., 'DB', 'VALIDATION')
isValidErrorCode(code: string): booleanType guard to check if a string is a valid error code.
paginate<DB, TB, O>(query: SelectQueryBuilder<DB, TB, O>, options?: PaginationOptions): Promise<PaginatedResult<O>>Offset-based pagination.
Options:
interface PaginationOptions {
page?: number // Default: 1
limit?: number // Default: 20, max: 10,000
dialect?: 'postgres' | 'mysql' | 'sqlite' | 'mssql' // For MSSQL-specific syntax
}
Returns:
interface PaginatedResult<T> {
data: T[]
pagination: {
page: number
limit: number
total: number
totalPages: number
hasNext: boolean
hasPrev: boolean
}
}
paginateCursor<DB, TB, O>(query: SelectQueryBuilder<DB, TB, O>, options: CursorOptions<O>): Promise<PaginatedResult<O>>Cursor-based pagination.
Options:
interface CursorOptions<T> {
orderBy: Array<{
column: keyof T & string
direction: 'asc' | 'desc'
}>
cursor?: string
limit?: number // Default: 20
dialect?: 'postgres' | 'mysql' | 'sqlite' | 'mssql' // For MSSQL-specific syntax
}
Returns:
interface PaginatedResult<T> {
data: T[]
pagination: {
limit: number
hasNext: boolean
nextCursor?: string
}
}
type Executor<DB> = Kysely<DB> | Transaction<DB>Universal executor type.
interface TimestampsTimestamp columns.
{ created_at: Date, updated_at?: Date }
interface SoftDeleteSoft delete column.
{
deleted_at: Date | null
}
interface AuditFieldsAudit columns.
{ created_by?: number, updated_by?: number }
interface QueryMetricsQuery performance metrics.
{
sql: string
params?: unknown[]
duration: number
timestamp: number
}
interface KyseraLoggerLogger interface for Kysera ecosystem.
Methods:
debug(message: string, ...args: unknown[]): voidinfo(message: string, ...args: unknown[]): voidwarn(message: string, ...args: unknown[]): voiderror(message: string, ...args: unknown[]): voidconsoleLogger: KyseraLoggerSimple console logger implementation.
silentLogger: KyseraLoggerNo-op logger for silent operation.
createPrefixedLogger(prefix: string, baseLogger?: KyseraLogger): KyseraLoggerCreate a logger with a specific prefix.
Features have been moved to specialized packages for better modularity:
// Before (in @kysera/core)
import { checkDatabaseHealth, createMetricsPool, HealthMonitor } from '@kysera/core'
// After
import { checkDatabaseHealth, createMetricsPool, HealthMonitor } from '@kysera/infra'
// Before
import { withRetry, CircuitBreaker, isTransientError } from '@kysera/core'
// After
import { withRetry, CircuitBreaker, isTransientError } from '@kysera/infra'
// Before
import { createGracefulShutdown, shutdownDatabase } from '@kysera/core'
// After
import { createGracefulShutdown, shutdownDatabase } from '@kysera/infra'
// Before
import { testInTransaction, createFactory, cleanDatabase } from '@kysera/core'
// After
import { testInTransaction, createFactory, cleanDatabase } from '@kysera/testing'
// Before
import { withDebug, QueryProfiler, formatSQL } from '@kysera/core'
// After
import { withDebug, QueryProfiler, formatSQL } from '@kysera/debug'
Core now focuses on fundamental utilities:
@kysera/core supports testing against multiple databases simultaneously using environment variables. This ensures error handling works correctly across all supported databases.
Use these environment variables to enable specific database tests:
# Test with PostgreSQL (default, always runs)
pnpm test
# Test with MySQL
TEST_MYSQL=1 pnpm test
# Test with MSSQL
TEST_MSSQL=1 pnpm test
# Test with all databases
TEST_POSTGRES=1 TEST_MYSQL=1 TEST_MSSQL=1 pnpm test
Each database requires proper setup before running tests:
# Using Docker
docker run -d \
--name kysera-postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=kysera_test \
-p 5432:5432 \
postgres:16-alpine
# Connection string
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/kysera_test
# Using Docker
docker run -d \
--name kysera-mysql \
-e MYSQL_ROOT_PASSWORD=mysql \
-e MYSQL_DATABASE=kysera_test \
-p 3306:3306 \
mysql:8
# Connection string
DATABASE_URL=mysql://root:mysql@localhost:3306/kysera_test
# Using Docker
docker run -d \
--name kysera-mssql \
-e "ACCEPT_EULA=Y" \
-e "SA_PASSWORD=YourStrong@Passw0rd" \
-p 1433:1433 \
mcr.microsoft.com/mssql/server:2022-latest
# Connection string
DATABASE_URL=sqlserver://sa:YourStrong@Passw0rd@localhost:1433/kysera_test
In CI/CD pipelines, you can run tests against all databases in parallel:
# GitHub Actions example
strategy:
matrix:
database: [postgres, mysql, mssql]
steps:
- name: Run tests
run: TEST_${{ matrix.database | upper }}=1 pnpm test
The error handling system is tested against all supported databases to ensure consistent behavior:
Each test verifies that parseDatabaseError correctly identifies and parses database-specific error codes into unified error types.
// โ
Good: Use unified error codes
import { ErrorCodes } from '@kysera/core'
throw new DatabaseError('Connection failed', ErrorCodes.DB_CONNECTION_FAILED)
// โ Bad: Hard-coded strings
throw new DatabaseError('Connection failed', 'CONNECTION_ERROR')
try {
await createUser(email)
} catch (error) {
const dbError = parseDatabaseError(error, 'postgres')
if (dbError instanceof UniqueConstraintError) {
throw new Error('Email already registered')
}
if (dbError instanceof ForeignKeyError) {
throw new Error('Related record not found')
}
throw new Error('Failed to create user')
}
// โ Bad for large datasets
const result = await paginate(query, { page: 1000, limit: 20 })
// Offset 19980 - scans 20k rows!
// โ
Good: O(log n) with index
const result = await paginateCursor(query, {
orderBy: [{ column: 'id', direction: 'asc' }],
cursor,
limit: 20
})
// โ
Works with both db and transactions
async function findUser(executor: Executor<DB>, id: number) {
return executor.selectFrom('users').selectAll().where('id', '=', id).executeTakeFirst()
}
// Usage
await findUser(db, 123)
await db.transaction().execute(trx => findUser(trx, 123))
import type { Timestamps, SoftDelete, Selectable, Insertable } from '@kysera/core'
interface UsersTable extends Timestamps, SoftDelete {
id: Generated<number>
email: string
name: string
}
type User = Selectable<UsersTable>
type NewUser = Insertable<UsersTable>
MIT ยฉ Kysera
Built with โค๏ธ for production TypeScript applications
FAQs
Core utilities for Kysely - errors, pagination, types, logger
The npm package @kysera/core receives a total of 99 weekly downloads. As such, @kysera/core popularity was classified as not popular.
We found that @kysera/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
TeamPCP and BreachForums are promoting a Shai-Hulud supply chain attack contest with a $1,000 prize for the biggest package compromise.

Security News
Packagist urges PHP projects to update Composer after a GitHub token format change exposed some GitHub Actions tokens in CI logs.

Research
GemStuffer abuses RubyGems as an exfiltration channel, packaging scraped UK council portal data into junk gems published from new accounts.