duckpond 0.2.0 → 0.2.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/DuckPond.js"],"names":[],"mappings":"AAAA,+HAAkC,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,sCAAsB","file":"/home/jordanburke/IdeaProjects/duckpond/dist/DuckPond.js"}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/DuckPond.js"],"names":[],"mappings":"AAAA,+HAAkC,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,sCAAsB","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/DuckPond.js"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/cache/LRUCache.js"],"names":[],"mappings":"AAAA,gIAAmC,gCAA6B,gCAA6B,sCAAsB","file":"/home/jordanburke/IdeaProjects/duckpond/dist/cache/LRUCache.js"}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/cache/LRUCache.js"],"names":[],"mappings":"AAAA,gIAAmC,gCAA6B,gCAA6B,sCAAsB","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/cache/LRUCache.js"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"names":["ErrorCode"],"mappings":"AA8JO,6EAAKA,CAAAA,CAAAA,CAAAA,CAAAA,EAAAA,CAEVA,CAAAA,CAAA,iBAAA,CAAoB,mBAAA,CACpBA,CAAAA,CAAA,kBAAA,CAAqB,oBAAA,CACrBA,CAAAA,CAAA,mBAAA,CAAsB,qBAAA,CACtBA,CAAAA,CAAA,mBAAA,CAAsB,qBAAA,CAGtBA,CAAAA,CAAA,cAAA,CAAiB,gBAAA,CACjBA,CAAAA,CAAA,mBAAA,CAAsB,qBAAA,CACtBA,CAAAA,CAAA,iBAAA,CAAoB,mBAAA,CAGpBA,CAAAA,CAAA,qBAAA,CAAwB,uBAAA,CACxBA,CAAAA,CAAA,aAAA,CAAgB,eAAA,CAChBA,CAAAA,CAAA,WAAA,CAAc,aAAA,CAGdA,CAAAA,CAAA,qBAAA,CAAwB,uBAAA,CACxBA,CAAAA,CAAA,aAAA,CAAgB,eAAA,CAChBA,CAAAA,CAAA,sBAAA,CAAyB,wBAAA,CAGzBA,CAAAA,CAAA,cAAA,CAAiB,gBAAA,CACjBA,CAAAA,CAAA,eAAA,CAAkB,iBAAA,CAGlBA,CAAAA,CAAA,aAAA,CAAgB,eAAA,CA3BNA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,EAAA,CAAA,CAAA,CAAA,CAAA,cAAA","file":"/home/jordanburke/IdeaProjects/duckpond/dist/chunk-2PPSIE57.js","sourcesContent":["import type { DuckDBConnection } from \"@duckdb/node-api\"\nimport type { Either } from \"functype/either\"\nimport type { List } from \"functype/list\"\nimport type { Option } from \"functype/option\"\n\n/**\n * Configuration for DuckPond manager\n */\nexport interface DuckPondConfig {\n // R2 Configuration (Cloudflare)\n r2?: {\n accountId: string\n accessKeyId: string\n secretAccessKey: string\n bucket: string\n }\n\n // S3 Configuration (AWS)\n s3?: {\n region: string\n accessKeyId: string\n secretAccessKey: string\n bucket: string\n endpoint?: string // For S3-compatible services\n }\n\n // DuckDB Settings\n memoryLimit?: string // Default: '4GB'\n threads?: number // Default: 4\n tempDir?: string // Default: '/tmp/duckpond'\n\n // Cache Settings\n maxActiveUsers?: number // Default: 10\n evictionTimeout?: number // Default: 300000 (5 min in ms)\n cacheType?: \"disk\" | \"memory\" | \"noop\" // Default: 'disk'\n cacheDir?: string // Default: '/tmp/duckpond-cache'\n\n // Storage Strategy\n strategy?: \"parquet\" | \"duckdb\" | \"hybrid\" // Default: 'parquet'\n}\n\n/**\n * Represents an active user database connection\n */\nexport interface UserDatabase {\n userId: string\n connection: DuckDBConnection\n lastAccess: Date\n attached: boolean\n memoryUsage?: number\n}\n\n/**\n * Statistics about a user's database\n */\nexport interface UserStats {\n userId: string\n attached: boolean\n lastAccess: Date\n memoryUsage: number\n storageUsage: number\n queryCount: number\n}\n\n/**\n * Database schema information\n */\nexport interface Schema {\n tables: TableSchema[]\n}\n\nexport interface TableSchema {\n name: string\n columns: ColumnSchema[]\n rowCount?: number\n}\n\nexport interface ColumnSchema {\n name: string\n type: string\n nullable: boolean\n}\n\n/**\n * Options for creating a new user\n */\nexport interface CreateUserOptions {\n template?: string\n initialData?: Record<string, unknown[]>\n metadata?: Record<string, unknown>\n}\n\n/**\n * Storage usage statistics\n */\nexport interface StorageStats {\n totalSize: number\n fileCount: number\n lastModified: Date\n files?: FileInfo[]\n}\n\nexport interface FileInfo {\n path: string\n size: number\n modified: Date\n}\n\n/**\n * Query result with metadata\n */\nexport interface QueryResult<T = unknown> {\n rows: T[]\n rowCount: number\n executionTime: number\n columns: string[]\n}\n\n/**\n * Metrics for monitoring\n */\nexport interface DuckPondMetrics {\n activeUsers: number\n totalQueries: number\n avgQueryTime: number\n cacheHitRate: number\n memoryUsage: number\n storageUsage: number\n}\n\n/**\n * Events emitted by DuckPond\n */\nexport type DuckPondEvent =\n | { type: \"user:attached\"; userId: string; timestamp: Date }\n | { type: \"user:detached\"; userId: string; reason: \"eviction\" | \"manual\"; timestamp: Date }\n | { type: \"query:executed\"; userId: string; duration: number; timestamp: Date }\n | { type: \"query:failed\"; userId: string; error: string; timestamp: Date }\n | { type: \"cache:hit\"; userId: string }\n | { type: \"cache:miss\"; userId: string }\n | { type: \"error\"; error: Error; timestamp: Date }\n\n/**\n * Type-safe result types using functype Either\n */\nexport type DuckPondResult<T> = Either<DuckPondError, T>\nexport type AsyncDuckPondResult<T> = Promise<Either<DuckPondError, T>>\n\n/**\n * Error types\n */\nexport interface DuckPondError {\n code: ErrorCode\n message: string\n cause?: Error\n context?: Record<string, unknown>\n}\n\nexport enum ErrorCode {\n // Connection errors\n CONNECTION_FAILED = \"CONNECTION_FAILED\",\n CONNECTION_TIMEOUT = \"CONNECTION_TIMEOUT\",\n R2_CONNECTION_ERROR = \"R2_CONNECTION_ERROR\",\n S3_CONNECTION_ERROR = \"S3_CONNECTION_ERROR\",\n\n // User errors\n USER_NOT_FOUND = \"USER_NOT_FOUND\",\n USER_ALREADY_EXISTS = \"USER_ALREADY_EXISTS\",\n USER_NOT_ATTACHED = \"USER_NOT_ATTACHED\",\n\n // Query errors\n QUERY_EXECUTION_ERROR = \"QUERY_EXECUTION_ERROR\",\n QUERY_TIMEOUT = \"QUERY_TIMEOUT\",\n INVALID_SQL = \"INVALID_SQL\",\n\n // Resource errors\n MEMORY_LIMIT_EXCEEDED = \"MEMORY_LIMIT_EXCEEDED\",\n STORAGE_ERROR = \"STORAGE_ERROR\",\n STORAGE_QUOTA_EXCEEDED = \"STORAGE_QUOTA_EXCEEDED\",\n\n // Configuration errors\n INVALID_CONFIG = \"INVALID_CONFIG\",\n NOT_INITIALIZED = \"NOT_INITIALIZED\",\n\n // Unknown\n UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n}\n\n/**\n * Configuration with defaults applied\n */\nexport type ResolvedConfig = Required<\n Omit<DuckPondConfig, \"r2\" | \"s3\"> & {\n r2: DuckPondConfig[\"r2\"]\n s3: DuckPondConfig[\"s3\"]\n }\n>\n\n/**\n * Result of listUsers operation\n */\nexport interface ListUsersResult {\n users: List<string> // List of cached user IDs (functype immutable list)\n count: number // Number of cached users\n maxActiveUsers: number // Maximum cache capacity\n utilizationPercent: number // Cache utilization (0-100)\n}\n"]}
1
+ {"version":3,"sources":["../src/types.ts"],"names":["ErrorCode"],"mappings":"AA8JO,6EAAKA,CAAAA,CAAAA,CAAAA,CAAAA,EAAAA,CAEVA,CAAAA,CAAA,iBAAA,CAAoB,mBAAA,CACpBA,CAAAA,CAAA,kBAAA,CAAqB,oBAAA,CACrBA,CAAAA,CAAA,mBAAA,CAAsB,qBAAA,CACtBA,CAAAA,CAAA,mBAAA,CAAsB,qBAAA,CAGtBA,CAAAA,CAAA,cAAA,CAAiB,gBAAA,CACjBA,CAAAA,CAAA,mBAAA,CAAsB,qBAAA,CACtBA,CAAAA,CAAA,iBAAA,CAAoB,mBAAA,CAGpBA,CAAAA,CAAA,qBAAA,CAAwB,uBAAA,CACxBA,CAAAA,CAAA,aAAA,CAAgB,eAAA,CAChBA,CAAAA,CAAA,WAAA,CAAc,aAAA,CAGdA,CAAAA,CAAA,qBAAA,CAAwB,uBAAA,CACxBA,CAAAA,CAAA,aAAA,CAAgB,eAAA,CAChBA,CAAAA,CAAA,sBAAA,CAAyB,wBAAA,CAGzBA,CAAAA,CAAA,cAAA,CAAiB,gBAAA,CACjBA,CAAAA,CAAA,eAAA,CAAkB,iBAAA,CAGlBA,CAAAA,CAAA,aAAA,CAAgB,eAAA,CA3BNA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,EAAA,CAAA,CAAA,CAAA,CAAA,cAAA","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-2PPSIE57.js","sourcesContent":["import type { DuckDBConnection } from \"@duckdb/node-api\"\nimport type { Either } from \"functype/either\"\nimport type { List } from \"functype/list\"\nimport type { Option } from \"functype/option\"\n\n/**\n * Configuration for DuckPond manager\n */\nexport interface DuckPondConfig {\n // R2 Configuration (Cloudflare)\n r2?: {\n accountId: string\n accessKeyId: string\n secretAccessKey: string\n bucket: string\n }\n\n // S3 Configuration (AWS)\n s3?: {\n region: string\n accessKeyId: string\n secretAccessKey: string\n bucket: string\n endpoint?: string // For S3-compatible services\n }\n\n // DuckDB Settings\n memoryLimit?: string // Default: '4GB'\n threads?: number // Default: 4\n tempDir?: string // Default: '/tmp/duckpond'\n\n // Cache Settings\n maxActiveUsers?: number // Default: 10\n evictionTimeout?: number // Default: 300000 (5 min in ms)\n cacheType?: \"disk\" | \"memory\" | \"noop\" // Default: 'disk'\n cacheDir?: string // Default: '/tmp/duckpond-cache'\n\n // Storage Strategy\n strategy?: \"parquet\" | \"duckdb\" | \"hybrid\" // Default: 'parquet'\n}\n\n/**\n * Represents an active user database connection\n */\nexport interface UserDatabase {\n userId: string\n connection: DuckDBConnection\n lastAccess: Date\n attached: boolean\n memoryUsage?: number\n}\n\n/**\n * Statistics about a user's database\n */\nexport interface UserStats {\n userId: string\n attached: boolean\n lastAccess: Date\n memoryUsage: number\n storageUsage: number\n queryCount: number\n}\n\n/**\n * Database schema information\n */\nexport interface Schema {\n tables: TableSchema[]\n}\n\nexport interface TableSchema {\n name: string\n columns: ColumnSchema[]\n rowCount?: number\n}\n\nexport interface ColumnSchema {\n name: string\n type: string\n nullable: boolean\n}\n\n/**\n * Options for creating a new user\n */\nexport interface CreateUserOptions {\n template?: string\n initialData?: Record<string, unknown[]>\n metadata?: Record<string, unknown>\n}\n\n/**\n * Storage usage statistics\n */\nexport interface StorageStats {\n totalSize: number\n fileCount: number\n lastModified: Date\n files?: FileInfo[]\n}\n\nexport interface FileInfo {\n path: string\n size: number\n modified: Date\n}\n\n/**\n * Query result with metadata\n */\nexport interface QueryResult<T = unknown> {\n rows: T[]\n rowCount: number\n executionTime: number\n columns: string[]\n}\n\n/**\n * Metrics for monitoring\n */\nexport interface DuckPondMetrics {\n activeUsers: number\n totalQueries: number\n avgQueryTime: number\n cacheHitRate: number\n memoryUsage: number\n storageUsage: number\n}\n\n/**\n * Events emitted by DuckPond\n */\nexport type DuckPondEvent =\n | { type: \"user:attached\"; userId: string; timestamp: Date }\n | { type: \"user:detached\"; userId: string; reason: \"eviction\" | \"manual\"; timestamp: Date }\n | { type: \"query:executed\"; userId: string; duration: number; timestamp: Date }\n | { type: \"query:failed\"; userId: string; error: string; timestamp: Date }\n | { type: \"cache:hit\"; userId: string }\n | { type: \"cache:miss\"; userId: string }\n | { type: \"error\"; error: Error; timestamp: Date }\n\n/**\n * Type-safe result types using functype Either\n */\nexport type DuckPondResult<T> = Either<DuckPondError, T>\nexport type AsyncDuckPondResult<T> = Promise<Either<DuckPondError, T>>\n\n/**\n * Error types\n */\nexport interface DuckPondError {\n code: ErrorCode\n message: string\n cause?: Error\n context?: Record<string, unknown>\n}\n\nexport enum ErrorCode {\n // Connection errors\n CONNECTION_FAILED = \"CONNECTION_FAILED\",\n CONNECTION_TIMEOUT = \"CONNECTION_TIMEOUT\",\n R2_CONNECTION_ERROR = \"R2_CONNECTION_ERROR\",\n S3_CONNECTION_ERROR = \"S3_CONNECTION_ERROR\",\n\n // User errors\n USER_NOT_FOUND = \"USER_NOT_FOUND\",\n USER_ALREADY_EXISTS = \"USER_ALREADY_EXISTS\",\n USER_NOT_ATTACHED = \"USER_NOT_ATTACHED\",\n\n // Query errors\n QUERY_EXECUTION_ERROR = \"QUERY_EXECUTION_ERROR\",\n QUERY_TIMEOUT = \"QUERY_TIMEOUT\",\n INVALID_SQL = \"INVALID_SQL\",\n\n // Resource errors\n MEMORY_LIMIT_EXCEEDED = \"MEMORY_LIMIT_EXCEEDED\",\n STORAGE_ERROR = \"STORAGE_ERROR\",\n STORAGE_QUOTA_EXCEEDED = \"STORAGE_QUOTA_EXCEEDED\",\n\n // Configuration errors\n INVALID_CONFIG = \"INVALID_CONFIG\",\n NOT_INITIALIZED = \"NOT_INITIALIZED\",\n\n // Unknown\n UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n}\n\n/**\n * Configuration with defaults applied\n */\nexport type ResolvedConfig = Required<\n Omit<DuckPondConfig, \"r2\" | \"s3\"> & {\n r2: DuckPondConfig[\"r2\"]\n s3: DuckPondConfig[\"s3\"]\n }\n>\n\n/**\n * Result of listUsers operation\n */\nexport interface ListUsersResult {\n users: List<string> // List of cached user IDs (functype immutable list)\n count: number // Number of cached users\n maxActiveUsers: number // Maximum cache capacity\n utilizationPercent: number // Cache utilization (0-100)\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/chunk-5XGN7UAV.js"],"names":[],"mappings":"AAAA,6EAAI,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc","file":"/home/jordanburke/IdeaProjects/duckpond/dist/chunk-5XGN7UAV.js"}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-5XGN7UAV.js"],"names":[],"mappings":"AAAA,6EAAI,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-5XGN7UAV.js"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/chunk-J2OQ62DV.js","../src/cache/LRUCache.ts"],"names":["log","loggers","LRUCache","maxSize","__publicField"],"mappings":"AAAA,+HAAuC,sDAAwC,qCCA1D,yCACE,IAKjBA,CAAAA,CAAMC,kBAAAA,CAAQ,KAAA,CAOPC,CAAAA,aAAN,KAAuC,CAI5C,WAAA,CAAYC,CAAAA,CAAkB,EAAA,CAAI,CAHlCC,gCAAAA,IAAA,CAAQ,OAAA,CAAA,CACRA,gCAAAA,IAAA,CAAiB,SAAA,CAAA,CAGf,IAAA,CAAK,KAAA,CAAQ,IAAI,GAAA,CACjB,IAAA,CAAK,OAAA,CAAUD,CAAAA,CACfH,CAAAA,CAAI,CAAA,+BAAA,EAAkCG,CAAO,CAAA,CAAA","file":"/home/jordanburke/IdeaProjects/duckpond/dist/chunk-J2OQ62DV.js","sourcesContent":[null,"import { List } from \"functype/list\"\nimport { Option } from \"functype/option\"\n\nimport type { UserDatabase } from \"../types\"\nimport { loggers } from \"../utils/logger\"\n\nconst log = loggers.cache\n\n/**\n * LRU Cache for managing active user database connections\n *\n * Uses functype Option for safe null handling\n */\nexport class LRUCache<T extends UserDatabase> {\n private cache: Map<string, T>\n private readonly maxSize: number\n\n constructor(maxSize: number = 10) {\n this.cache = new Map()\n this.maxSize = maxSize\n log(`Created LRU cache with maxSize=${maxSize}`)\n }\n\n /**\n * Get a value from the cache\n * Returns Option.Some(value) if found, Option.None otherwise\n */\n get(key: string): Option<T> {\n return Option(this.cache.get(key)).map((item) => {\n // Move to end (most recently used)\n this.cache.delete(key)\n this.cache.set(key, item)\n item.lastAccess = new Date()\n log(`Cache hit: ${key}`)\n return item\n })\n }\n\n /**\n * Set a value in the cache\n * Evicts LRU item if at capacity\n */\n set(key: string, value: T): void {\n // Remove if exists (to update position)\n if (this.cache.has(key)) {\n this.cache.delete(key)\n }\n\n // Evict LRU if at capacity\n if (this.cache.size >= this.maxSize) {\n this.evictLRU()\n }\n\n value.lastAccess = new Date()\n this.cache.set(key, value)\n log(`Cache set: ${key} (size=${this.cache.size})`)\n }\n\n /**\n * Remove a value from the cache\n */\n delete(key: string): boolean {\n const deleted = this.cache.delete(key)\n if (deleted) {\n log(`Cache delete: ${key}`)\n }\n return deleted\n }\n\n /**\n * Check if a key exists in the cache\n */\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n /**\n * Get the least recently used key\n * Returns Option.Some(key) if cache not empty, Option.None otherwise\n */\n getLRU(): Option<string> {\n const firstKey = this.cache.keys().next().value\n return Option(firstKey)\n }\n\n /**\n * Evict the least recently used item\n */\n private evictLRU(): void {\n this.getLRU().forEach((key) => {\n log(`Evicting LRU: ${key}`)\n this.cache.delete(key)\n })\n }\n\n /**\n * Get all keys for items older than the timeout\n * Returns a List of stale keys (functype immutable list)\n */\n getStale(timeoutMs: number): List<string> {\n const now = Date.now()\n const staleKeys: string[] = []\n\n for (const [key, value] of this.cache.entries()) {\n const age = now - value.lastAccess.getTime()\n if (age > timeoutMs) {\n staleKeys.push(key)\n }\n }\n\n if (staleKeys.length > 0) {\n log(`Found ${staleKeys.length} stale items`)\n }\n\n return List(staleKeys)\n }\n\n /**\n * Get the current size of the cache\n */\n size(): number {\n return this.cache.size\n }\n\n /**\n * Clear all items from the cache\n */\n clear(): void {\n const size = this.cache.size\n this.cache.clear()\n log(`Cache cleared (removed ${size} items)`)\n }\n\n /**\n * Get all values as a List (functype immutable list)\n */\n values(): List<T> {\n return List(Array.from(this.cache.values()))\n }\n\n /**\n * Get all keys as a List\n */\n keys(): List<string> {\n return List(Array.from(this.cache.keys()))\n }\n\n /**\n * Get cache statistics\n */\n getStats(): {\n size: number\n maxSize: number\n utilizationPercent: number\n oldestAccessTime: Option<Date>\n } {\n const values = this.values().toArray()\n const sorted = values.sort((a, b) => a.lastAccess.getTime() - b.lastAccess.getTime())\n const oldestAccessTime = Option(sorted[0]).map((item) => item.lastAccess)\n\n return {\n size: this.cache.size,\n maxSize: this.maxSize,\n utilizationPercent: (this.cache.size / this.maxSize) * 100,\n oldestAccessTime,\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-J2OQ62DV.js","../src/cache/LRUCache.ts"],"names":["log","loggers","LRUCache","maxSize","__publicField"],"mappings":"AAAA,+HAAuC,sDAAwC,qCCA1D,yCACE,IAKjBA,CAAAA,CAAMC,kBAAAA,CAAQ,KAAA,CAOPC,CAAAA,aAAN,KAAuC,CAI5C,WAAA,CAAYC,CAAAA,CAAkB,EAAA,CAAI,CAHlCC,gCAAAA,IAAA,CAAQ,OAAA,CAAA,CACRA,gCAAAA,IAAA,CAAiB,SAAA,CAAA,CAGf,IAAA,CAAK,KAAA,CAAQ,IAAI,GAAA,CACjB,IAAA,CAAK,OAAA,CAAUD,CAAAA,CACfH,CAAAA,CAAI,CAAA,+BAAA,EAAkCG,CAAO,CAAA,CAAA","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-J2OQ62DV.js","sourcesContent":[null,"import { List } from \"functype/list\"\nimport { Option } from \"functype/option\"\n\nimport type { UserDatabase } from \"../types\"\nimport { loggers } from \"../utils/logger\"\n\nconst log = loggers.cache\n\n/**\n * LRU Cache for managing active user database connections\n *\n * Uses functype Option for safe null handling\n */\nexport class LRUCache<T extends UserDatabase> {\n private cache: Map<string, T>\n private readonly maxSize: number\n\n constructor(maxSize: number = 10) {\n this.cache = new Map()\n this.maxSize = maxSize\n log(`Created LRU cache with maxSize=${maxSize}`)\n }\n\n /**\n * Get a value from the cache\n * Returns Option.Some(value) if found, Option.None otherwise\n */\n get(key: string): Option<T> {\n return Option(this.cache.get(key)).map((item) => {\n // Move to end (most recently used)\n this.cache.delete(key)\n this.cache.set(key, item)\n item.lastAccess = new Date()\n log(`Cache hit: ${key}`)\n return item\n })\n }\n\n /**\n * Set a value in the cache\n * Evicts LRU item if at capacity\n */\n set(key: string, value: T): void {\n // Remove if exists (to update position)\n if (this.cache.has(key)) {\n this.cache.delete(key)\n }\n\n // Evict LRU if at capacity\n if (this.cache.size >= this.maxSize) {\n this.evictLRU()\n }\n\n value.lastAccess = new Date()\n this.cache.set(key, value)\n log(`Cache set: ${key} (size=${this.cache.size})`)\n }\n\n /**\n * Remove a value from the cache\n */\n delete(key: string): boolean {\n const deleted = this.cache.delete(key)\n if (deleted) {\n log(`Cache delete: ${key}`)\n }\n return deleted\n }\n\n /**\n * Check if a key exists in the cache\n */\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n /**\n * Get the least recently used key\n * Returns Option.Some(key) if cache not empty, Option.None otherwise\n */\n getLRU(): Option<string> {\n const firstKey = this.cache.keys().next().value\n return Option(firstKey)\n }\n\n /**\n * Evict the least recently used item\n */\n private evictLRU(): void {\n this.getLRU().forEach((key) => {\n log(`Evicting LRU: ${key}`)\n this.cache.delete(key)\n })\n }\n\n /**\n * Get all keys for items older than the timeout\n * Returns a List of stale keys (functype immutable list)\n */\n getStale(timeoutMs: number): List<string> {\n const now = Date.now()\n const staleKeys: string[] = []\n\n for (const [key, value] of this.cache.entries()) {\n const age = now - value.lastAccess.getTime()\n if (age > timeoutMs) {\n staleKeys.push(key)\n }\n }\n\n if (staleKeys.length > 0) {\n log(`Found ${staleKeys.length} stale items`)\n }\n\n return List(staleKeys)\n }\n\n /**\n * Get the current size of the cache\n */\n size(): number {\n return this.cache.size\n }\n\n /**\n * Clear all items from the cache\n */\n clear(): void {\n const size = this.cache.size\n this.cache.clear()\n log(`Cache cleared (removed ${size} items)`)\n }\n\n /**\n * Get all values as a List (functype immutable list)\n */\n values(): List<T> {\n return List(Array.from(this.cache.values()))\n }\n\n /**\n * Get all keys as a List\n */\n keys(): List<string> {\n return List(Array.from(this.cache.keys()))\n }\n\n /**\n * Get cache statistics\n */\n getStats(): {\n size: number\n maxSize: number\n utilizationPercent: number\n oldestAccessTime: Option<Date>\n } {\n const values = this.values().toArray()\n const sorted = values.sort((a, b) => a.lastAccess.getTime() - b.lastAccess.getTime())\n const oldestAccessTime = Option(sorted[0]).map((item) => item.lastAccess)\n\n return {\n size: this.cache.size,\n maxSize: this.maxSize,\n utilizationPercent: (this.cache.size / this.maxSize) * 100,\n oldestAccessTime,\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/chunk-MLZHFU2B.js","../src/DuckPond.ts"],"names":["log","loggers","DuckPond","config","__publicField","Option","LRUCache","success","instance","DuckDBInstance","setupResult","error","err","Left","toDuckPondError","Errors","conn","inst","endpoint","userId","cached"],"mappings":"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","file":"/home/jordanburke/IdeaProjects/duckpond/dist/chunk-MLZHFU2B.js","sourcesContent":[null,"import { type DuckDBConnection, DuckDBInstance } from \"@duckdb/node-api\"\nimport { Either, Left, Right } from \"functype\"\nimport { Option } from \"functype\"\nimport { Try } from \"functype\"\n\nimport { LRUCache } from \"./cache/LRUCache\"\nimport type {\n AsyncDuckPondResult,\n CreateUserOptions,\n DuckPondConfig,\n DuckPondResult,\n ListUsersResult,\n ResolvedConfig,\n Schema,\n UserDatabase,\n UserStats,\n} from \"./types\"\nimport { ErrorCode } from \"./types\"\nimport { Errors, success, toDuckPondError } from \"./utils/errors\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.main\n\n/**\n * DuckPond - Multi-tenant DuckDB manager with R2/S3 storage\n *\n * Manages per-user DuckDB instances with:\n * - LRU caching for active users\n * - R2/S3 object storage integration\n * - Functional error handling with functype Either\n * - Automatic resource cleanup\n *\n * @example\n * ```typescript\n * const pond = new DuckPond({\n * r2: {\n * accountId: 'xxx',\n * accessKeyId: 'yyy',\n * secretAccessKey: 'zzz',\n * bucket: 'my-bucket'\n * }\n * })\n *\n * await pond.init()\n *\n * const result = await pond.query('user123', 'SELECT * FROM orders')\n * result.fold(\n * error => console.error('Query failed:', error),\n * rows => console.log('Results:', rows)\n * )\n * ```\n */\nexport class DuckPond {\n private instance: Option<DuckDBInstance> = Option.none()\n private cache: LRUCache<UserDatabase>\n private config: ResolvedConfig\n private evictionTimer: Option<NodeJS.Timeout> = Option.none()\n private initialized = false\n\n constructor(config: DuckPondConfig) {\n // Apply defaults\n this.config = {\n memoryLimit: config.memoryLimit || \"4GB\",\n threads: config.threads || 4,\n tempDir: config.tempDir || \"/tmp/duckpond\",\n maxActiveUsers: config.maxActiveUsers || 10,\n evictionTimeout: config.evictionTimeout || 300000,\n cacheType: config.cacheType || \"disk\",\n cacheDir: config.cacheDir || \"/tmp/duckpond-cache\",\n strategy: config.strategy || \"parquet\",\n r2: config.r2,\n s3: config.s3,\n }\n\n this.cache = new LRUCache(this.config.maxActiveUsers)\n\n log(\"DuckPond created with config:\", {\n memoryLimit: this.config.memoryLimit,\n threads: this.config.threads,\n maxActiveUsers: this.config.maxActiveUsers,\n strategy: this.config.strategy,\n })\n }\n\n /**\n * Initialize DuckPond\n * Must be called before any other operations\n */\n async init(): AsyncDuckPondResult<void> {\n if (this.initialized) {\n log(\"Already initialized\")\n return success(undefined)\n }\n\n log(\"Initializing DuckPond...\")\n\n try {\n // Create DuckDB instance\n const instance = await DuckDBInstance.create(\":memory:\")\n this.instance = Option(instance)\n\n // Setup cloud storage\n const setupResult = await this.setupCloudStorage()\n if (setupResult.isLeft()) {\n const error = setupResult.fold(\n (err) => err,\n () => null,\n )\n throw new Error(error?.message || \"Cloud storage setup failed\")\n }\n\n // Start eviction timer\n this.startEvictionTimer()\n\n this.initialized = true\n log(\"DuckPond initialized successfully\")\n return success(undefined)\n } catch (error) {\n return Left(toDuckPondError(error, ErrorCode.CONNECTION_FAILED))\n }\n }\n\n /**\n * Configure R2/S3 access and DuckDB extensions\n */\n private async setupCloudStorage(): AsyncDuckPondResult<void> {\n if (this.instance.isNone()) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.notInitialized() as any\n }\n\n const instance = this.instance.fold(\n () => {\n throw new Error(\"Unexpected: instance should be Some\")\n },\n (inst) => inst,\n )\n\n const conn = await instance.connect()\n\n try {\n // Create secret for R2 or S3\n if (this.config.r2) {\n log(\"Configuring R2 access\")\n await conn.run(`\n CREATE SECRET r2_secret (\n TYPE R2,\n ACCOUNT_ID '${this.config.r2.accountId}',\n ACCESS_KEY_ID '${this.config.r2.accessKeyId}',\n SECRET_ACCESS_KEY '${this.config.r2.secretAccessKey}'\n );\n `)\n } else if (this.config.s3) {\n log(\"Configuring S3 access\")\n const endpoint = this.config.s3.endpoint ? `ENDPOINT '${this.config.s3.endpoint}',` : \"\"\n await conn.run(`\n CREATE SECRET s3_secret (\n TYPE S3,\n REGION '${this.config.s3.region}',\n ${endpoint}\n ACCESS_KEY_ID '${this.config.s3.accessKeyId}',\n SECRET_ACCESS_KEY '${this.config.s3.secretAccessKey}'\n );\n `)\n }\n\n // Install extensions only if R2/S3 is configured\n if (this.config.r2 || this.config.s3) {\n await conn.run(`\n INSTALL httpfs;\n LOAD httpfs;\n INSTALL cache_httpfs;\n LOAD cache_httpfs;\n `)\n\n // Configure cache\n await conn.run(`\n SET cache_httpfs_type='${this.config.cacheType}';\n `)\n }\n\n // Configure performance settings\n await conn.run(`\n SET memory_limit='${this.config.memoryLimit}';\n SET threads=${this.config.threads};\n `)\n\n log(\"Cloud storage configured successfully\")\n return success(undefined)\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.r2ConnectionError(\"Failed to setup cloud storage\", error as Error) as any\n }\n }\n\n /**\n * Get a connection for a user\n * Loads from cache or attaches new database\n */\n async getUserConnection(userId: string): AsyncDuckPondResult<DuckDBConnection> {\n if (!this.initialized) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.notInitialized() as any\n }\n\n // Check cache\n const cached = this.cache.get(userId)\n if (cached.isSome()) {\n log(`Using cached connection for user: ${userId}`)\n return cached.fold(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (): AsyncDuckPondResult<DuckDBConnection> => Errors.userNotFound(userId) as any,\n (userDb): AsyncDuckPondResult<DuckDBConnection> => Promise.resolve(success(userDb.connection)),\n )\n }\n\n log(`Loading database for user: ${userId}`)\n\n // Evict if at capacity\n if (this.cache.size() >= this.config.maxActiveUsers) {\n await this.evictLRU()\n }\n\n if (this.instance.isNone()) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.notInitialized() as any\n }\n\n const instance = this.instance.fold(\n () => {\n throw new Error(\"Unexpected: instance should be Some\")\n },\n (inst) => inst,\n )\n\n const conn = await instance.connect()\n\n // Attach user's database (strategy-dependent)\n const attachResult = await this.attachUserDatabase(conn, userId)\n if (attachResult.isLeft()) {\n // Note: DuckDB connections are managed by the instance, no need to close\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return attachResult as any\n }\n\n // Add to cache\n this.cache.set(userId, {\n userId,\n connection: conn,\n lastAccess: new Date(),\n attached: true,\n })\n\n log(`Loaded database for user: ${userId}`)\n return success(conn)\n }\n\n /**\n * Attach a user's database based on storage strategy\n */\n private async attachUserDatabase(conn: DuckDBConnection, userId: string): AsyncDuckPondResult<void> {\n try {\n const bucket = this.config.r2?.bucket || this.config.s3?.bucket\n const protocol = this.config.r2 ? \"r2\" : \"s3\"\n\n if (this.config.strategy === \"duckdb\") {\n // Attach .duckdb file (read-only)\n const dbPath = `${protocol}://${bucket}/users/${userId}/database.duckdb`\n await conn.run(`ATTACH '${dbPath}' AS user_${userId} (READ_ONLY);`)\n log(`Attached .duckdb file for user: ${userId}`)\n }\n // For parquet strategy, no explicit attach needed\n return success(undefined)\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.storageError(\"Failed to attach user database\", error as Error) as any\n }\n }\n\n /**\n * Execute a SQL query for a user\n * Returns Either<Error, results>\n */\n async query<T = unknown>(userId: string, sql: string): AsyncDuckPondResult<T[]> {\n const connResult = await this.getUserConnection(userId)\n\n if (connResult.isLeft()) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return connResult as any\n }\n\n const conn = connResult.fold(\n () => {\n throw new Error(\"Unexpected: connection should be Right\")\n },\n (c) => c,\n )\n\n try {\n const resultObj = await conn.run(sql)\n // DuckDB node-api: use getRowObjects() to get rows as objects\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const rows = (await (resultObj as any).getRowObjects()) as T[]\n\n return success(rows)\n } catch (error) {\n return Left(toDuckPondError(error, ErrorCode.QUERY_EXECUTION_ERROR))\n }\n }\n\n /**\n * Execute SQL without returning results (DDL, DML)\n */\n async execute(userId: string, sql: string): AsyncDuckPondResult<void> {\n const connResult = await this.getUserConnection(userId)\n\n if (connResult.isLeft()) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return connResult as any\n }\n\n const conn = connResult.fold(\n () => {\n throw new Error(\"Unexpected: connection should be Right\")\n },\n (c) => c,\n )\n\n try {\n await conn.run(sql)\n return success(undefined)\n } catch (error) {\n return Left(toDuckPondError(error, ErrorCode.QUERY_EXECUTION_ERROR))\n }\n }\n\n /**\n * Detach a user's database and free resources\n */\n async detachUser(userId: string): AsyncDuckPondResult<void> {\n const cached = this.cache.get(userId)\n\n if (cached.isNone()) {\n log(`User not attached: ${userId}`)\n return success(undefined)\n }\n\n log(`Detaching user: ${userId}`)\n\n const userDb = cached.fold(\n () => {\n throw new Error(\"Unexpected: cached should be Some\")\n },\n (db) => db,\n )\n\n try {\n if (this.config.strategy === \"duckdb\") {\n await userDb.connection.run(`DETACH user_${userId}`)\n }\n // Note: DuckDB connections don't have a close() method in node-api\n // They are managed by the instance\n this.cache.delete(userId)\n log(`Detached user: ${userId}`)\n return success(undefined)\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.storageError(\"Failed to detach user\", error as Error) as any\n }\n }\n\n /**\n * Evict the least recently used user\n */\n private async evictLRU(): Promise<void> {\n this.cache.getLRU().forEach(async (userId) => {\n log(`Evicting LRU user: ${userId}`)\n await this.detachUser(userId)\n })\n }\n\n /**\n * Start background timer to evict idle users\n */\n private startEvictionTimer(): void {\n const timer = setInterval(async () => {\n const staleUsers = this.cache.getStale(this.config.evictionTimeout)\n\n staleUsers.forEach(async (userId) => {\n log(`Evicting idle user: ${userId}`)\n await this.detachUser(userId)\n })\n }, 60000) // Check every minute\n\n // Don't keep process alive\n timer.unref()\n\n this.evictionTimer = Option(timer)\n log(\"Eviction timer started\")\n }\n\n /**\n * Check if a user is currently attached\n */\n isAttached(userId: string): boolean {\n return this.cache.has(userId)\n }\n\n /**\n * Get statistics about a user's database\n */\n async getUserStats(userId: string): AsyncDuckPondResult<UserStats> {\n const cached = this.cache.get(userId)\n\n return success({\n userId,\n attached: cached.isSome(),\n lastAccess: cached.fold(\n () => new Date(0),\n (u) => u.lastAccess,\n ),\n memoryUsage: cached.fold(\n () => 0,\n (u) => u.memoryUsage || 0,\n ),\n storageUsage: 0, // TODO: Calculate from R2\n queryCount: 0, // TODO: Track queries\n })\n }\n\n /**\n * Get list of all currently cached users\n * Returns a List of user IDs and cache statistics\n */\n listUsers(): ListUsersResult {\n const stats = this.cache.getStats()\n const keys = this.cache.keys()\n\n return {\n users: keys, // Already returns List<string>\n count: stats.size,\n maxActiveUsers: stats.maxSize,\n utilizationPercent: stats.utilizationPercent,\n }\n }\n\n /**\n * Close DuckPond and cleanup all resources\n */\n async close(): AsyncDuckPondResult<void> {\n log(\"Closing DuckPond...\")\n\n // Stop eviction timer\n this.evictionTimer.forEach((timer) => clearInterval(timer))\n\n // Detach all users\n const detachPromises = this.cache.keys().map((userId) => this.detachUser(userId.toString()))\n\n await Promise.all(detachPromises.toArray())\n\n // Close instance\n this.instance = Option.none()\n this.initialized = false\n log(\"DuckPond closed\")\n return success(undefined)\n }\n}\n"]}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-MLZHFU2B.js","../src/DuckPond.ts"],"names":["log","loggers","DuckPond","config","__publicField","Option","LRUCache","success","instance","DuckDBInstance","setupResult","error","err","Left","toDuckPondError","Errors","conn","inst","endpoint","userId","cached"],"mappings":"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","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-MLZHFU2B.js","sourcesContent":[null,"import { type DuckDBConnection, DuckDBInstance } from \"@duckdb/node-api\"\nimport { Either, Left, Right } from \"functype\"\nimport { Option } from \"functype\"\nimport { Try } from \"functype\"\n\nimport { LRUCache } from \"./cache/LRUCache\"\nimport type {\n AsyncDuckPondResult,\n CreateUserOptions,\n DuckPondConfig,\n DuckPondResult,\n ListUsersResult,\n ResolvedConfig,\n Schema,\n UserDatabase,\n UserStats,\n} from \"./types\"\nimport { ErrorCode } from \"./types\"\nimport { Errors, success, toDuckPondError } from \"./utils/errors\"\nimport { loggers } from \"./utils/logger\"\n\nconst log = loggers.main\n\n/**\n * DuckPond - Multi-tenant DuckDB manager with R2/S3 storage\n *\n * Manages per-user DuckDB instances with:\n * - LRU caching for active users\n * - R2/S3 object storage integration\n * - Functional error handling with functype Either\n * - Automatic resource cleanup\n *\n * @example\n * ```typescript\n * const pond = new DuckPond({\n * r2: {\n * accountId: 'xxx',\n * accessKeyId: 'yyy',\n * secretAccessKey: 'zzz',\n * bucket: 'my-bucket'\n * }\n * })\n *\n * await pond.init()\n *\n * const result = await pond.query('user123', 'SELECT * FROM orders')\n * result.fold(\n * error => console.error('Query failed:', error),\n * rows => console.log('Results:', rows)\n * )\n * ```\n */\nexport class DuckPond {\n private instance: Option<DuckDBInstance> = Option.none()\n private cache: LRUCache<UserDatabase>\n private config: ResolvedConfig\n private evictionTimer: Option<NodeJS.Timeout> = Option.none()\n private initialized = false\n\n constructor(config: DuckPondConfig) {\n // Apply defaults\n this.config = {\n memoryLimit: config.memoryLimit || \"4GB\",\n threads: config.threads || 4,\n tempDir: config.tempDir || \"/tmp/duckpond\",\n maxActiveUsers: config.maxActiveUsers || 10,\n evictionTimeout: config.evictionTimeout || 300000,\n cacheType: config.cacheType || \"disk\",\n cacheDir: config.cacheDir || \"/tmp/duckpond-cache\",\n strategy: config.strategy || \"parquet\",\n r2: config.r2,\n s3: config.s3,\n }\n\n this.cache = new LRUCache(this.config.maxActiveUsers)\n\n log(\"DuckPond created with config:\", {\n memoryLimit: this.config.memoryLimit,\n threads: this.config.threads,\n maxActiveUsers: this.config.maxActiveUsers,\n strategy: this.config.strategy,\n })\n }\n\n /**\n * Initialize DuckPond\n * Must be called before any other operations\n */\n async init(): AsyncDuckPondResult<void> {\n if (this.initialized) {\n log(\"Already initialized\")\n return success(undefined)\n }\n\n log(\"Initializing DuckPond...\")\n\n try {\n // Create DuckDB instance\n const instance = await DuckDBInstance.create(\":memory:\")\n this.instance = Option(instance)\n\n // Setup cloud storage\n const setupResult = await this.setupCloudStorage()\n if (setupResult.isLeft()) {\n const error = setupResult.fold(\n (err) => err,\n () => null,\n )\n throw new Error(error?.message || \"Cloud storage setup failed\")\n }\n\n // Start eviction timer\n this.startEvictionTimer()\n\n this.initialized = true\n log(\"DuckPond initialized successfully\")\n return success(undefined)\n } catch (error) {\n return Left(toDuckPondError(error, ErrorCode.CONNECTION_FAILED))\n }\n }\n\n /**\n * Configure R2/S3 access and DuckDB extensions\n */\n private async setupCloudStorage(): AsyncDuckPondResult<void> {\n if (this.instance.isNone()) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.notInitialized() as any\n }\n\n const instance = this.instance.fold(\n () => {\n throw new Error(\"Unexpected: instance should be Some\")\n },\n (inst) => inst,\n )\n\n const conn = await instance.connect()\n\n try {\n // Create secret for R2 or S3\n if (this.config.r2) {\n log(\"Configuring R2 access\")\n await conn.run(`\n CREATE SECRET r2_secret (\n TYPE R2,\n ACCOUNT_ID '${this.config.r2.accountId}',\n ACCESS_KEY_ID '${this.config.r2.accessKeyId}',\n SECRET_ACCESS_KEY '${this.config.r2.secretAccessKey}'\n );\n `)\n } else if (this.config.s3) {\n log(\"Configuring S3 access\")\n const endpoint = this.config.s3.endpoint ? `ENDPOINT '${this.config.s3.endpoint}',` : \"\"\n await conn.run(`\n CREATE SECRET s3_secret (\n TYPE S3,\n REGION '${this.config.s3.region}',\n ${endpoint}\n ACCESS_KEY_ID '${this.config.s3.accessKeyId}',\n SECRET_ACCESS_KEY '${this.config.s3.secretAccessKey}'\n );\n `)\n }\n\n // Install extensions only if R2/S3 is configured\n if (this.config.r2 || this.config.s3) {\n await conn.run(`\n INSTALL httpfs;\n LOAD httpfs;\n INSTALL cache_httpfs;\n LOAD cache_httpfs;\n `)\n\n // Configure cache\n await conn.run(`\n SET cache_httpfs_type='${this.config.cacheType}';\n `)\n }\n\n // Configure performance settings\n await conn.run(`\n SET memory_limit='${this.config.memoryLimit}';\n SET threads=${this.config.threads};\n `)\n\n log(\"Cloud storage configured successfully\")\n return success(undefined)\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.r2ConnectionError(\"Failed to setup cloud storage\", error as Error) as any\n }\n }\n\n /**\n * Get a connection for a user\n * Loads from cache or attaches new database\n */\n async getUserConnection(userId: string): AsyncDuckPondResult<DuckDBConnection> {\n if (!this.initialized) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.notInitialized() as any\n }\n\n // Check cache\n const cached = this.cache.get(userId)\n if (cached.isSome()) {\n log(`Using cached connection for user: ${userId}`)\n return cached.fold(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (): AsyncDuckPondResult<DuckDBConnection> => Errors.userNotFound(userId) as any,\n (userDb): AsyncDuckPondResult<DuckDBConnection> => Promise.resolve(success(userDb.connection)),\n )\n }\n\n log(`Loading database for user: ${userId}`)\n\n // Evict if at capacity\n if (this.cache.size() >= this.config.maxActiveUsers) {\n await this.evictLRU()\n }\n\n if (this.instance.isNone()) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.notInitialized() as any\n }\n\n const instance = this.instance.fold(\n () => {\n throw new Error(\"Unexpected: instance should be Some\")\n },\n (inst) => inst,\n )\n\n const conn = await instance.connect()\n\n // Attach user's database (strategy-dependent)\n const attachResult = await this.attachUserDatabase(conn, userId)\n if (attachResult.isLeft()) {\n // Note: DuckDB connections are managed by the instance, no need to close\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return attachResult as any\n }\n\n // Add to cache\n this.cache.set(userId, {\n userId,\n connection: conn,\n lastAccess: new Date(),\n attached: true,\n })\n\n log(`Loaded database for user: ${userId}`)\n return success(conn)\n }\n\n /**\n * Attach a user's database based on storage strategy\n */\n private async attachUserDatabase(conn: DuckDBConnection, userId: string): AsyncDuckPondResult<void> {\n try {\n const bucket = this.config.r2?.bucket || this.config.s3?.bucket\n const protocol = this.config.r2 ? \"r2\" : \"s3\"\n\n if (this.config.strategy === \"duckdb\") {\n // Attach .duckdb file (read-only)\n const dbPath = `${protocol}://${bucket}/users/${userId}/database.duckdb`\n await conn.run(`ATTACH '${dbPath}' AS user_${userId} (READ_ONLY);`)\n log(`Attached .duckdb file for user: ${userId}`)\n }\n // For parquet strategy, no explicit attach needed\n return success(undefined)\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.storageError(\"Failed to attach user database\", error as Error) as any\n }\n }\n\n /**\n * Execute a SQL query for a user\n * Returns Either<Error, results>\n */\n async query<T = unknown>(userId: string, sql: string): AsyncDuckPondResult<T[]> {\n const connResult = await this.getUserConnection(userId)\n\n if (connResult.isLeft()) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return connResult as any\n }\n\n const conn = connResult.fold(\n () => {\n throw new Error(\"Unexpected: connection should be Right\")\n },\n (c) => c,\n )\n\n try {\n const resultObj = await conn.run(sql)\n // DuckDB node-api: use getRowObjects() to get rows as objects\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const rows = (await (resultObj as any).getRowObjects()) as T[]\n\n return success(rows)\n } catch (error) {\n return Left(toDuckPondError(error, ErrorCode.QUERY_EXECUTION_ERROR))\n }\n }\n\n /**\n * Execute SQL without returning results (DDL, DML)\n */\n async execute(userId: string, sql: string): AsyncDuckPondResult<void> {\n const connResult = await this.getUserConnection(userId)\n\n if (connResult.isLeft()) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return connResult as any\n }\n\n const conn = connResult.fold(\n () => {\n throw new Error(\"Unexpected: connection should be Right\")\n },\n (c) => c,\n )\n\n try {\n await conn.run(sql)\n return success(undefined)\n } catch (error) {\n return Left(toDuckPondError(error, ErrorCode.QUERY_EXECUTION_ERROR))\n }\n }\n\n /**\n * Detach a user's database and free resources\n */\n async detachUser(userId: string): AsyncDuckPondResult<void> {\n const cached = this.cache.get(userId)\n\n if (cached.isNone()) {\n log(`User not attached: ${userId}`)\n return success(undefined)\n }\n\n log(`Detaching user: ${userId}`)\n\n const userDb = cached.fold(\n () => {\n throw new Error(\"Unexpected: cached should be Some\")\n },\n (db) => db,\n )\n\n try {\n if (this.config.strategy === \"duckdb\") {\n await userDb.connection.run(`DETACH user_${userId}`)\n }\n // Note: DuckDB connections don't have a close() method in node-api\n // They are managed by the instance\n this.cache.delete(userId)\n log(`Detached user: ${userId}`)\n return success(undefined)\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Errors.storageError(\"Failed to detach user\", error as Error) as any\n }\n }\n\n /**\n * Evict the least recently used user\n */\n private async evictLRU(): Promise<void> {\n this.cache.getLRU().forEach(async (userId) => {\n log(`Evicting LRU user: ${userId}`)\n await this.detachUser(userId)\n })\n }\n\n /**\n * Start background timer to evict idle users\n */\n private startEvictionTimer(): void {\n const timer = setInterval(async () => {\n const staleUsers = this.cache.getStale(this.config.evictionTimeout)\n\n staleUsers.forEach(async (userId) => {\n log(`Evicting idle user: ${userId}`)\n await this.detachUser(userId)\n })\n }, 60000) // Check every minute\n\n // Don't keep process alive\n timer.unref()\n\n this.evictionTimer = Option(timer)\n log(\"Eviction timer started\")\n }\n\n /**\n * Check if a user is currently attached\n */\n isAttached(userId: string): boolean {\n return this.cache.has(userId)\n }\n\n /**\n * Get statistics about a user's database\n */\n async getUserStats(userId: string): AsyncDuckPondResult<UserStats> {\n const cached = this.cache.get(userId)\n\n return success({\n userId,\n attached: cached.isSome(),\n lastAccess: cached.fold(\n () => new Date(0),\n (u) => u.lastAccess,\n ),\n memoryUsage: cached.fold(\n () => 0,\n (u) => u.memoryUsage || 0,\n ),\n storageUsage: 0, // TODO: Calculate from R2\n queryCount: 0, // TODO: Track queries\n })\n }\n\n /**\n * Get list of all currently cached users\n * Returns a List of user IDs and cache statistics\n */\n listUsers(): ListUsersResult {\n const stats = this.cache.getStats()\n const keys = this.cache.keys()\n\n return {\n users: keys, // Already returns List<string>\n count: stats.size,\n maxActiveUsers: stats.maxSize,\n utilizationPercent: stats.utilizationPercent,\n }\n }\n\n /**\n * Close DuckPond and cleanup all resources\n */\n async close(): AsyncDuckPondResult<void> {\n log(\"Closing DuckPond...\")\n\n // Stop eviction timer\n this.evictionTimer.forEach((timer) => clearInterval(timer))\n\n // Detach all users\n const detachPromises = this.cache.keys().map((userId) => this.detachUser(userId.toString()))\n\n await Promise.all(detachPromises.toArray())\n\n // Close instance\n this.instance = Option.none()\n this.initialized = false\n log(\"DuckPond closed\")\n return success(undefined)\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/errors.ts"],"names":["createError","code","message","cause","context","Left","success","value","Right","toDuckPondError","error","defaultCode","Errors","userId"],"mappings":"AAAA,6GAAyC,SAOzBA,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAC8B,CAC9B,OAAOC,4BAAAA,CACL,IAAA,CAAAJ,CAAAA,CACA,OAAA,CAAAC,CAAAA,CACA,KAAA,CAAAC,CAAAA,CACA,OAAA,CAAAC,CACF,CAAC,CACH,CAKO,SAASE,CAAAA,CAAkBC,CAAAA,CAA6D,CAE7F,OAAOC,6BAAAA,CAAW,CACpB,CAKO,SAASC,CAAAA,CAAgBC,CAAAA,CAAgBC,CAAAA,CAAAA,eAAAA,CAAiE,CAC/G,OAAID,EAAAA,WAAiB,KAAA,CACZ,CACL,IAAA,CAAMC,CAAAA,CACN,OAAA,CAASD,CAAAA,CAAM,OAAA,CACf,KAAA,CAAOA,CACT,CAAA,CAGK,CACL,IAAA,CAAMC,CAAAA,CACN,OAAA,CAAS,MAAA,CAAOD,CAAK,CACvB,CACF,CAKO,IAAME,CAAAA,CAAS,CACpB,gBAAA,CAAkB,CAACV,CAAAA,CAAiBC,CAAAA,CAAAA,EAAkBH,CAAAA,CAAAA,mBAAAA,CAAyCE,CAAAA,CAASC,CAAK,CAAA,CAE7G,iBAAA,CAAmB,CAACD,CAAAA,CAAiBC,CAAAA,CAAAA,EAAkBH,CAAAA,CAAAA,qBAAAA,CAA2CE,CAAAA,CAASC,CAAK,CAAA,CAEhH,iBAAA,CAAmB,CAACD,CAAAA,CAAiBC,CAAAA,CAAAA,EAAkBH,CAAAA,CAAAA,qBAAAA,CAA2CE,CAAAA,CAASC,CAAK,CAAA,CAEhH,YAAA,CAAeU,CAAAA,EACbb,CAAAA,CAAAA,gBAAAA,CAAsC,CAAA,gBAAA,EAAmBa,CAAM,CAAA,CAAA;AA+CnE","file":"/home/jordanburke/IdeaProjects/duckpond/dist/chunk-MZTKR3LR.js","sourcesContent":["import { type Either, Left, Right } from \"functype\"\n\nimport { DuckPondError, ErrorCode } from \"../types\"\n\n/**\n * Create a DuckPondError as a Left Either\n */\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: Error,\n context?: Record<string, unknown>,\n): Either<DuckPondError, never> {\n return Left({\n code,\n message,\n cause,\n context,\n })\n}\n\n/**\n * Wrap a value in a Right Either\n */\nexport function success<T = void>(value?: T): Either<DuckPondError, T extends void ? void : T> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Right(value) as any\n}\n\n/**\n * Convert an unknown error to a DuckPondError\n */\nexport function toDuckPondError(error: unknown, defaultCode: ErrorCode = ErrorCode.UNKNOWN_ERROR): DuckPondError {\n if (error instanceof Error) {\n return {\n code: defaultCode,\n message: error.message,\n cause: error,\n }\n }\n\n return {\n code: defaultCode,\n message: String(error),\n }\n}\n\n/**\n * Error factory functions for common errors\n */\nexport const Errors = {\n connectionFailed: (message: string, cause?: Error) => createError(ErrorCode.CONNECTION_FAILED, message, cause),\n\n r2ConnectionError: (message: string, cause?: Error) => createError(ErrorCode.R2_CONNECTION_ERROR, message, cause),\n\n s3ConnectionError: (message: string, cause?: Error) => createError(ErrorCode.S3_CONNECTION_ERROR, message, cause),\n\n userNotFound: (userId: string) =>\n createError(ErrorCode.USER_NOT_FOUND, `User not found: ${userId}`, undefined, { userId }),\n\n userAlreadyExists: (userId: string) =>\n createError(ErrorCode.USER_ALREADY_EXISTS, `User already exists: ${userId}`, undefined, {\n userId,\n }),\n\n userNotAttached: (userId: string) =>\n createError(ErrorCode.USER_NOT_ATTACHED, `User not attached: ${userId}`, undefined, {\n userId,\n }),\n\n queryExecutionError: (message: string, sql?: string, cause?: Error) =>\n createError(ErrorCode.QUERY_EXECUTION_ERROR, message, cause, { sql }),\n\n queryTimeout: (timeoutMs: number) =>\n createError(ErrorCode.QUERY_TIMEOUT, `Query timeout after ${timeoutMs}ms`, undefined, {\n timeoutMs,\n }),\n\n memoryLimitExceeded: (limit: string) =>\n createError(ErrorCode.MEMORY_LIMIT_EXCEEDED, `Memory limit exceeded: ${limit}`, undefined, {\n limit,\n }),\n\n storageError: (message: string, cause?: Error) => createError(ErrorCode.STORAGE_ERROR, message, cause),\n\n invalidConfig: (message: string) => createError(ErrorCode.INVALID_CONFIG, message),\n\n notInitialized: () => createError(ErrorCode.NOT_INITIALIZED, \"DuckPond not initialized. Call init() first.\"),\n}\n\n/**\n * Format an error for logging\n */\nexport function formatError(error: DuckPondError): string {\n const parts = [`[${error.code}] ${error.message}`]\n\n if (error.cause) {\n parts.push(` Caused by: ${error.cause.message}`)\n }\n\n if (error.context) {\n parts.push(` Context: ${JSON.stringify(error.context)}`)\n }\n\n return parts.join(\"\\n\")\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/errors.ts"],"names":["createError","code","message","cause","context","Left","success","value","Right","toDuckPondError","error","defaultCode","Errors","userId"],"mappings":"AAAA,6GAAyC,SAOzBA,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAC8B,CAC9B,OAAOC,4BAAAA,CACL,IAAA,CAAAJ,CAAAA,CACA,OAAA,CAAAC,CAAAA,CACA,KAAA,CAAAC,CAAAA,CACA,OAAA,CAAAC,CACF,CAAC,CACH,CAKO,SAASE,CAAAA,CAAkBC,CAAAA,CAA6D,CAE7F,OAAOC,6BAAAA,CAAW,CACpB,CAKO,SAASC,CAAAA,CAAgBC,CAAAA,CAAgBC,CAAAA,CAAAA,eAAAA,CAAiE,CAC/G,OAAID,EAAAA,WAAiB,KAAA,CACZ,CACL,IAAA,CAAMC,CAAAA,CACN,OAAA,CAASD,CAAAA,CAAM,OAAA,CACf,KAAA,CAAOA,CACT,CAAA,CAGK,CACL,IAAA,CAAMC,CAAAA,CACN,OAAA,CAAS,MAAA,CAAOD,CAAK,CACvB,CACF,CAKO,IAAME,CAAAA,CAAS,CACpB,gBAAA,CAAkB,CAACV,CAAAA,CAAiBC,CAAAA,CAAAA,EAAkBH,CAAAA,CAAAA,mBAAAA,CAAyCE,CAAAA,CAASC,CAAK,CAAA,CAE7G,iBAAA,CAAmB,CAACD,CAAAA,CAAiBC,CAAAA,CAAAA,EAAkBH,CAAAA,CAAAA,qBAAAA,CAA2CE,CAAAA,CAASC,CAAK,CAAA,CAEhH,iBAAA,CAAmB,CAACD,CAAAA,CAAiBC,CAAAA,CAAAA,EAAkBH,CAAAA,CAAAA,qBAAAA,CAA2CE,CAAAA,CAASC,CAAK,CAAA,CAEhH,YAAA,CAAeU,CAAAA,EACbb,CAAAA,CAAAA,gBAAAA,CAAsC,CAAA,gBAAA,EAAmBa,CAAM,CAAA,CAAA;AA+CnE","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-MZTKR3LR.js","sourcesContent":["import { type Either, Left, Right } from \"functype\"\n\nimport { DuckPondError, ErrorCode } from \"../types\"\n\n/**\n * Create a DuckPondError as a Left Either\n */\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: Error,\n context?: Record<string, unknown>,\n): Either<DuckPondError, never> {\n return Left({\n code,\n message,\n cause,\n context,\n })\n}\n\n/**\n * Wrap a value in a Right Either\n */\nexport function success<T = void>(value?: T): Either<DuckPondError, T extends void ? void : T> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Right(value) as any\n}\n\n/**\n * Convert an unknown error to a DuckPondError\n */\nexport function toDuckPondError(error: unknown, defaultCode: ErrorCode = ErrorCode.UNKNOWN_ERROR): DuckPondError {\n if (error instanceof Error) {\n return {\n code: defaultCode,\n message: error.message,\n cause: error,\n }\n }\n\n return {\n code: defaultCode,\n message: String(error),\n }\n}\n\n/**\n * Error factory functions for common errors\n */\nexport const Errors = {\n connectionFailed: (message: string, cause?: Error) => createError(ErrorCode.CONNECTION_FAILED, message, cause),\n\n r2ConnectionError: (message: string, cause?: Error) => createError(ErrorCode.R2_CONNECTION_ERROR, message, cause),\n\n s3ConnectionError: (message: string, cause?: Error) => createError(ErrorCode.S3_CONNECTION_ERROR, message, cause),\n\n userNotFound: (userId: string) =>\n createError(ErrorCode.USER_NOT_FOUND, `User not found: ${userId}`, undefined, { userId }),\n\n userAlreadyExists: (userId: string) =>\n createError(ErrorCode.USER_ALREADY_EXISTS, `User already exists: ${userId}`, undefined, {\n userId,\n }),\n\n userNotAttached: (userId: string) =>\n createError(ErrorCode.USER_NOT_ATTACHED, `User not attached: ${userId}`, undefined, {\n userId,\n }),\n\n queryExecutionError: (message: string, sql?: string, cause?: Error) =>\n createError(ErrorCode.QUERY_EXECUTION_ERROR, message, cause, { sql }),\n\n queryTimeout: (timeoutMs: number) =>\n createError(ErrorCode.QUERY_TIMEOUT, `Query timeout after ${timeoutMs}ms`, undefined, {\n timeoutMs,\n }),\n\n memoryLimitExceeded: (limit: string) =>\n createError(ErrorCode.MEMORY_LIMIT_EXCEEDED, `Memory limit exceeded: ${limit}`, undefined, {\n limit,\n }),\n\n storageError: (message: string, cause?: Error) => createError(ErrorCode.STORAGE_ERROR, message, cause),\n\n invalidConfig: (message: string) => createError(ErrorCode.INVALID_CONFIG, message),\n\n notInitialized: () => createError(ErrorCode.NOT_INITIALIZED, \"DuckPond not initialized. Call init() first.\"),\n}\n\n/**\n * Format an error for logging\n */\nexport function formatError(error: DuckPondError): string {\n const parts = [`[${error.code}] ${error.message}`]\n\n if (error.cause) {\n parts.push(` Caused by: ${error.cause.message}`)\n }\n\n if (error.context) {\n parts.push(` Context: ${JSON.stringify(error.context)}`)\n }\n\n return parts.join(\"\\n\")\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/logger.ts"],"names":["createLogger","namespace","debug"],"mappings":"AAAA,qPAAkB,SAWFA,CAAAA,CAAaC,CAAAA,CAAmC,CAC9D,OAAOC,6BAAAA,CAAM,SAAA,EAAYD,CAAS,CAAA,CAAA","file":"/home/jordanburke/IdeaProjects/duckpond/dist/chunk-Q6UFPTQC.js","sourcesContent":["import debug from \"debug\"\n\n/**\n * Create a namespaced logger for DuckPond\n *\n * Usage:\n * const log = createLogger('DuckPond')\n * log('Initializing...')\n *\n * Enable with: DEBUG=duckpond:* node app.js\n */\nexport function createLogger(namespace: string): debug.Debugger {\n return debug(`duckpond:${namespace}`)\n}\n\n/**\n * Pre-configured loggers for different modules\n */\nexport const loggers = {\n main: createLogger(\"main\"),\n cache: createLogger(\"cache\"),\n connection: createLogger(\"connection\"),\n query: createLogger(\"query\"),\n storage: createLogger(\"storage\"),\n metrics: createLogger(\"metrics\"),\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/logger.ts"],"names":["createLogger","namespace","debug"],"mappings":"AAAA,qPAAkB,SAWFA,CAAAA,CAAaC,CAAAA,CAAmC,CAC9D,OAAOC,6BAAAA,CAAM,SAAA,EAAYD,CAAS,CAAA,CAAA","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/chunk-Q6UFPTQC.js","sourcesContent":["import debug from \"debug\"\n\n/**\n * Create a namespaced logger for DuckPond\n *\n * Usage:\n * const log = createLogger('DuckPond')\n * log('Initializing...')\n *\n * Enable with: DEBUG=duckpond:* node app.js\n */\nexport function createLogger(namespace: string): debug.Debugger {\n return debug(`duckpond:${namespace}`)\n}\n\n/**\n * Pre-configured loggers for different modules\n */\nexport const loggers = {\n main: createLogger(\"main\"),\n cache: createLogger(\"cache\"),\n connection: createLogger(\"connection\"),\n query: createLogger(\"query\"),\n storage: createLogger(\"storage\"),\n metrics: createLogger(\"metrics\"),\n}\n"]}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/index.js","../src/index.ts"],"names":[],"mappings":"AAAA,+HAAuC,sDAAwC,sDAAoE,sDAAwC,sDAA+C,+BAA4B,yCC+DlO,qCACf,yCACE,mCACH,okBAAA","file":"/home/jordanburke/IdeaProjects/duckpond/dist/index.js","sourcesContent":[null,"/**\n * DuckPond - Multi-tenant DuckDB manager with R2/S3 storage\n *\n * @example\n * ```typescript\n * import { DuckPond } from 'duckpond'\n *\n * const pond = new DuckPond({\n * r2: {\n * accountId: process.env.R2_ACCOUNT_ID!,\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * bucket: 'my-bucket'\n * },\n * maxActiveUsers: 10\n * })\n *\n * await pond.init()\n *\n * // Query with functype Either for safe error handling\n * const result = await pond.query('user123', 'SELECT * FROM orders')\n * result.fold(\n * error => console.error('Failed:', error.message),\n * rows => console.log('Success:', rows)\n * )\n *\n * await pond.close()\n * ```\n */\n\n// Main class\nexport { DuckPond } from \"./DuckPond\"\n\n// Types\nexport type {\n AsyncDuckPondResult,\n ColumnSchema,\n CreateUserOptions,\n DuckPondConfig,\n DuckPondError,\n DuckPondEvent,\n DuckPondMetrics,\n DuckPondResult,\n FileInfo,\n ListUsersResult,\n QueryResult,\n ResolvedConfig,\n Schema,\n StorageStats,\n TableSchema,\n UserDatabase,\n UserStats,\n} from \"./types\"\nexport { ErrorCode } from \"./types\"\n\n// Utilities\nexport { createError, Errors, formatError, success, toDuckPondError } from \"./utils/errors\"\nexport { createLogger, loggers } from \"./utils/logger\"\n\n// Cache\nexport { LRUCache } from \"./cache/LRUCache\"\n\n// Re-export functype for convenience\nexport { Either, Left, Right } from \"functype/either\"\nexport { List } from \"functype/list\"\nexport { Option } from \"functype/option\"\nexport { Try } from \"functype/try\"\n"]}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/index.js","../src/index.ts"],"names":[],"mappings":"AAAA,+HAAuC,sDAAwC,sDAAoE,sDAAwC,sDAA+C,+BAA4B,yCC+DlO,qCACf,yCACE,mCACH,okBAAA","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/index.js","sourcesContent":[null,"/**\n * DuckPond - Multi-tenant DuckDB manager with R2/S3 storage\n *\n * @example\n * ```typescript\n * import { DuckPond } from 'duckpond'\n *\n * const pond = new DuckPond({\n * r2: {\n * accountId: process.env.R2_ACCOUNT_ID!,\n * accessKeyId: process.env.R2_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,\n * bucket: 'my-bucket'\n * },\n * maxActiveUsers: 10\n * })\n *\n * await pond.init()\n *\n * // Query with functype Either for safe error handling\n * const result = await pond.query('user123', 'SELECT * FROM orders')\n * result.fold(\n * error => console.error('Failed:', error.message),\n * rows => console.log('Success:', rows)\n * )\n *\n * await pond.close()\n * ```\n */\n\n// Main class\nexport { DuckPond } from \"./DuckPond\"\n\n// Types\nexport type {\n AsyncDuckPondResult,\n ColumnSchema,\n CreateUserOptions,\n DuckPondConfig,\n DuckPondError,\n DuckPondEvent,\n DuckPondMetrics,\n DuckPondResult,\n FileInfo,\n ListUsersResult,\n QueryResult,\n ResolvedConfig,\n Schema,\n StorageStats,\n TableSchema,\n UserDatabase,\n UserStats,\n} from \"./types\"\nexport { ErrorCode } from \"./types\"\n\n// Utilities\nexport { createError, Errors, formatError, success, toDuckPondError } from \"./utils/errors\"\nexport { createLogger, loggers } from \"./utils/logger\"\n\n// Cache\nexport { LRUCache } from \"./cache/LRUCache\"\n\n// Re-export functype for convenience\nexport { Either, Left, Right } from \"functype/either\"\nexport { List } from \"functype/list\"\nexport { Option } from \"functype/option\"\nexport { Try } from \"functype/try\"\n"]}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/types.js"],"names":[],"mappings":"AAAA,+HAAkC,+BAA4B,uCAAuB","file":"/home/jordanburke/IdeaProjects/duckpond/dist/types.js"}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/types.js"],"names":[],"mappings":"AAAA,+HAAkC,+BAA4B,uCAAuB","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/types.js"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/utils/errors.js"],"names":[],"mappings":"AAAA,gIAA2C,gCAA6B,gCAA6B,4MAAwF","file":"/home/jordanburke/IdeaProjects/duckpond/dist/utils/errors.js"}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/utils/errors.js"],"names":[],"mappings":"AAAA,gIAA2C,gCAA6B,gCAA6B,4MAAwF","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/utils/errors.js"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/jordanburke/IdeaProjects/duckpond/dist/utils/logger.js"],"names":[],"mappings":"AAAA,gIAAqC,gCAA6B,gFAAuC","file":"/home/jordanburke/IdeaProjects/duckpond/dist/utils/logger.js"}
1
+ {"version":3,"sources":["/Users/jordanburke/IdeaProjects/duckpond/dist/utils/logger.js"],"names":[],"mappings":"AAAA,gIAAqC,gCAA6B,gFAAuC","file":"/Users/jordanburke/IdeaProjects/duckpond/dist/utils/logger.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duckpond",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Multi-tenant DuckDB manager with R2/S3 storage and functional programming patterns",
5
5
  "keywords": [
6
6
  "duckdb",
@@ -21,26 +21,26 @@
21
21
  "url": "https://github.com/jordanburke/duckpond"
22
22
  },
23
23
  "devDependencies": {
24
- "@eslint/eslintrc": "^3.3.1",
25
- "@eslint/js": "^9.38.0",
26
- "@types/node": "^22.18.11",
27
- "@typescript-eslint/eslint-plugin": "^8.46.1",
28
- "@typescript-eslint/parser": "^8.46.1",
29
- "@vitest/coverage-v8": "3.2.4",
30
- "@vitest/ui": "^3.2.4",
24
+ "@eslint/eslintrc": "^3.3.3",
25
+ "@eslint/js": "^9.39.2",
26
+ "@types/node": "^22.19.3",
27
+ "@typescript-eslint/eslint-plugin": "^8.51.0",
28
+ "@typescript-eslint/parser": "^8.51.0",
29
+ "@vitest/coverage-v8": "4.0.16",
30
+ "@vitest/ui": "^4.0.16",
31
31
  "cross-env": "^10.1.0",
32
- "eslint": "^9.38.0",
32
+ "eslint": "^9.39.2",
33
33
  "eslint-config-prettier": "^10.1.8",
34
34
  "eslint-plugin-import": "^2.32.0",
35
35
  "eslint-plugin-prettier": "^5.5.4",
36
36
  "eslint-plugin-simple-import-sort": "^12.1.1",
37
- "globals": "^16.4.0",
38
- "prettier": "^3.6.2",
39
- "rimraf": "^6.0.1",
37
+ "globals": "^16.5.0",
38
+ "prettier": "^3.7.4",
39
+ "rimraf": "^6.1.2",
40
40
  "ts-node": "^10.9.2",
41
- "tsup": "^8.5.0",
41
+ "tsup": "^8.5.1",
42
42
  "typescript": "^5.9.3",
43
- "vitest": "^3.2.4"
43
+ "vitest": "^4.0.16"
44
44
  },
45
45
  "main": "./dist/index.js",
46
46
  "module": "./dist/index.mjs",
@@ -57,10 +57,10 @@
57
57
  "dist"
58
58
  ],
59
59
  "dependencies": {
60
- "@duckdb/node-api": "1.4.1-r.4",
61
- "@duckdb/node-bindings": "1.4.1-r.4",
60
+ "@duckdb/node-api": "^1.4.3-r.2",
61
+ "@duckdb/node-bindings": "^1.4.3-r.2",
62
62
  "debug": "^4.4.3",
63
- "functype": "^0.16.0"
63
+ "functype": "^0.41.3"
64
64
  },
65
65
  "scripts": {
66
66
  "validate": "pnpm format && pnpm lint && pnpm test && pnpm build",