From 901efc4548f14728164e55a49e82bdd833bbb1c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:20:14 +0000 Subject: [PATCH 1/9] Initial plan From e9ec0e2026ce39ef7bf4b59797f218d331f2ddb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:23:07 +0000 Subject: [PATCH 2/9] Fix undefined driver access in afterEach hook Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/tools/driver-tck/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tools/driver-tck/src/index.ts b/packages/tools/driver-tck/src/index.ts index f7ba8552..7fc2846e 100644 --- a/packages/tools/driver-tck/src/index.ts +++ b/packages/tools/driver-tck/src/index.ts @@ -56,7 +56,7 @@ export function runDriverTCK( beforeEach(async () => { driver = createDriver(); - if (driver.clear) { + if (driver && driver.clear) { await driver.clear(); } if (hooks.beforeEach) { @@ -68,7 +68,7 @@ export function runDriverTCK( if (hooks.afterEach) { await hooks.afterEach(); } - if (driver.clear) { + if (driver && driver.clear) { await driver.clear(); } }, timeout); From 0e120eea6ad4b48a5c28acc61e5d226dc546e7d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:35:09 +0000 Subject: [PATCH 3/9] Initial plan From dfb4b82e5a268fd59a3296316c72fb04ca77acd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:52:29 +0000 Subject: [PATCH 4/9] Fix SQL driver TCK tests - change better-sqlite3 to sqlite3 and add table initialization Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/drivers/sql/test/tck.test.ts | 35 +++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/drivers/sql/test/tck.test.ts b/packages/drivers/sql/test/tck.test.ts index 636ef6fb..841b49a7 100644 --- a/packages/drivers/sql/test/tck.test.ts +++ b/packages/drivers/sql/test/tck.test.ts @@ -22,7 +22,7 @@ describe('SqlDriver TCK Compliance', () => { () => { // Use SQLite in-memory database for testing driver = new SqlDriver({ - client: 'better-sqlite3', + client: 'sqlite3', connection: { filename: ':memory:' }, @@ -38,24 +38,29 @@ describe('SqlDriver TCK Compliance', () => { timeout: 30000, hooks: { beforeEach: async () => { - // Clear all tables - if (driver) { - try { - const tables = await driver['knex'].raw(` - SELECT name FROM sqlite_master - WHERE type='table' AND name NOT LIKE 'sqlite_%' - `); - - for (const table of tables) { - await driver['knex'].raw(`DROP TABLE IF EXISTS "${table.name}"`); + // Initialize the tck_test table + await driver.init([ + { + name: 'tck_test', + fields: { + name: { type: 'string' }, + email: { type: 'string' }, + age: { type: 'number' }, + role: { type: 'string' }, + active: { type: 'boolean' }, + status: { type: 'string' }, + department: { type: 'string' }, + description: { type: 'string' }, + optionalField: { type: 'string' } } - } catch (error) { - // Ignore errors during cleanup } - } + ]); }, afterEach: async () => { - // Cleanup handled in beforeEach + // Close database connection + if (driver && driver['knex']) { + await driver['knex'].destroy(); + } } } } From 49919702271a4d3affc568867ba3d0bf5bc555af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 06:05:01 +0000 Subject: [PATCH 5/9] Initial plan From 7e48ad3945f64879887cb318cbb59412931bd791 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 06:29:57 +0000 Subject: [PATCH 6/9] Fix Redis driver to throw error on duplicate ID creation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/drivers/redis/src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/drivers/redis/src/index.ts b/packages/drivers/redis/src/index.ts index 954e5583..e1c1fc11 100644 --- a/packages/drivers/redis/src/index.ts +++ b/packages/drivers/redis/src/index.ts @@ -394,7 +394,13 @@ export class RedisDriver implements Driver { }; const key = this.generateRedisKey(objectName, id); - await this.client.set(key, JSON.stringify(doc)); + + // Use SET with NX option to prevent overwriting existing records + const result = await this.client.set(key, JSON.stringify(doc), { NX: true }); + + if (!result) { + throw new Error(`Record with ID ${id} already exists in ${objectName}`); + } return doc; } From f3522a09f3fd3606dc0bf931179f8aaf46d05b17 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 06:55:46 +0000 Subject: [PATCH 7/9] Initial plan From 9775309f12810e04e0df7367f037ffd873649e3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 07:04:04 +0000 Subject: [PATCH 8/9] Add timestamp support and fix MongoDB driver update method - Add automatic created_at/updated_at timestamps in create method - Change update method to use findOneAndUpdate and return full document - Preserve created_at in update (don't allow it to be modified) - Fix insertOne/deleteOne to not pass empty options object - Update tests to use findOneAndUpdate mock instead of updateOne Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/drivers/mongo/src/index.ts | 47 +++++++++++++++++------ packages/drivers/mongo/test/index.test.ts | 22 +++++------ 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/packages/drivers/mongo/src/index.ts b/packages/drivers/mongo/src/index.ts index dd59e180..973437a0 100644 --- a/packages/drivers/mongo/src/index.ts +++ b/packages/drivers/mongo/src/index.ts @@ -410,9 +410,20 @@ export class MongoDriver implements Driver { mongoDoc._id = new ObjectId().toHexString(); } - // Pass session for transactional operations - const mongoOptions = options?.session ? { session: options.session } : {}; - const result = await collection.insertOne(mongoDoc, mongoOptions); + // Add timestamps if not already present + const now = new Date().toISOString(); + if (!mongoDoc.created_at) { + mongoDoc.created_at = now; + } + if (!mongoDoc.updated_at) { + mongoDoc.updated_at = now; + } + + // Pass session for transactional operations only if it exists + const result = options?.session + ? await collection.insertOne(mongoDoc, { session: options.session }) + : await collection.insertOne(mongoDoc); + // Return API format document (convert _id to id) return this.mapFromMongo({ ...mongoDoc, _id: result.insertedId }); } @@ -422,23 +433,37 @@ export class MongoDriver implements Driver { // Map API document (id) to MongoDB document (_id) for update data // But we should not allow updating the _id field itself - const { id: _ignoredId, ...updateData } = data; // intentionally ignore id to prevent updating primary key + const { id: _ignoredId, created_at: _ignoredCreatedAt, ...updateData } = data; // intentionally ignore id and created_at to prevent updating them + + // Add updated_at timestamp + updateData.updated_at = new Date().toISOString(); // Handle atomic operators if present const isAtomic = Object.keys(updateData).some(k => k.startsWith('$')); const update = isAtomic ? updateData : { $set: updateData }; - // Pass session for transactional operations - const mongoOptions = options?.session ? { session: options.session } : {}; - const result = await collection.updateOne({ _id: this.normalizeId(id) }, update, mongoOptions); - return result.modifiedCount; // or return updated document? + // Use findOneAndUpdate to return the updated document + const mongoOptions: any = { returnDocument: 'after' }; + if (options?.session) { + mongoOptions.session = options.session; + } + + const result = await collection.findOneAndUpdate( + { _id: this.normalizeId(id) }, + update, + mongoOptions + ); + + // Return API format document (convert _id to id) + return this.mapFromMongo(result); } async delete(objectName: string, id: string | number, options?: any) { const collection = await this.getCollection(objectName); - // Pass session for transactional operations - const mongoOptions = options?.session ? { session: options.session } : {}; - const result = await collection.deleteOne({ _id: this.normalizeId(id) }, mongoOptions); + // Pass session for transactional operations only if it exists + const result = options?.session + ? await collection.deleteOne({ _id: this.normalizeId(id) }, { session: options.session }) + : await collection.deleteOne({ _id: this.normalizeId(id) }); return result.deletedCount; } diff --git a/packages/drivers/mongo/test/index.test.ts b/packages/drivers/mongo/test/index.test.ts index f238331c..f293c82a 100644 --- a/packages/drivers/mongo/test/index.test.ts +++ b/packages/drivers/mongo/test/index.test.ts @@ -22,6 +22,7 @@ const mockCollection = { insertedCount: 2 }), updateOne: jest.fn().mockResolvedValue({ modifiedCount: 1 }), + findOneAndUpdate: jest.fn().mockResolvedValue({ _id: '123', name: 'Updated' }), deleteOne: jest.fn().mockResolvedValue({ deletedCount: 1 }), countDocuments: jest.fn().mockResolvedValue(10) }; @@ -437,20 +438,17 @@ describe('MongoDriver', () => { data: { name: 'Updated User' } }; - mockCollection.updateOne.mockResolvedValue({ - modifiedCount: 1, - acknowledged: true - } as any); - mockCollection.findOne.mockResolvedValue({ + mockCollection.findOneAndUpdate.mockResolvedValue({ _id: '123', - name: 'Updated User' + name: 'Updated User', + updated_at: new Date().toISOString() }); const result = await driver.executeCommand(command); expect(result.success).toBe(true); expect(result.affected).toBe(1); - expect(mockCollection.updateOne).toHaveBeenCalled(); + expect(mockCollection.findOneAndUpdate).toHaveBeenCalled(); }); it('should execute delete command', async () => { @@ -504,11 +502,11 @@ describe('MongoDriver', () => { ] }; - mockCollection.updateOne.mockResolvedValue({ - modifiedCount: 1, - acknowledged: true - } as any); - mockCollection.findOne.mockResolvedValue({ _id: '1', name: 'Updated 1' }); + mockCollection.findOneAndUpdate.mockResolvedValue({ + _id: '1', + name: 'Updated 1', + updated_at: new Date().toISOString() + }); const result = await driver.executeCommand(command); From 0ca5306d16ee09212161170946213eb9882e26ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 07:07:58 +0000 Subject: [PATCH 9/9] Improve type safety in MongoDB driver update method - Import FindOneAndUpdateOptions from mongodb package - Replace 'any' type with proper FindOneAndUpdateOptions type - Maintains all functionality while improving type safety Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/drivers/mongo/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/drivers/mongo/src/index.ts b/packages/drivers/mongo/src/index.ts index 973437a0..3237d37f 100644 --- a/packages/drivers/mongo/src/index.ts +++ b/packages/drivers/mongo/src/index.ts @@ -11,7 +11,7 @@ type DriverInterface = Data.DriverInterface; */ import { Driver } from '@objectql/types'; -import { MongoClient, Db, Filter, ObjectId, FindOptions, ChangeStream, ChangeStreamDocument } from 'mongodb'; +import { MongoClient, Db, Filter, ObjectId, FindOptions, FindOneAndUpdateOptions, ChangeStream, ChangeStreamDocument } from 'mongodb'; /** * Change stream event handler callback @@ -443,7 +443,7 @@ export class MongoDriver implements Driver { const update = isAtomic ? updateData : { $set: updateData }; // Use findOneAndUpdate to return the updated document - const mongoOptions: any = { returnDocument: 'after' }; + const mongoOptions: FindOneAndUpdateOptions = { returnDocument: 'after' }; if (options?.session) { mongoOptions.session = options.session; }