@hedystia/db 2.0.14 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"repository.mjs","names":[],"sources":["../../src/core/repository.ts"],"sourcesContent":["import type { CacheManager } from \"../cache\";\nimport { FileDriver } from \"../drivers/file\";\nimport { S3Driver } from \"../drivers/s3\";\nimport {\n compileBulkInsert,\n compileDelete,\n compileInsert,\n compileSelect,\n compileUpdate,\n compileWhere,\n} from \"../drivers/sql-compiler\";\nimport { QueryError } from \"../errors\";\nimport type { SchemaRegistry } from \"../schema\";\nimport type {\n DatabaseDriver,\n DeleteOptions,\n QueryOptions,\n Repository,\n TableMetadata,\n UpdateOptions,\n WhereClause,\n} from \"../types\";\n\n/**\n * Repository implementation that provides CRUD operations for a table\n * @template T - The row type for this table\n */\nexport class TableRepository<T extends Record<string, any>> implements Repository<T> {\n private tableName: string;\n private driver: DatabaseDriver;\n private cache: CacheManager;\n private registry: SchemaRegistry;\n private meta: TableMetadata;\n private columnMap: Record<string, string>;\n private reverseColumnMap: Record<string, string>;\n private hasAliases: boolean;\n private tableCacheConfig?: { enabled: boolean; ttl?: number; maxTtl?: number };\n private jsonColumns: Set<string>;\n private jsonCodeKeys: Set<string>;\n private isFileLikeDriver: boolean;\n private dateColumns: Set<string>;\n\n constructor(\n tableName: string,\n driver: DatabaseDriver,\n cache: CacheManager,\n registry: SchemaRegistry,\n tableCacheConfig?: { enabled: boolean; ttl?: number; maxTtl?: number },\n ) {\n this.tableName = tableName;\n this.driver = driver;\n this.cache = cache;\n this.registry = registry;\n this.tableCacheConfig = tableCacheConfig;\n const meta = registry.getTable(tableName);\n if (!meta) {\n throw new QueryError(`Table \"${tableName}\" is not registered`);\n }\n this.meta = meta;\n this.columnMap = registry.getColumnMap(tableName);\n this.reverseColumnMap = registry.getReverseColumnMap(tableName);\n this.hasAliases = Object.entries(this.columnMap).some(\n ([codeKey, dbName]) => codeKey !== dbName,\n );\n this.jsonColumns = new Set(\n meta.columns.filter((c) => c.type === \"json\" || c.type === \"array\").map((c) => c.name),\n );\n this.jsonCodeKeys = new Set<string>();\n for (const col of meta.columns) {\n if (col.type === \"json\" || col.type === \"array\") {\n const codeKey = this.reverseColumnMap[col.name] ?? col.name;\n this.jsonCodeKeys.add(codeKey);\n }\n }\n this.dateColumns = new Set<string>();\n for (const col of meta.columns) {\n if (col.type === \"datetime\" || col.type === \"timestamp\") {\n const codeKey = this.reverseColumnMap[col.name] ?? col.name;\n this.dateColumns.add(codeKey);\n }\n }\n this.isFileLikeDriver = driver instanceof FileDriver || driver instanceof S3Driver;\n }\n\n /**\n * Find all rows matching the query options\n * @param {QueryOptions<T>} [options] - Query options\n * @returns {Promise<T[]>} Array of matching rows\n */\n async find(options?: QueryOptions<T>): Promise<T[]> {\n return this.cache.getOrSetWithTableConfig(\n this.tableName,\n \"find\",\n options,\n async () => {\n const dbOptions = this.mapOptionsToDb(options);\n let rows: T[];\n if (this.isFileLikeDriver) {\n rows = this.mapRows(this.findFile(dbOptions as QueryOptions<T>));\n } else {\n rows = this.mapRows(await this.findSQL(dbOptions as QueryOptions<T>));\n }\n if (options?.with) {\n rows = await this.loadRelations(rows, options.with);\n }\n this.cacheEntities(rows);\n return rows;\n },\n this.tableCacheConfig,\n );\n }\n\n /**\n * Find all rows matching the query options (alias for find)\n * @param {QueryOptions<T>} [options] - Query options\n * @returns {Promise<T[]>} Array of matching rows\n */\n async findMany(options?: QueryOptions<T>): Promise<T[]> {\n return this.find(options);\n }\n\n /**\n * Find the first row matching the query options\n * @param {QueryOptions<T>} [options] - Query options\n * @returns {Promise<T | null>} The first matching row or null\n */\n async findFirst(options?: QueryOptions<T>): Promise<T | null> {\n return this.cache.getOrSetWithTableConfig(\n this.tableName,\n \"findFirst\",\n options,\n async () => {\n const dbOptions = this.mapOptionsToDb(options);\n const opts = { ...dbOptions, take: 1 };\n let rows: T[];\n if (this.isFileLikeDriver) {\n rows = this.mapRows(this.findFile(opts as QueryOptions<T>));\n } else {\n rows = this.mapRows(await this.findSQL(opts as QueryOptions<T>));\n }\n if (rows.length === 0) {\n return null;\n }\n if (options?.with) {\n rows = await this.loadRelations(rows, options.with);\n }\n const row = rows[0] ?? null;\n if (row) {\n this.cacheEntity(row);\n }\n return row;\n },\n this.tableCacheConfig,\n );\n }\n\n /**\n * Insert one or more rows\n * @param {Partial<T> | Partial<T>[]} data - Data to insert\n * @returns {Promise<T>} The inserted row\n */\n async insert(data: Partial<T> | Partial<T>[]): Promise<T> {\n const single = Array.isArray(data) ? data[0] : data;\n if (!single) {\n throw new QueryError(\"Insert data cannot be empty\");\n }\n\n this.cache.invalidateTable(this.tableName);\n const cleaned = this.toDbKeys(this.cleanData(single));\n\n if (this.isFileLikeDriver) {\n const id = (this.driver as FileDriver | S3Driver).insertRow(this.tableName, cleaned);\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (pk) {\n cleaned[pk] = id;\n }\n const result = this.toCodeKeys(cleaned) as T;\n this.cacheEntity(result);\n return result;\n }\n\n const params: unknown[] = [];\n const sql = compileInsert(this.tableName, cleaned, params);\n const result = await this.driver.execute(sql, params);\n\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (pk && result.insertId) {\n cleaned[pk] = result.insertId;\n }\n\n const mapped = this.toCodeKeys(cleaned) as T;\n this.cacheEntity(mapped);\n return mapped;\n }\n\n /**\n * Insert multiple rows\n * @param {Partial<T>[]} data - Array of data to insert\n * @returns {Promise<T[]>} The inserted rows\n */\n async insertMany(data: Partial<T>[]): Promise<T[]> {\n if (data.length === 0) {\n return [];\n }\n\n this.cache.invalidateTable(this.tableName);\n\n if (this.isFileLikeDriver) {\n const results: T[] = [];\n for (const item of data) {\n results.push(await this.insert(item));\n }\n return results;\n }\n\n const cleanedData = data.map((item) => this.toDbKeys(this.cleanData(item)));\n const params: unknown[] = [];\n const sql = compileBulkInsert(this.tableName, cleanedData, params);\n const result = await this.driver.execute(sql, params);\n\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (pk && result.insertId) {\n for (let i = 0; i < cleanedData.length; i++) {\n const row = cleanedData[i];\n if (row) {\n row[pk] = result.insertId + i;\n }\n }\n }\n\n const finalRows = this.mapRows(cleanedData);\n this.cacheEntities(finalRows);\n return finalRows;\n }\n\n /**\n * Update rows matching the where clause\n * @param {UpdateOptions<T>} options - Update options with where and data\n * @returns {Promise<T[]>} The updated rows\n */\n async update(options: UpdateOptions<T>): Promise<T[]> {\n if (!options.where || Object.keys(options.where).length === 0) {\n throw new QueryError(\"Update requires a where clause\");\n }\n\n this.cache.invalidateTable(this.tableName);\n const cleaned = this.toDbKeys(this.cleanData(options.data));\n const dbWhere = this.mapWhereToDb(options.where as WhereClause);\n\n if (this.isFileLikeDriver) {\n const filter = this.buildFileFilter(dbWhere);\n (this.driver as FileDriver | S3Driver).updateRows(this.tableName, filter, cleaned);\n return this.find({ where: options.where } as QueryOptions<T>);\n }\n\n const params: unknown[] = [];\n const sql = compileUpdate(this.tableName, cleaned, dbWhere, params);\n await this.driver.execute(sql, params);\n\n return this.find({ where: options.where } as QueryOptions<T>);\n }\n\n /**\n * Delete rows matching the where clause\n * @param {DeleteOptions<T>} options - Delete options with where clause\n * @returns {Promise<number>} Number of deleted rows\n */\n async delete(options: DeleteOptions<T>): Promise<number> {\n if (!options.where || Object.keys(options.where).length === 0) {\n throw new QueryError(\"Delete requires a where clause\");\n }\n\n this.cache.invalidateTable(this.tableName);\n const dbWhere = this.mapWhereToDb(options.where as WhereClause);\n\n if (this.isFileLikeDriver) {\n const filter = this.buildFileFilter(dbWhere);\n return (this.driver as FileDriver | S3Driver).deleteRows(this.tableName, filter);\n }\n\n const params: unknown[] = [];\n const sql = compileDelete(this.tableName, dbWhere, params);\n const result = await this.driver.execute(sql, params);\n return result.affectedRows;\n }\n\n /**\n * Count rows matching the where clause\n * @param {Pick<QueryOptions<T>, \"where\">} [options] - Count options\n * @returns {Promise<number>} Row count\n */\n async count(options?: Pick<QueryOptions<T>, \"where\">): Promise<number> {\n return this.cache.getOrSetWithTableConfig(\n this.tableName,\n \"count\",\n options,\n async () => {\n const dbWhere = options?.where\n ? this.mapWhereToDb(options.where as WhereClause)\n : undefined;\n\n if (this.isFileLikeDriver) {\n const filter = dbWhere ? this.buildFileFilter(dbWhere) : undefined;\n return (this.driver as FileDriver | S3Driver).countRows(this.tableName, filter);\n }\n\n const params: unknown[] = [];\n let sql = `SELECT COUNT(*) as count FROM \\`${this.tableName}\\``;\n if (dbWhere && Object.keys(dbWhere).length > 0) {\n sql += ` WHERE ${compileWhere(dbWhere, params)}`;\n }\n const rows = await this.driver.query(sql, params);\n return rows[0]?.count ?? 0;\n },\n this.tableCacheConfig,\n );\n }\n\n /**\n * Check if any row exists matching the where clause\n * @param {Pick<QueryOptions<T>, \"where\">} options - Exists options\n * @returns {Promise<boolean>} Whether a matching row exists\n */\n async exists(options: Pick<QueryOptions<T>, \"where\">): Promise<boolean> {\n const c = await this.count(options);\n return c > 0;\n }\n\n /**\n * Insert or update a row based on the where clause\n * @param {object} options - Upsert options\n * @param {WhereClause<T>} options.where - Condition to check\n * @param {Partial<T>} options.create - Data to insert if not found\n * @param {Partial<T>} options.update - Data to update if found\n * @returns {Promise<T>} The upserted row\n */\n async upsert(options: {\n where: WhereClause<T>;\n create: Partial<T>;\n update: Partial<T>;\n }): Promise<T> {\n const existing = await this.findFirst({ where: options.where } as QueryOptions<T>);\n if (existing) {\n const updated = await this.update({ where: options.where, data: options.update });\n return updated[0] ?? existing;\n }\n return this.insert(options.create);\n }\n\n /**\n * Remove all rows from the table\n */\n async truncate(): Promise<void> {\n this.cache.invalidateTable(this.tableName);\n if (this.isFileLikeDriver) {\n (this.driver as FileDriver | S3Driver).truncateTable(this.tableName);\n return;\n }\n await this.driver.execute(`DELETE FROM \\`${this.tableName}\\``);\n }\n\n private async findSQL(options?: QueryOptions<T>): Promise<T[]> {\n const params: unknown[] = [];\n const sql = compileSelect(\n this.tableName,\n {\n select: options?.select as string[] | undefined,\n where: options?.where as WhereClause | undefined,\n orderBy: options?.orderBy as Record<string, \"asc\" | \"desc\"> | undefined,\n take: options?.take,\n skip: options?.skip,\n },\n params,\n );\n return this.driver.query(sql, params);\n }\n\n private findFile(options?: QueryOptions<T>): T[] {\n const fd = this.driver as FileDriver | S3Driver;\n const filter = options?.where ? this.buildFileFilter(options.where as WhereClause) : undefined;\n let rows = fd.findRows(this.tableName, filter) as T[];\n\n if (options?.select) {\n const selectSet = new Set(options.select as string[]);\n rows = rows.map((row) => {\n const filtered: Record<string, any> = {};\n for (const key of selectSet) {\n filtered[key as string] = row[key as string];\n }\n return filtered as T;\n });\n }\n\n if (options?.orderBy) {\n const entries = Object.entries(options.orderBy);\n rows.sort((a, b) => {\n for (const [col, dir] of entries) {\n const av = a[col];\n const bv = b[col];\n if (av < bv) {\n return dir === \"asc\" ? -1 : 1;\n }\n if (av > bv) {\n return dir === \"asc\" ? 1 : -1;\n }\n }\n return 0;\n });\n }\n\n if (options?.skip) {\n rows = rows.slice(options.skip);\n }\n if (options?.take) {\n rows = rows.slice(0, options.take);\n }\n\n return rows;\n }\n\n private buildFileFilter(where: WhereClause): (row: Record<string, unknown>) => boolean {\n return (row) => this.matchWhere(row, where);\n }\n\n private matchWhere(row: Record<string, unknown>, where: WhereClause): boolean {\n for (const [key, value] of Object.entries(where)) {\n if (key === \"OR\" && Array.isArray(value)) {\n const any = (value as WhereClause[]).some((sub) => this.matchWhere(row, sub));\n if (!any) {\n return false;\n }\n continue;\n }\n if (key === \"AND\" && Array.isArray(value)) {\n const all = (value as WhereClause[]).every((sub) => this.matchWhere(row, sub));\n if (!all) {\n return false;\n }\n continue;\n }\n\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n const cond = value as any;\n const rowVal = row[key];\n if (cond.eq !== undefined && rowVal !== cond.eq) {\n return false;\n }\n if (cond.neq !== undefined && rowVal === cond.neq) {\n return false;\n }\n if (cond.gt !== undefined && !((rowVal as any) > cond.gt)) {\n return false;\n }\n if (cond.gte !== undefined && !((rowVal as any) >= cond.gte)) {\n return false;\n }\n if (cond.lt !== undefined && !((rowVal as any) < cond.lt)) {\n return false;\n }\n if (cond.lte !== undefined && !((rowVal as any) <= cond.lte)) {\n return false;\n }\n if (\n cond.like !== undefined &&\n !String(rowVal).match(new RegExp(cond.like.replace(/%/g, \".*\"), \"i\"))\n ) {\n return false;\n }\n if (\n cond.notLike !== undefined &&\n String(rowVal).match(new RegExp(cond.notLike.replace(/%/g, \".*\"), \"i\"))\n ) {\n return false;\n }\n if (cond.in !== undefined && !cond.in.includes(rowVal)) {\n return false;\n }\n if (cond.notIn?.includes(rowVal)) {\n return false;\n }\n if (cond.isNull === true && rowVal !== null && rowVal !== undefined) {\n return false;\n }\n if (cond.isNull === false && (rowVal === null || rowVal === undefined)) {\n return false;\n }\n if (cond.between !== undefined) {\n if ((rowVal as any) < cond.between[0] || (rowVal as any) > cond.between[1]) {\n return false;\n }\n }\n } else {\n if (row[key] !== value) {\n return false;\n }\n }\n }\n return true;\n }\n\n private async loadRelations(\n rows: T[],\n withOpts: Record<string, boolean | QueryOptions>,\n ): Promise<T[]> {\n if (rows.length === 0) {\n return rows;\n }\n const relations = this.registry.getRelations(this.tableName);\n\n for (const [relationName, opts] of Object.entries(withOpts)) {\n if (!opts) {\n continue;\n }\n const relation = relations.find((r) => r.relationName === relationName);\n if (!relation) {\n continue;\n }\n\n const isParent = relation.from.table === this.tableName;\n if (isParent) {\n const ids = rows.map((r) => r[relation.from.column]).filter((v) => v != null);\n if (ids.length === 0) {\n continue;\n }\n const uniqueIds = [...new Set(ids)];\n const relatedOpts: QueryOptions = typeof opts === \"object\" ? opts : {};\n const related = await this.findRelated(\n relation.to.table,\n relation.to.column,\n uniqueIds,\n relatedOpts,\n );\n const relatedMap = new Map<unknown, unknown[]>();\n for (const r of related) {\n const key = (r as any)[relation.to.column];\n if (!relatedMap.has(key)) {\n relatedMap.set(key, []);\n }\n relatedMap.get(key)!.push(r);\n }\n for (const row of rows) {\n const key = row[relation.from.column];\n const relRows = relatedMap.get(key);\n (row as any)[relationName] = relRows ?? [];\n }\n } else {\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (!pk) {\n continue;\n }\n const ids = rows.map((r) => r[pk]).filter((v) => v != null);\n if (ids.length === 0) {\n continue;\n }\n const uniqueIds = [...new Set(ids)];\n const relatedOpts: QueryOptions = typeof opts === \"object\" ? opts : {};\n const related = await this.findRelated(\n relation.to.table,\n relation.to.column,\n uniqueIds,\n relatedOpts,\n );\n const relatedMap = new Map<unknown, unknown[]>();\n for (const r of related) {\n const key = (r as any)[relation.to.column];\n if (!relatedMap.has(key)) {\n relatedMap.set(key, []);\n }\n relatedMap.get(key)!.push(r);\n }\n for (const row of rows) {\n const key = row[pk];\n (row as any)[relationName] = relatedMap.get(key) ?? [];\n }\n }\n }\n\n return rows;\n }\n\n private async findRelated(\n tableName: string,\n column: string,\n ids: unknown[],\n options: QueryOptions,\n ): Promise<any[]> {\n if (this.isFileLikeDriver) {\n const fd = this.driver as FileDriver | S3Driver;\n const filter = (row: Record<string, unknown>) => ids.includes(row[column]);\n return fd.findRows(tableName, filter);\n }\n\n const params: unknown[] = [];\n const placeholders = ids.map(() => \"?\").join(\", \");\n params.push(...ids);\n\n let sql = `SELECT * FROM \\`${tableName}\\` WHERE \\`${column}\\` IN (${placeholders})`;\n if (options.orderBy) {\n const orderParts = Object.entries(options.orderBy).map(\n ([col, dir]) => `\\`${col}\\` ${(dir as string).toUpperCase()}`,\n );\n if (orderParts.length > 0) {\n sql += ` ORDER BY ${orderParts.join(\", \")}`;\n }\n }\n if (options.take) {\n sql += ` LIMIT ${options.take}`;\n }\n\n return this.driver.query(sql, params);\n }\n\n private cleanData(data: Partial<T>): Record<string, unknown> {\n const cleaned: Record<string, unknown> = {};\n const dbColumnNames = new Set(this.meta.columns.map((c) => c.name));\n const codeKeys = new Set(Object.keys(this.columnMap));\n const shouldSerialize = !this.isFileLikeDriver;\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n if (codeKeys.has(key) || dbColumnNames.has(key)) {\n if (\n shouldSerialize &&\n (this.jsonCodeKeys.has(key) || this.jsonColumns.has(key)) &&\n value != null &&\n typeof value === \"object\"\n ) {\n cleaned[key] = JSON.stringify(value);\n } else {\n cleaned[key] = value;\n }\n }\n }\n return cleaned;\n }\n\n private cacheEntities(rows: T[]): void {\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (!pk) {\n return;\n }\n for (const row of rows) {\n if (row[pk] != null) {\n this.cache.setEntity(this.tableName, row[pk], row);\n }\n }\n }\n\n private cacheEntity(row: T): void {\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (!pk || row[pk] == null) {\n return;\n }\n this.cache.setEntity(this.tableName, row[pk], row);\n }\n\n private toDbKeys(data: Record<string, unknown>): Record<string, unknown> {\n if (!this.hasAliases) {\n return data;\n }\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n result[this.columnMap[key] ?? key] = value;\n }\n return result;\n }\n\n private toCodeKeys(row: Record<string, unknown>): Record<string, unknown> {\n if (!this.hasAliases) {\n return row;\n }\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row)) {\n result[this.reverseColumnMap[key] ?? key] = value;\n }\n return result;\n }\n\n private mapWhereToDb(where: WhereClause): WhereClause {\n if (!this.hasAliases) {\n return where;\n }\n const result: WhereClause = {};\n for (const [key, value] of Object.entries(where)) {\n if (key === \"OR\" || key === \"AND\") {\n (result as any)[key] = (value as WhereClause[]).map((sub) => this.mapWhereToDb(sub));\n } else {\n result[this.columnMap[key] ?? key] = value;\n }\n }\n return result;\n }\n\n private mapSelectToDb(select: string[]): string[] {\n if (!this.hasAliases) {\n return select;\n }\n return select.map((key) => this.columnMap[key] ?? key);\n }\n\n private mapOrderByToDb(orderBy: Record<string, \"asc\" | \"desc\">): Record<string, \"asc\" | \"desc\"> {\n if (!this.hasAliases) {\n return orderBy;\n }\n const result: Record<string, \"asc\" | \"desc\"> = {};\n for (const [key, value] of Object.entries(orderBy)) {\n result[this.columnMap[key] ?? key] = value;\n }\n return result;\n }\n\n private mapOptionsToDb(options?: QueryOptions<T>): QueryOptions | undefined {\n if (!options || !this.hasAliases) {\n return options as QueryOptions | undefined;\n }\n const mapped: QueryOptions = { ...options } as any;\n if (options.where) {\n mapped.where = this.mapWhereToDb(options.where as WhereClause);\n }\n if (options.select) {\n mapped.select = this.mapSelectToDb(options.select);\n }\n if (options.orderBy) {\n mapped.orderBy = this.mapOrderByToDb(options.orderBy as Record<string, \"asc\" | \"desc\">);\n }\n return mapped;\n }\n\n private mapRows(rows: Record<string, unknown>[]): T[] {\n const shouldDeserialize =\n (this.jsonCodeKeys.size > 0 || this.dateColumns.size > 0) && !this.isFileLikeDriver;\n if (!this.hasAliases && !shouldDeserialize) {\n return rows as T[];\n }\n return rows.map((row) => {\n const mapped = this.hasAliases ? this.toCodeKeys(row) : { ...row };\n if (shouldDeserialize) {\n for (const key of this.jsonCodeKeys) {\n const val = mapped[key];\n if (typeof val === \"string\") {\n try {\n mapped[key] = JSON.parse(val);\n } catch {}\n }\n }\n for (const key of this.dateColumns) {\n const val = mapped[key];\n if (typeof val === \"string\") {\n mapped[key] = new Date(val);\n } else if (typeof val === \"number\") {\n mapped[key] = new Date(val);\n }\n }\n }\n return mapped as T;\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"repository.mjs","names":[],"sources":["../../src/core/repository.ts"],"sourcesContent":["import type { CacheManager } from \"../cache\";\nimport {\n compileBulkInsert,\n compileDelete,\n compileInsert,\n compileSelect,\n compileUpdate,\n compileWhere,\n} from \"../drivers/sql-compiler\";\nimport { QueryError } from \"../errors\";\nimport type { SchemaRegistry } from \"../schema\";\nimport type {\n DatabaseDriver,\n DeleteOptions,\n QueryOptions,\n Repository,\n TableMetadata,\n UpdateOptions,\n WhereClause,\n} from \"../types\";\n\n/**\n * Repository implementation that provides CRUD operations for a table\n * @template T - The row type for this table\n */\nexport class TableRepository<T extends Record<string, any>> implements Repository<T> {\n private tableName: string;\n private driver: DatabaseDriver;\n private cache: CacheManager;\n private registry: SchemaRegistry;\n private meta: TableMetadata;\n private columnMap: Record<string, string>;\n private reverseColumnMap: Record<string, string>;\n private hasAliases: boolean;\n private tableCacheConfig?: { enabled: boolean; ttl?: number; maxTtl?: number };\n private jsonColumns: Set<string>;\n private jsonCodeKeys: Set<string>;\n private dateColumns: Set<string>;\n\n constructor(\n tableName: string,\n driver: DatabaseDriver,\n cache: CacheManager,\n registry: SchemaRegistry,\n tableCacheConfig?: { enabled: boolean; ttl?: number; maxTtl?: number },\n ) {\n this.tableName = tableName;\n this.driver = driver;\n this.cache = cache;\n this.registry = registry;\n this.tableCacheConfig = tableCacheConfig;\n const meta = registry.getTable(tableName);\n if (!meta) {\n throw new QueryError(`Table \"${tableName}\" is not registered`);\n }\n this.meta = meta;\n this.columnMap = registry.getColumnMap(tableName);\n this.reverseColumnMap = registry.getReverseColumnMap(tableName);\n this.hasAliases = Object.entries(this.columnMap).some(\n ([codeKey, dbName]) => codeKey !== dbName,\n );\n this.jsonColumns = new Set(\n meta.columns.filter((c) => c.type === \"json\" || c.type === \"array\").map((c) => c.name),\n );\n this.jsonCodeKeys = new Set<string>();\n for (const col of meta.columns) {\n if (col.type === \"json\" || col.type === \"array\") {\n const codeKey = this.reverseColumnMap[col.name] ?? col.name;\n this.jsonCodeKeys.add(codeKey);\n }\n }\n this.dateColumns = new Set<string>();\n for (const col of meta.columns) {\n if (col.type === \"datetime\" || col.type === \"timestamp\") {\n const codeKey = this.reverseColumnMap[col.name] ?? col.name;\n this.dateColumns.add(codeKey);\n }\n }\n }\n\n /**\n * Find all rows matching the query options\n * @param {QueryOptions<T>} [options] - Query options\n * @returns {Promise<T[]>} Array of matching rows\n */\n async find(options?: QueryOptions<T>): Promise<T[]> {\n return this.cache.getOrSetWithTableConfig(\n this.tableName,\n \"find\",\n options,\n async () => {\n const dbOptions = this.mapOptionsToDb(options);\n const rows = this.mapRows(await this.findSQL(dbOptions as QueryOptions<T>));\n if (options?.with) {\n return this.loadRelations(rows, options.with);\n }\n this.cacheEntities(rows);\n return rows;\n },\n this.tableCacheConfig,\n );\n }\n\n /**\n * Find all rows matching the query options (alias for find)\n * @param {QueryOptions<T>} [options] - Query options\n * @returns {Promise<T[]>} Array of matching rows\n */\n async findMany(options?: QueryOptions<T>): Promise<T[]> {\n return this.find(options);\n }\n\n /**\n * Find the first row matching the query options\n * @param {QueryOptions<T>} [options] - Query options\n * @returns {Promise<T | null>} The first matching row or null\n */\n async findFirst(options?: QueryOptions<T>): Promise<T | null> {\n return this.cache.getOrSetWithTableConfig(\n this.tableName,\n \"findFirst\",\n options,\n async () => {\n const dbOptions = this.mapOptionsToDb(options);\n const opts = { ...dbOptions, take: 1 };\n const rows = this.mapRows(await this.findSQL(opts as QueryOptions<T>));\n if (rows.length === 0) {\n return null;\n }\n if (options?.with) {\n const loaded = await this.loadRelations(rows, options.with);\n const row = loaded[0] ?? null;\n if (row) {\n this.cacheEntity(row);\n }\n return row;\n }\n const row = rows[0] ?? null;\n if (row) {\n this.cacheEntity(row);\n }\n return row;\n },\n this.tableCacheConfig,\n );\n }\n\n /**\n * Insert one or more rows\n * @param {Partial<T> | Partial<T>[]} data - Data to insert\n * @returns {Promise<T>} The inserted row\n */\n async insert(data: Partial<T> | Partial<T>[]): Promise<T> {\n const single = Array.isArray(data) ? data[0] : data;\n if (!single) {\n throw new QueryError(\"Insert data cannot be empty\");\n }\n\n this.cache.invalidateTable(this.tableName);\n const cleaned = this.toDbKeys(this.cleanData(single));\n\n const params: unknown[] = [];\n const sql = compileInsert(this.tableName, cleaned, params);\n const result = await this.driver.execute(sql, params);\n\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (pk && result.insertId) {\n cleaned[pk] = result.insertId;\n }\n\n const [mappedResult] = this.mapRows([cleaned]) as [T];\n this.cacheEntity(mappedResult);\n return mappedResult;\n }\n\n /**\n * Insert multiple rows\n * @param {Partial<T>[]} data - Array of data to insert\n * @returns {Promise<T[]>} The inserted rows\n */\n async insertMany(data: Partial<T>[]): Promise<T[]> {\n if (data.length === 0) {\n return [];\n }\n\n this.cache.invalidateTable(this.tableName);\n\n const cleanedData = data.map((item) => this.toDbKeys(this.cleanData(item)));\n const params: unknown[] = [];\n const sql = compileBulkInsert(this.tableName, cleanedData, params);\n const result = await this.driver.execute(sql, params);\n\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (pk && result.insertId) {\n for (let i = 0; i < cleanedData.length; i++) {\n const row = cleanedData[i];\n if (row) {\n row[pk] = result.insertId + i;\n }\n }\n }\n\n const finalRows = this.mapRows(cleanedData);\n this.cacheEntities(finalRows);\n return finalRows;\n }\n\n /**\n * Update rows matching the where clause\n * @param {UpdateOptions<T>} options - Update options with where and data\n * @returns {Promise<T[]>} The updated rows\n */\n async update(options: UpdateOptions<T>): Promise<T[]> {\n if (!options.where || Object.keys(options.where).length === 0) {\n throw new QueryError(\"Update requires a where clause\");\n }\n\n this.cache.invalidateTable(this.tableName);\n const cleaned = this.toDbKeys(this.cleanData(options.data));\n const dbWhere = this.mapWhereToDb(options.where as WhereClause);\n\n const params: unknown[] = [];\n const sql = compileUpdate(this.tableName, cleaned, dbWhere, params);\n await this.driver.execute(sql, params);\n\n return this.find({ where: options.where } as QueryOptions<T>);\n }\n\n /**\n * Delete rows matching the where clause\n * @param {DeleteOptions<T>} options - Delete options with where clause\n * @returns {Promise<number>} Number of deleted rows\n */\n async delete(options: DeleteOptions<T>): Promise<number> {\n if (!options.where || Object.keys(options.where).length === 0) {\n throw new QueryError(\"Delete requires a where clause\");\n }\n\n this.cache.invalidateTable(this.tableName);\n const dbWhere = this.mapWhereToDb(options.where as WhereClause);\n\n const params: unknown[] = [];\n const sql = compileDelete(this.tableName, dbWhere, params);\n const result = await this.driver.execute(sql, params);\n return result.affectedRows;\n }\n\n /**\n * Count rows matching the where clause\n * @param {Pick<QueryOptions<T>, \"where\">} [options] - Count options\n * @returns {Promise<number>} Row count\n */\n async count(options?: Pick<QueryOptions<T>, \"where\">): Promise<number> {\n return this.cache.getOrSetWithTableConfig(\n this.tableName,\n \"count\",\n options,\n async () => {\n const dbWhere = options?.where\n ? this.mapWhereToDb(options.where as WhereClause)\n : undefined;\n\n const params: unknown[] = [];\n let sql = `SELECT COUNT(*) as count FROM \\`${this.tableName}\\``;\n if (dbWhere && Object.keys(dbWhere).length > 0) {\n sql += ` WHERE ${compileWhere(dbWhere, params)}`;\n }\n const rows = await this.driver.query(sql, params);\n return rows[0]?.count ?? 0;\n },\n this.tableCacheConfig,\n );\n }\n\n /**\n * Check if any row exists matching the where clause\n * @param {Pick<QueryOptions<T>, \"where\">} options - Exists options\n * @returns {Promise<boolean>} Whether a matching row exists\n */\n async exists(options: Pick<QueryOptions<T>, \"where\">): Promise<boolean> {\n const c = await this.count(options);\n return c > 0;\n }\n\n /**\n * Insert or update a row based on the where clause\n * @param {object} options - Upsert options\n * @param {WhereClause<T>} options.where - Condition to check\n * @param {Partial<T>} options.create - Data to insert if not found\n * @param {Partial<T>} options.update - Data to update if found\n * @returns {Promise<T>} The upserted row\n */\n async upsert(options: {\n where: WhereClause<T>;\n create: Partial<T>;\n update: Partial<T>;\n }): Promise<T> {\n const existing = await this.findFirst({ where: options.where } as QueryOptions<T>);\n if (existing) {\n const updated = await this.update({ where: options.where, data: options.update });\n return updated[0] ?? existing;\n }\n return this.insert(options.create);\n }\n\n /**\n * Remove all rows from the table\n */\n async truncate(): Promise<void> {\n this.cache.invalidateTable(this.tableName);\n await this.driver.execute(`DELETE FROM \\`${this.tableName}\\``);\n }\n\n private async findSQL(options?: QueryOptions<T>): Promise<T[]> {\n const params: unknown[] = [];\n const sql = compileSelect(\n this.tableName,\n {\n select: options?.select as string[] | undefined,\n where: options?.where as WhereClause | undefined,\n orderBy: options?.orderBy as Record<string, \"asc\" | \"desc\"> | undefined,\n take: options?.take,\n skip: options?.skip,\n },\n params,\n );\n return this.driver.query(sql, params);\n }\n\n private matchWhere(row: Record<string, unknown>, where: WhereClause): boolean {\n for (const [key, value] of Object.entries(where)) {\n if (key === \"OR\" && Array.isArray(value)) {\n const any = (value as WhereClause[]).some((sub) => this.matchWhere(row, sub));\n if (!any) {\n return false;\n }\n continue;\n }\n if (key === \"AND\" && Array.isArray(value)) {\n const all = (value as WhereClause[]).every((sub) => this.matchWhere(row, sub));\n if (!all) {\n return false;\n }\n continue;\n }\n\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n const cond = value as any;\n const rowVal = row[key];\n if (cond.eq !== undefined && rowVal !== cond.eq) {\n return false;\n }\n if (cond.neq !== undefined && rowVal === cond.neq) {\n return false;\n }\n if (cond.gt !== undefined && !((rowVal as any) > cond.gt)) {\n return false;\n }\n if (cond.gte !== undefined && !((rowVal as any) >= cond.gte)) {\n return false;\n }\n if (cond.lt !== undefined && !((rowVal as any) < cond.lt)) {\n return false;\n }\n if (cond.lte !== undefined && !((rowVal as any) <= cond.lte)) {\n return false;\n }\n if (\n cond.like !== undefined &&\n !String(rowVal).match(new RegExp(cond.like.replace(/%/g, \".*\"), \"i\"))\n ) {\n return false;\n }\n if (\n cond.notLike !== undefined &&\n String(rowVal).match(new RegExp(cond.notLike.replace(/%/g, \".*\"), \"i\"))\n ) {\n return false;\n }\n if (cond.in !== undefined && !cond.in.includes(rowVal)) {\n return false;\n }\n if (cond.notIn?.includes(rowVal)) {\n return false;\n }\n if (cond.isNull === true && rowVal !== null && rowVal !== undefined) {\n return false;\n }\n if (cond.isNull === false && (rowVal === null || rowVal === undefined)) {\n return false;\n }\n if (cond.between !== undefined) {\n if ((rowVal as any) < cond.between[0] || (rowVal as any) > cond.between[1]) {\n return false;\n }\n }\n } else {\n if (row[key] !== value) {\n return false;\n }\n }\n }\n return true;\n }\n\n private async loadRelations(\n rows: T[],\n withOpts: Record<string, boolean | QueryOptions>,\n ): Promise<T[]> {\n if (rows.length === 0) {\n return rows;\n }\n const relations = this.registry.getRelations(this.tableName);\n\n for (const [relationName, opts] of Object.entries(withOpts)) {\n if (!opts) {\n continue;\n }\n const relation = relations.find((r) => r.relationName === relationName);\n if (!relation) {\n continue;\n }\n\n const isParent = relation.from.table === this.tableName;\n if (isParent) {\n const ids = rows.map((r) => r[relation.from.column]).filter((v) => v != null);\n if (ids.length === 0) {\n continue;\n }\n const uniqueIds = [...new Set(ids)];\n const relatedOpts: QueryOptions = typeof opts === \"object\" ? opts : {};\n const related = await this.findRelated(\n relation.to.table,\n relation.to.column,\n uniqueIds,\n relatedOpts,\n );\n const relatedMap = new Map<unknown, unknown[]>();\n for (const r of related) {\n const key = (r as any)[relation.to.column];\n if (!relatedMap.has(key)) {\n relatedMap.set(key, []);\n }\n relatedMap.get(key)!.push(r);\n }\n for (const row of rows) {\n const key = row[relation.from.column];\n const relRows = relatedMap.get(key);\n (row as any)[relationName] = relRows ?? [];\n }\n } else {\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (!pk) {\n continue;\n }\n const ids = rows.map((r) => r[pk]).filter((v) => v != null);\n if (ids.length === 0) {\n continue;\n }\n const uniqueIds = [...new Set(ids)];\n const relatedOpts: QueryOptions = typeof opts === \"object\" ? opts : {};\n const related = await this.findRelated(\n relation.to.table,\n relation.to.column,\n uniqueIds,\n relatedOpts,\n );\n const relatedMap = new Map<unknown, unknown[]>();\n for (const r of related) {\n const key = (r as any)[relation.to.column];\n if (!relatedMap.has(key)) {\n relatedMap.set(key, []);\n }\n relatedMap.get(key)!.push(r);\n }\n for (const row of rows) {\n const key = row[pk];\n (row as any)[relationName] = relatedMap.get(key) ?? [];\n }\n }\n }\n\n return rows;\n }\n\n private async findRelated(\n tableName: string,\n column: string,\n ids: unknown[],\n options: QueryOptions,\n ): Promise<any[]> {\n const params: unknown[] = [];\n const placeholders = ids.map(() => \"?\").join(\", \");\n params.push(...ids);\n\n let sql = `SELECT * FROM \\`${tableName}\\` WHERE \\`${column}\\` IN (${placeholders})`;\n if (options.orderBy) {\n const orderParts = Object.entries(options.orderBy).map(\n ([col, dir]) => `\\`${col}\\` ${(dir as string).toUpperCase()}`,\n );\n if (orderParts.length > 0) {\n sql += ` ORDER BY ${orderParts.join(\", \")}`;\n }\n }\n if (options.take) {\n sql += ` LIMIT ${options.take}`;\n }\n\n return this.driver.query(sql, params);\n }\n\n private cleanData(data: Partial<T>): Record<string, unknown> {\n const cleaned: Record<string, unknown> = {};\n const dbColumnNames = new Set(this.meta.columns.map((c) => c.name));\n const codeKeys = new Set(Object.keys(this.columnMap));\n const shouldSerialize = true;\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n if (codeKeys.has(key) || dbColumnNames.has(key)) {\n if (\n shouldSerialize &&\n (this.jsonCodeKeys.has(key) || this.jsonColumns.has(key)) &&\n value != null &&\n typeof value === \"object\"\n ) {\n cleaned[key] = JSON.stringify(value);\n } else {\n cleaned[key] = value;\n }\n }\n }\n return cleaned;\n }\n\n private cacheEntities(rows: T[]): void {\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (!pk) {\n return;\n }\n for (const row of rows) {\n if (row[pk] != null) {\n this.cache.setEntity(this.tableName, row[pk], row);\n }\n }\n }\n\n private cacheEntity(row: T): void {\n const pk = this.registry.getPrimaryKey(this.tableName);\n if (!pk || row[pk] == null) {\n return;\n }\n this.cache.setEntity(this.tableName, row[pk], row);\n }\n\n private toDbKeys(data: Record<string, unknown>): Record<string, unknown> {\n if (!this.hasAliases) {\n return data;\n }\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n result[this.columnMap[key] ?? key] = value;\n }\n return result;\n }\n\n private toCodeKeys(row: Record<string, unknown>): Record<string, unknown> {\n if (!this.hasAliases) {\n return row;\n }\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row)) {\n result[this.reverseColumnMap[key] ?? key] = value;\n }\n return result;\n }\n\n private mapWhereToDb(where: WhereClause): WhereClause {\n if (!this.hasAliases) {\n return where;\n }\n const result: WhereClause = {};\n for (const [key, value] of Object.entries(where)) {\n if (key === \"OR\" || key === \"AND\") {\n (result as any)[key] = (value as WhereClause[]).map((sub) => this.mapWhereToDb(sub));\n } else {\n result[this.columnMap[key] ?? key] = value;\n }\n }\n return result;\n }\n\n private mapSelectToDb(select: string[]): string[] {\n if (!this.hasAliases) {\n return select;\n }\n return select.map((key) => this.columnMap[key] ?? key);\n }\n\n private mapOrderByToDb(orderBy: Record<string, \"asc\" | \"desc\">): Record<string, \"asc\" | \"desc\"> {\n if (!this.hasAliases) {\n return orderBy;\n }\n const result: Record<string, \"asc\" | \"desc\"> = {};\n for (const [key, value] of Object.entries(orderBy)) {\n result[this.columnMap[key] ?? key] = value;\n }\n return result;\n }\n\n private mapOptionsToDb(options?: QueryOptions<T>): QueryOptions | undefined {\n if (!options || !this.hasAliases) {\n return options as QueryOptions | undefined;\n }\n const mapped: QueryOptions = { ...options } as any;\n if (options.where) {\n mapped.where = this.mapWhereToDb(options.where as WhereClause);\n }\n if (options.select) {\n mapped.select = this.mapSelectToDb(options.select);\n }\n if (options.orderBy) {\n mapped.orderBy = this.mapOrderByToDb(options.orderBy as Record<string, \"asc\" | \"desc\">);\n }\n return mapped;\n }\n\n private mapRows(rows: Record<string, unknown>[]): T[] {\n const shouldDeserialize = this.jsonCodeKeys.size > 0 || this.dateColumns.size > 0;\n if (!this.hasAliases && !shouldDeserialize) {\n return rows as T[];\n }\n return rows.map((row) => {\n const mapped = this.hasAliases ? this.toCodeKeys(row) : { ...row };\n if (shouldDeserialize) {\n for (const key of this.jsonCodeKeys) {\n const val = mapped[key];\n if (typeof val === \"string\") {\n try {\n mapped[key] = JSON.parse(val);\n } catch {}\n }\n }\n for (const key of this.dateColumns) {\n const val = mapped[key];\n if (typeof val === \"string\") {\n mapped[key] = new Date(val);\n } else if (typeof val === \"number\") {\n mapped[key] = new Date(val);\n }\n }\n }\n return mapped as T;\n });\n }\n}\n"],"mappings":"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"}
@@ -158,105 +158,6 @@ var FileDriver = class extends require_driver.BaseDriver {
158
158
  for (const [name, tableData] of this.data) result[name] = [...tableData.meta.columns];
159
159
  return result;
160
160
  }
