From 3fd76eb9403fedc7a6d14af1b9f6078fd7752fa1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 13:55:51 +0000 Subject: [PATCH 1/3] Initial plan From b89bbf72976f4389bf6e50c775f501b4b0d0a9a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:10:33 +0000 Subject: [PATCH 2/3] fix: add missing datasource(), on(), and CRUD hook methods to @objectstack/objectql mock and fix bridge driver naming Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/foundation/core/src/app.ts | 5 +- .../test/__mocks__/@objectstack/objectql.ts | 118 ++++++++++++++++-- 2 files changed, 108 insertions(+), 15 deletions(-) diff --git a/packages/foundation/core/src/app.ts b/packages/foundation/core/src/app.ts index 93cd772a..3be4a0f8 100644 --- a/packages/foundation/core/src/app.ts +++ b/packages/foundation/core/src/app.ts @@ -63,9 +63,8 @@ export class ObjectQL extends UpstreamObjectQL { // Store drivers for registration during init() if (config.datasources) { for (const [name, driver] of Object.entries(config.datasources)) { - if (!(driver as any).name) { - (driver as any).name = name; - } + // Always set driver.name to the config key so datasource(name) lookups work + (driver as any).name = name; // Cast: local Driver interface is structurally compatible with upstream DriverInterface this.pendingDrivers.push({ name, diff --git a/packages/foundation/core/test/__mocks__/@objectstack/objectql.ts b/packages/foundation/core/test/__mocks__/@objectstack/objectql.ts index 4f38f940..e4855394 100644 --- a/packages/foundation/core/test/__mocks__/@objectstack/objectql.ts +++ b/packages/foundation/core/test/__mocks__/@objectstack/objectql.ts @@ -20,7 +20,18 @@ export class ObjectQL { async connect() {} async disconnect() {} - async init() {} + async init() { + // Initialize drivers (connect + sync schema) + for (const [_name, driver] of this.drivers) { + if (driver.connect) { + await driver.connect(); + } + if (driver.init) { + const objects = SchemaRegistry.getAllObjects(); + await driver.init(objects); + } + } + } registerDriver(driver: any, isDefault: boolean = false) { if (!driver.name) { @@ -31,6 +42,18 @@ export class ObjectQL { this.defaultDriver = driver.name; } } + + datasource(name: string): any { + const driver = this.drivers.get(name); + if (!driver) { + throw new Error(`[ObjectQL] Datasource '${name}' not found`); + } + return driver; + } + + getDriverByName(name: string): any { + return this.drivers.get(name); + } registerObject(schema: any, packageId: string = '__runtime__', namespace?: string): string { // Auto-assign field names from keys @@ -68,6 +91,10 @@ export class ObjectQL { this.hooks.get(event)!.push({ handler, options }); } + on(event: string, objectName: string, handler: any, packageId?: string) { + this.registerHook(event, handler, { object: objectName, packageId }); + } + registerMiddleware(fn: any, options?: { object?: string }) { this.middlewares.push({ fn, object: options?.object }); } @@ -128,25 +155,92 @@ export class ObjectQL { }); return opCtx.result; }, + create: async (data: any) => { + const driver = this.drivers.get(this.defaultDriver || this.drivers.keys().next().value); + const opCtx = { object: name, operation: 'insert', data, context: options, result: undefined as any }; + await this.executeWithMiddleware(opCtx, async () => { + const hookContext: any = { + object: name, event: 'beforeCreate', + input: { data: opCtx.data }, session: options, + data: opCtx.data, user: options.userId ? { id: options.userId } : options.user + }; + await this.triggerHooks('beforeCreate', hookContext); + const finalData = hookContext.data || hookContext.input.data; + const result = driver?.create ? await driver.create(name, finalData, options) : finalData; + hookContext.event = 'afterCreate'; + hookContext.result = result; + await this.triggerHooks('afterCreate', hookContext); + return hookContext.result; + }); + return opCtx.result; + }, insert: async (data: any) => { const driver = this.drivers.get(this.defaultDriver || this.drivers.keys().next().value); - if (driver && driver.insert) { - return driver.insert(name, data); - } - return data; + const opCtx = { object: name, operation: 'insert', data, context: options, result: undefined as any }; + await this.executeWithMiddleware(opCtx, async () => { + const hookContext: any = { + object: name, event: 'beforeCreate', + input: { data: opCtx.data }, session: options, + data: opCtx.data, user: options.userId ? { id: options.userId } : options.user + }; + await this.triggerHooks('beforeCreate', hookContext); + const finalData = hookContext.data || hookContext.input.data; + const result = driver?.create ? await driver.create(name, finalData, options) + : driver?.insert ? await driver.insert(name, finalData) + : finalData; + hookContext.event = 'afterCreate'; + hookContext.result = result; + await this.triggerHooks('afterCreate', hookContext); + return hookContext.result; + }); + return opCtx.result; }, update: async (id: string, data: any) => { const driver = this.drivers.get(this.defaultDriver || this.drivers.keys().next().value); - if (driver && driver.update) { - return driver.update(name, id, data); - } - return data; + const opCtx = { object: name, operation: 'update', data, context: options, result: undefined as any }; + await this.executeWithMiddleware(opCtx, async () => { + // Fetch previous data for hooks that need it + let previousData: any; + if (driver?.findOne) { + try { previousData = await driver.findOne(name, { _id: id }); } catch (_e) { /* ignore */ } + } + const hookContext: any = { + object: name, event: 'beforeUpdate', + input: { id, data: opCtx.data }, session: options, + data: opCtx.data, previousData, user: options.userId ? { id: options.userId } : options.user + }; + await this.triggerHooks('beforeUpdate', hookContext); + const finalData = hookContext.data || hookContext.input.data; + const result = driver?.update ? await driver.update(name, id, finalData, options) : finalData; + hookContext.event = 'afterUpdate'; + hookContext.result = result; + await this.triggerHooks('afterUpdate', hookContext); + return hookContext.result; + }); + return opCtx.result; }, delete: async (id: string) => { const driver = this.drivers.get(this.defaultDriver || this.drivers.keys().next().value); - if (driver && driver.delete) { - return driver.delete(name, id); - } + const opCtx = { object: name, operation: 'delete', context: options, result: undefined as any }; + await this.executeWithMiddleware(opCtx, async () => { + // Fetch current data for hooks that need it + let previousData: any; + if (driver?.findOne) { + try { previousData = await driver.findOne(name, { _id: id }); } catch (_e) { /* ignore */ } + } + const hookContext: any = { + object: name, event: 'beforeDelete', + input: { id }, session: options, + data: previousData, previousData, user: options.userId ? { id: options.userId } : options.user + }; + await this.triggerHooks('beforeDelete', hookContext); + const result = driver?.delete ? await driver.delete(name, id) : true; + hookContext.event = 'afterDelete'; + hookContext.result = result; + await this.triggerHooks('afterDelete', hookContext); + return hookContext.result; + }); + return opCtx.result; } }) }; From 5f27d1579aaec86eb5fafd169ada3562d5071da7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:13:38 +0000 Subject: [PATCH 3/3] fix: add count() method to mock context and fix enterprise-erp test Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../core/test/__mocks__/@objectstack/objectql.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/foundation/core/test/__mocks__/@objectstack/objectql.ts b/packages/foundation/core/test/__mocks__/@objectstack/objectql.ts index e4855394..9c21ffb3 100644 --- a/packages/foundation/core/test/__mocks__/@objectstack/objectql.ts +++ b/packages/foundation/core/test/__mocks__/@objectstack/objectql.ts @@ -241,6 +241,15 @@ export class ObjectQL { return hookContext.result; }); return opCtx.result; + }, + count: async (filter?: any) => { + const driver = this.drivers.get(this.defaultDriver || this.drivers.keys().next().value); + if (driver?.count) { + return driver.count(name, filter); + } + // Fallback: use find and count + const results = driver?.find ? await driver.find(name, filter) : []; + return Array.isArray(results) ? results.length : 0; } }) };