-
Notifications
You must be signed in to change notification settings - Fork 2
Fix ObjectQL bridge class initialization and test infrastructure #382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,18 +51,27 @@ export class ObjectQL extends UpstreamObjectQL { | |
| /** Typed self-reference for compat methods */ | ||
| private get compat(): UpstreamCompat { return this as unknown as UpstreamCompat; } | ||
|
|
||
| private pendingDrivers: Array<{ name: string; driver: DriverInterface; isDefault: boolean }> = []; | ||
|
|
||
| // Explicitly declare inherited methods to ensure they're in the type definition | ||
| declare registerObject: (schema: ServiceObject, packageId?: string, namespace?: string) => string; | ||
|
|
||
| constructor(config: ObjectQLConfig = {}) { | ||
| // Upstream constructor only accepts hostContext | ||
| super(); | ||
|
|
||
| // Register drivers from legacy datasources config | ||
| // 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; | ||
| } | ||
| // Cast: local Driver interface is structurally compatible with upstream DriverInterface | ||
| this.registerDriver(driver as DriverInterface, name === 'default'); | ||
| this.pendingDrivers.push({ | ||
| name, | ||
| driver: driver as DriverInterface, | ||
| isDefault: name === 'default' | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -74,6 +83,12 @@ export class ObjectQL extends UpstreamObjectQL { | |
| * bridge all objects loaded via ObjectLoader into the upstream SchemaRegistry. | ||
| */ | ||
| async init(): Promise<void> { | ||
| // Register any pending drivers from the constructor config | ||
| for (const { driver, isDefault } of this.pendingDrivers) { | ||
| (this as any).registerDriver(driver, isDefault); | ||
| } | ||
| this.pendingDrivers = []; | ||
|
Comment on lines
+86
to
+90
|
||
|
|
||
| this.syncMetadataToRegistry(); | ||
| return super.init(); | ||
| } | ||
|
|
@@ -89,7 +104,7 @@ export class ObjectQL extends UpstreamObjectQL { | |
| if (obj && obj.name) { | ||
| // Only register if not already in SchemaRegistry | ||
| if (!SchemaRegistry.getObject(obj.name)) { | ||
| this.compat.registerObject(obj as ServiceObject, '__filesystem__'); | ||
| super.registerObject(obj as ServiceObject, '__filesystem__'); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -115,9 +130,9 @@ export class ObjectQL extends UpstreamObjectQL { | |
| * local MetadataRegistry for objects loaded via ObjectLoader but | ||
| * not yet synced (i.e., init() hasn't been called yet). | ||
| */ | ||
| getObject(name: string): ServiceObject | undefined { | ||
| // Check upstream SchemaRegistry | ||
| const upstream = SchemaRegistry.getObject(name); | ||
| override getObject(name: string): ServiceObject | undefined { | ||
| // Check upstream SchemaRegistry first (call parent) | ||
| const upstream = super.getObject(name); | ||
| if (upstream) return upstream; | ||
| // Fallback: check local MetadataRegistry (pre-init) | ||
| return this.metadata.get<ServiceObject>('object', name); | ||
|
|
@@ -129,15 +144,9 @@ export class ObjectQL extends UpstreamObjectQL { | |
| * Merges results from the upstream SchemaRegistry with the | ||
| * local MetadataRegistry (for pre-init objects). | ||
| */ | ||
| getConfigs(): Record<string, ServiceObject> { | ||
| const result: Record<string, ServiceObject> = {}; | ||
| // Get upstream objects from SchemaRegistry | ||
| const upstreamObjects = SchemaRegistry.getAllObjects(); | ||
| for (const obj of upstreamObjects) { | ||
| if (obj.name) { | ||
| result[obj.name] = obj; | ||
| } | ||
| } | ||
| override getConfigs(): Record<string, ServiceObject> { | ||
| // Get upstream objects first (call parent) | ||
| const result = super.getConfigs(); | ||
| // Merge local MetadataRegistry entries not yet synced upstream | ||
| const localObjects = this.metadata.list<any>('object'); | ||
| for (const obj of localObjects) { | ||
|
|
@@ -152,8 +161,8 @@ export class ObjectQL extends UpstreamObjectQL { | |
| * Remove all hooks, actions, and objects contributed by a package. | ||
| * Also cleans up the local MetadataRegistry. | ||
| */ | ||
| removePackage(packageId: string): void { | ||
| this.compat.removePackage(packageId); | ||
| override removePackage(packageId: string): void { | ||
| super.removePackage(packageId); | ||
| this.metadata.unregisterPackage(packageId); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,9 +11,103 @@ | |||||||
| */ | ||||||||
|
|
||||||||
| export class ObjectQL { | ||||||||
| private drivers = new Map<string, any>(); | ||||||||
| private defaultDriver: any = null; | ||||||||
| private hooks = new Map<string, any[]>(); | ||||||||
|
|
||||||||
| constructor(public config: any) {} | ||||||||
|
|
||||||||
| async connect() {} | ||||||||
| async disconnect() {} | ||||||||
| async init() {} | ||||||||
|
|
||||||||
| registerDriver(driver: any, isDefault: boolean = false) { | ||||||||
| if (!driver.name) { | ||||||||
| throw new Error('Driver must have a name'); | ||||||||
| } | ||||||||
| this.drivers.set(driver.name, driver); | ||||||||
| if (isDefault) { | ||||||||
| this.defaultDriver = driver.name; | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| registerObject(schema: any, packageId: string = '__runtime__', namespace?: string): string { | ||||||||
| // Auto-assign field names from keys | ||||||||
| if (schema.fields) { | ||||||||
| for (const [key, field] of Object.entries(schema.fields)) { | ||||||||
| if (field && typeof field === 'object' && !('name' in field)) { | ||||||||
| (field as any).name = key; | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
| return SchemaRegistry.registerObject(schema, packageId, namespace); | ||||||||
| } | ||||||||
|
|
||||||||
| getObject(name: string) { | ||||||||
| return SchemaRegistry.getObject(name); | ||||||||
| } | ||||||||
|
|
||||||||
| getConfigs(): Record<string, any> { | ||||||||
| return SchemaRegistry.getAllObjects().reduce((acc: any, obj: any) => { | ||||||||
| if (obj.name) { | ||||||||
| acc[obj.name] = obj; | ||||||||
| } | ||||||||
| return acc; | ||||||||
| }, {}); | ||||||||
| } | ||||||||
|
|
||||||||
| removePackage(packageId: string) { | ||||||||
| SchemaRegistry.unregisterObjectsByPackage(packageId); | ||||||||
| } | ||||||||
|
|
||||||||
| registerHook(event: string, handler: any, options?: any) { | ||||||||
| if (!this.hooks.has(event)) { | ||||||||
| this.hooks.set(event, []); | ||||||||
| } | ||||||||
| this.hooks.get(event)!.push({ handler, options }); | ||||||||
| } | ||||||||
|
|
||||||||
| createContext(options: any = {}) { | ||||||||
| return { | ||||||||
| isSystem: options.isSystem || false, | ||||||||
| object: (name: string) => ({ | ||||||||
| find: async (filter: any) => { | ||||||||
| const driver = this.drivers.get(this.defaultDriver || this.drivers.keys().next().value); | ||||||||
| if (driver && driver.find) { | ||||||||
| return driver.find(name, filter); | ||||||||
| } | ||||||||
| return []; | ||||||||
| }, | ||||||||
| findOne: async (filter: any) => { | ||||||||
| const driver = this.drivers.get(this.defaultDriver || this.drivers.keys().next().value); | ||||||||
| if (driver && driver.findOne) { | ||||||||
| return driver.findOne(name, filter); | ||||||||
| } | ||||||||
| return null; | ||||||||
| }, | ||||||||
| 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; | ||||||||
| }, | ||||||||
| 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; | ||||||||
| }, | ||||||||
| 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 mockStore = new Map<string, Map<string, any>>(); | ||||||||
|
|
@@ -42,4 +136,37 @@ export const SchemaRegistry = { | |||||||
| return items ? Array.from(items.values()) : []; | ||||||||
| }), | ||||||||
| metadata: mockStore, | ||||||||
|
|
||||||||
| // Additional methods needed for ObjectQL compatibility | ||||||||
| registerObject: jest.fn((schema: any, packageId?: string, namespace?: string) => { | ||||||||
| if (!mockStore.has('object')) { | ||||||||
| mockStore.set('object', new Map()); | ||||||||
| } | ||||||||
| const name = schema.name || 'unnamed'; | ||||||||
| mockStore.get('object')!.set(name, schema); | ||||||||
|
||||||||
| mockStore.get('object')!.set(name, schema); | |
| const storedSchema = packageId ? { ...schema, __packageId: packageId } : schema; | |
| mockStore.get('object')!.set(name, storedSchema); |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unregisterObjectsByPackage() will never delete anything because registerObject() never tags stored objects with the packageId (__packageId). Either tag on registration or implement removal based on a separate package index; otherwise ObjectQL.removePackage won’t behave like the real engine in tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthPlugin is commented out but still imported at the top of the file. With the repo’s ESLint config this will trigger a no-unused-vars warning; either remove the import while disabled or gate the plugin behind a flag so the import remains used when enabled.