161
- /**
162
- * Direct access for the repository to perform typed operations
163
- * @param {string} tableName - Table name
164
- * @returns {FileTableData | undefined} Table data
165
- */
166
- getTableData(tableName) {
167
- return this.data.get(tableName);
168
- }
169
- /**
170
- * Insert a row directly into the file store
171
- * @param {string} tableName - Table name
172
- * @param {Record<string, unknown>} row - Row data
173
- * @returns {number} The insert ID
174
- */
175
- insertRow(tableName, row) {
176
- const tableData = this.data.get(tableName);
177
- if (!tableData) throw new require_errors.DriverError(`Table "${tableName}" does not exist`);
178
- let pkCol = null;
179
- for (const col of tableData.meta.columns) if (col.autoIncrement) {
180
- pkCol = col.name;
181
- break;
182
- }
183
- if (pkCol && row[pkCol] === void 0) {
184
- tableData.autoIncrementId++;
185
- row[pkCol] = tableData.autoIncrementId;
186
- }
187
- tableData.rows.push({ ...row });
188
- this.flush(tableName);
189
- return pkCol ? row[pkCol] : 0;
190
- }
191
- /**
192
- * Find rows matching a filter function
193
- * @param {string} tableName - Table name
194
- * @param {(row: Record<string, unknown>) => boolean} filter - Filter function
195
- * @returns {Record<string, unknown>[]} Matching rows
196
- */
197
- findRows(tableName, filter) {
198
- const tableData = this.data.get(tableName);
199
- if (!tableData) return [];
200
- if (!filter) return tableData.rows.map((r) => ({ ...r }));
201
- return tableData.rows.filter(filter).map((r) => ({ ...r }));
202
- }
203
- /**
204
- * Update rows matching a filter
205
- * @param {string} tableName - Table name
206
- * @param {(row: Record<string, unknown>) => boolean} filter - Filter function
207
- * @param {Record<string, unknown>} data - Update data
208
- * @returns {number} Number of affected rows
209
- */
210
- updateRows(tableName, filter, data) {
211
- const tableData = this.data.get(tableName);
212
- if (!tableData) return 0;
213
- let count = 0;
214
- for (const row of tableData.rows) if (filter(row)) {
215
- Object.assign(row, data);
216
- count++;
217
- }
218
- if (count > 0) this.flush(tableName);
219
- return count;
220
- }
221
- /**
222
- * Delete rows matching a filter
223
- * @param {string} tableName - Table name
224
- * @param {(row: Record<string, unknown>) => boolean} filter - Filter function
225
- * @returns {number} Number of deleted rows
226
- */
227
- deleteRows(tableName, filter) {
228
- const tableData = this.data.get(tableName);
229
- if (!tableData) return 0;
230
- const before = tableData.rows.length;
231
- tableData.rows = tableData.rows.filter((r) => !filter(r));
232
- const deleted = before - tableData.rows.length;
233
- if (deleted > 0) this.flush(tableName);
234
- return deleted;
235
- }
236
- /**
237
- * Truncate a table
238
- * @param {string} tableName - Table name
239
- */
240
- truncateTable(tableName) {
241
- const tableData = this.data.get(tableName);
242
- if (tableData) {
243
- tableData.rows = [];
244
- tableData.autoIncrementId = 0;
245
- this.flush(tableName);
246
- }
247
- }
248
- /**
249
- * Count rows matching a filter
250
- * @param {string} tableName - Table name
251
- * @param {(row: Record<string, unknown>) => boolean} [filter] - Filter function
252
- * @returns {number} Row count
253
- */
254
- countRows(tableName, filter) {
255
- const tableData = this.data.get(tableName);
256
- if (!tableData) return 0;
257
- if (!filter) return tableData.rows.length;
258
- return tableData.rows.filter(filter).length;
259
- }
260
161
  loadAll() {
261
162
  if (!(0, fs.existsSync)(this.config.directory)) return;
262
163
  const { readdirSync } = require("fs");
@@ -291,42 +192,306 @@ var FileDriver = class extends require_driver.BaseDriver {
291
192
  affectedRows: 0
292
193
  };
293
194
  }
294
- if (trimmed.startsWith("SELECT")) {
195
+ if (trimmed.startsWith("INSERT")) return this.handleInsert(sql, params);
196
+ if (trimmed.startsWith("UPDATE")) return this.handleUpdate(sql, params);
197
+ if (trimmed.startsWith("DELETE")) return this.handleDelete(sql, params);
198
+ if (trimmed.startsWith("SELECT")) return this.handleSelect(sql, params);
199
+ return {
200
+ insertId: 0,
201
+ affectedRows: 0
202
+ };
203
+ }
204
+ handleInsert(sql, params) {
205
+ const tableMatch = sql.match(/INTO\s+`?(\w+)`?/i);
206
+ if (!tableMatch?.[1]) return {
207
+ insertId: 0,
208
+ affectedRows: 0
209
+ };
210
+ const tableName = tableMatch[1];
211
+ const tableData = this.data.get(tableName);
212
+ if (!tableData) return {
213
+ insertId: 0,
214
+ affectedRows: 0
215
+ };
216
+ const colMatch = sql.match(/\(([^)]+)\)\s+VALUES/i);
217
+ if (!colMatch?.[1]) return {
218
+ insertId: 0,
219
+ affectedRows: 0
220
+ };
221
+ const cols = colMatch[1].split(",").map((c) => c.trim().replace(/`/g, "")).filter(Boolean);
222
+ const rowCount = Math.floor(params.length / cols.length);
223
+ let insertId = 0;
224
+ let pkCol = null;
225
+ for (const col of tableData.meta.columns) if (col.autoIncrement) {
226
+ pkCol = col.name;
227
+ break;
228
+ }
229
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
230
+ const row = {};
231
+ const rowParamStart = rowIdx * cols.length;
232
+ for (let i = 0; i < cols.length; i++) {
233
+ const col = cols[i];
234
+ if (col) row[col] = params[rowParamStart + i];
235
+ }
236
+ if (pkCol && row[pkCol] === void 0) {
237
+ tableData.autoIncrementId++;
238
+ row[pkCol] = tableData.autoIncrementId;
239
+ if (rowIdx === 0) insertId = tableData.autoIncrementId;
240
+ } else if (pkCol && row[pkCol]) {
241
+ if (rowIdx === 0) insertId = row[pkCol];
242
+ }
243
+ tableData.rows.push({ ...row });
244
+ }
245
+ this.flush(tableName);
246
+ return {
247
+ insertId,
248
+ affectedRows: rowCount
249
+ };
250
+ }
251
+ handleUpdate(sql, params) {
252
+ const tableMatch = sql.match(/UPDATE\s+`?(\w+)`?/i);
253
+ if (!tableMatch?.[1]) return { affectedRows: 0 };
254
+ const tableName = tableMatch[1];
255
+ const tableData = this.data.get(tableName);
256
+ if (!tableData) return { affectedRows: 0 };
257
+ const setMatch = sql.match(/SET\s+(.+?)\s+WHERE/i) || sql.match(/SET\s+(.+)$/i);
258
+ if (!setMatch?.[1]) return { affectedRows: 0 };
259
+ const setClauses = setMatch[1].split(",");
260
+ const updates = {};
261
+ let paramIndex = 0;
262
+ for (const clause of setClauses) {
263
+ const eqMatch = clause.match(/`?(\w+)`?\s*=\s*\?/);
264
+ if (eqMatch?.[1]) {
265
+ updates[eqMatch[1]] = params[paramIndex];
266
+ paramIndex++;
267
+ }
268
+ }
269
+ const whereConditions = sql.match(/WHERE\s+(.+)$/i)?.[1] || "";
270
+ let affectedRows = 0;
271
+ for (const row of tableData.rows) if (this.matchesWhere(row, whereConditions, params, paramIndex)) {
272
+ Object.assign(row, updates);
273
+ affectedRows++;
274
+ }
275
+ if (affectedRows > 0) this.flush(tableName);
276
+ return { affectedRows };
277
+ }
278
+ handleDelete(sql, params) {
279
+ const tableMatch = sql.match(/FROM\s+`?(\w+)`?/i);
280
+ if (!tableMatch?.[1]) return { affectedRows: 0 };
281
+ const tableName = tableMatch[1];
282
+ const tableData = this.data.get(tableName);
283
+ if (!tableData) return { affectedRows: 0 };
284
+ const whereMatch = sql.match(/WHERE\s+(.+)$/i);
285
+ if (!whereMatch?.[1]) {
286
+ const affectedRows = tableData.rows.length;
287
+ tableData.rows = [];
288
+ if (affectedRows > 0) this.flush(tableName);
289
+ return { affectedRows };
290
+ }
291
+ const whereConditions = whereMatch[1];
292
+ const before = tableData.rows.length;
293
+ tableData.rows = tableData.rows.filter((row) => !this.matchesWhere(row, whereConditions, params, 0));
294
+ const affectedRows = before - tableData.rows.length;
295
+ if (affectedRows > 0) this.flush(tableName);
296
+ return { affectedRows };
297
+ }
298
+ handleSelect(sql, params) {
299
+ if (sql.match(/SELECT\s+COUNT\s*\(\s*\*\s*\)\s+as\s+(\w+)/i)) {
295
300
  const tableMatch = sql.match(/FROM\s+`?(\w+)`?/i);
296
- if (!tableMatch) return [];
301
+ if (!tableMatch?.[1]) return [];
297
302
  const tableName = tableMatch[1];
298
- if (!tableName) return [];
299
- return this.findRows(tableName);
303
+ const tableData = this.data.get(tableName);
304
+ if (!tableData) return [{ count: 0 }];
305
+ const whereMatch = sql.match(/WHERE\s+(.+?)(?:\s+ORDER|\s+LIMIT|\s*$)/i);
306
+ if (whereMatch?.[1]) {
307
+ const filter = (row) => this.matchesWhere(row, whereMatch[1], params, 0);
308
+ return [{ count: tableData.rows.filter(filter).length }];
309
+ }
310
+ return [{ count: tableData.rows.length }];
300
311
  }
301
- if (trimmed.startsWith("INSERT")) {
302
- const tableMatch = sql.match(/INTO\s+`?(\w+)`?/i);
303
- if (!tableMatch) return {
304
- insertId: 0,
305
- affectedRows: 0
312
+ const tableMatch = sql.match(/FROM\s+`?(\w+)`?/i);
313
+ if (!tableMatch?.[1]) return [];
314
+ const tableName = tableMatch[1];
315
+ const tableData = this.data.get(tableName);
316
+ if (!tableData) return [];
317
+ let rows = [...tableData.rows];
318
+ const whereMatch = sql.match(/WHERE\s+(.+?)(?:\s+ORDER\s+BY|\s+LIMIT|\s*$)/i);
319
+ if (whereMatch?.[1]) rows = rows.filter((row) => this.matchesWhere(row, whereMatch[1], params, 0));
320
+ const orderMatch = sql.match(/ORDER\s+BY\s+(.+?)(?:\s+LIMIT|\s*$)/i);
321
+ if (orderMatch?.[1]) {
322
+ const orders = orderMatch[1].split(",");
323
+ rows.sort((a, b) => {
324
+ for (const orderClause of orders) {
325
+ const [col, dir] = orderClause.trim().split(/\s+/);
326
+ const colName = col?.replace(/`/g, "") ?? "";
327
+ const aVal = a[colName];
328
+ const bVal = b[colName];
329
+ if (aVal < bVal) return dir?.toUpperCase() === "DESC" ? 1 : -1;
330
+ if (aVal > bVal) return dir?.toUpperCase() === "DESC" ? -1 : 1;
331
+ }
332
+ return 0;
333
+ });
334
+ }
335
+ const limitMatch = sql.match(/LIMIT\s+(\d+)(?:\s+OFFSET\s+(\d+)|\s*,\s*(\d+))?/i);
336
+ if (limitMatch) {
337
+ const limit = Number.parseInt(limitMatch[1], 10);
338
+ const offset = limitMatch[2] ? Number.parseInt(limitMatch[2], 10) : limitMatch[3] ? Number.parseInt(limitMatch[3], 10) : 0;
339
+ rows = rows.slice(offset, offset + limit);
340
+ }
341
+ return rows.map((r) => ({ ...r }));
342
+ }
343
+ matchesWhere(row, whereStr, params, paramOffset) {
344
+ return this.evaluateWhereExpression(row, whereStr, params, paramOffset).matched;
345
+ }
346
+ evaluateWhereExpression(row, expr, params, startParamIdx) {
347
+ let paramIdx = startParamIdx;
348
+ expr = expr.trim();
349
+ if (expr.startsWith("(") && expr.endsWith(")")) return this.evaluateWhereExpression(row, expr.slice(1, -1), params, paramIdx);
350
+ const orClauses = this.splitByTopLevel(expr, "OR");
351
+ if (orClauses.length > 1) {
352
+ for (const orClause of orClauses) {
353
+ const result = this.evaluateWhereExpression(row, orClause.trim(), params, paramIdx);
354
+ paramIdx = result.paramIdx;
355
+ if (result.matched) return {
356
+ matched: true,
357
+ paramIdx
358
+ };
359
+ }
360
+ return {
361
+ matched: false,
362
+ paramIdx
306
363
  };
307
- const colMatch = sql.match(/\(([^)]+)\)\s+VALUES/i);
308
- if (!colMatch) return {
309
- insertId: 0,
310
- affectedRows: 0
364
+ }
365
+ const andClauses = this.splitByTopLevel(expr, "AND");
366
+ if (andClauses.length > 1) {
367
+ for (const andClause of andClauses) {
368
+ const result = this.evaluateWhereExpression(row, andClause.trim(), params, paramIdx);
369
+ paramIdx = result.paramIdx;
370
+ if (!result.matched) return {
371
+ matched: false,
372
+ paramIdx
373
+ };
374
+ }
375
+ return {
376
+ matched: true,
377
+ paramIdx
311
378
  };
312
- const cols = colMatch[1]?.split(",").map((c) => c.trim().replace(/`/g, ""));
313
- if (!cols) return {
314
- insertId: 0,
315
- affectedRows: 0
379
+ }
380
+ return this.evaluateSingleCondition(row, expr, params, paramIdx);
381
+ }
382
+ splitByTopLevel(expr, operator) {
383
+ const parts = [];
384
+ let current = "";
385
+ let parenDepth = 0;
386
+ const opRegex = new RegExp(`\\s+${operator}\\s+`, "i");
387
+ let i = 0;
388
+ while (i < expr.length) {
389
+ const char = expr[i];
390
+ if (char === "(") {
391
+ parenDepth++;
392
+ current += char;
393
+ i++;
394
+ } else if (char === ")") {
395
+ parenDepth--;
396
+ current += char;
397
+ i++;
398
+ } else if (parenDepth === 0) {
399
+ const match = expr.slice(i).match(opRegex);
400
+ if (match && match.index === 0) {
401
+ if (current.trim()) parts.push(current.trim());
402
+ i += match[0].length;
403
+ current = "";
404
+ } else {
405
+ current += char;
406
+ i++;
407
+ }
408
+ } else {
409
+ current += char;
410
+ i++;
411
+ }
412
+ }
413
+ if (current.trim()) parts.push(current.trim());
414
+ return parts.length > 0 ? parts : [expr];
415
+ }
416
+ evaluateSingleCondition(row, condition, params, paramIdx) {
417
+ condition = condition.trim();
418
+ const notInMatch = condition.match(/`?(\w+)`?\s+NOT\s+IN\s+\(([^)]*)\)/i);
419
+ if (notInMatch) {
420
+ const col = notInMatch[1];
421
+ const placeholders = notInMatch[2].split(",").map((p) => p.trim());
422
+ const values = [];
423
+ for (const placeholder of placeholders) if (placeholder === "?") {
424
+ values.push(params[paramIdx]);
425
+ paramIdx++;
426
+ }
427
+ return {
428
+ matched: !values.includes(row[col]),
429
+ paramIdx
316
430
  };
317
- const row = {};
318
- for (let i = 0; i < cols.length; i++) {
319
- const col = cols[i];
320
- if (col) row[col] = params[i];
431
+ }
432
+ const inMatch = condition.match(/`?(\w+)`?\s+IN\s+\(([^)]*)\)/i);
433
+ if (inMatch) {
434
+ const col = inMatch[1];
435
+ const placeholders = inMatch[2].split(",").map((p) => p.trim());
436
+ const values = [];
437
+ for (const placeholder of placeholders) if (placeholder === "?") {
438
+ values.push(params[paramIdx]);
439
+ paramIdx++;
321
440
  }
322
441
  return {
323
- insertId: this.insertRow(tableMatch[1], row),
324
- affectedRows: 1
442
+ matched: values.includes(row[col]),
443
+ paramIdx
444
+ };
445
+ }
446
+ const eqMatch = condition.match(/`?(\w+)`?\s*=\s*\?/);
447
+ if (eqMatch) return {
448
+ matched: row[eqMatch[1]] === params[paramIdx],
449
+ paramIdx: paramIdx + 1
450
+ };
451
+ const neqMatch = condition.match(/`?(\w+)`?\s*!=\s*\?/);
452
+ if (neqMatch) return {
453
+ matched: row[neqMatch[1]] !== params[paramIdx],
454
+ paramIdx: paramIdx + 1
455
+ };
456
+ const gtMatch = condition.match(/`?(\w+)`?\s*>\s*\?/);
457
+ if (gtMatch) return {
458
+ matched: row[gtMatch[1]] > params[paramIdx],
459
+ paramIdx: paramIdx + 1
460
+ };
461
+ const gteMatch = condition.match(/`?(\w+)`?\s*>=\s*\?/);
462
+ if (gteMatch) return {
463
+ matched: row[gteMatch[1]] >= params[paramIdx],
464
+ paramIdx: paramIdx + 1
465
+ };
466
+ const ltMatch = condition.match(/`?(\w+)`?\s*<\s*\?/);
467
+ if (ltMatch) return {
468
+ matched: row[ltMatch[1]] < params[paramIdx],
469
+ paramIdx: paramIdx + 1
470
+ };
471
+ const lteMatch = condition.match(/`?(\w+)`?\s*<=\s*\?/);
472
+ if (lteMatch) return {
473
+ matched: row[lteMatch[1]] <= params[paramIdx],
474
+ paramIdx: paramIdx + 1
475
+ };
476
+ const notLikeMatch = condition.match(/`?(\w+)`?\s+NOT\s+LIKE\s+\?/i);
477
+ if (notLikeMatch) {
478
+ const pattern = params[paramIdx].replace(/%/g, ".*");
479
+ return {
480
+ matched: !new RegExp(`^${pattern}$`).test(String(row[notLikeMatch[1]])),
481
+ paramIdx: paramIdx + 1
482
+ };
483
+ }
484
+ const likeMatch = condition.match(/`?(\w+)`?\s+LIKE\s+\?/i);
485
+ if (likeMatch) {
486
+ const pattern = params[paramIdx].replace(/%/g, ".*");
487
+ return {
488
+ matched: new RegExp(`^${pattern}$`).test(String(row[likeMatch[1]])),
489
+ paramIdx: paramIdx + 1
325
490
  };
326
491
  }
327
492
  return {
328
- insertId: 0,
329
- affectedRows: 0
493
+ matched: true,
494
+ paramIdx
330
495
  };
331
496
  }
332
497
  };