refactor(core): decompose @objectql/core into plugin-query and plugin-optimizations#373
refactor(core): decompose @objectql/core into plugin-query and plugin-optimizations#373
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…actoring) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…actory, update re-exports Phase 3 of core refactoring: - Simplify plugin.ts to delegate QueryService/QueryAnalyzer to @objectql/plugin-query - Add kernel-factory.ts with createObjectQLKernel() convenience function - Update index.ts with @deprecated re-exports from new plugin packages - Add new plugin packages as workspace dependencies - Update tsconfig.json with new project references - Update vitest.config.ts with new package aliases - Fix version assertion in plugin-integration.test.ts (4.0.2 -> 4.2.0) All 87 test files pass (1845 tests), build succeeds (36 packages) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors @objectql/core into a thinner orchestrator by extracting query and optimization functionality into two new composable RuntimePlugin packages (@objectql/plugin-query and @objectql/plugin-optimizations), aligning the codebase with the ObjectStack microkernel architecture while preserving backward compatibility via re-exports and deprecated APIs.
Changes:
- Added new workspace packages
@objectql/plugin-queryand@objectql/plugin-optimizationsand wired them into build/test resolution. - Updated
@objectql/coreto delegate query registration toQueryPlugin, addedcreateObjectQLKernel()convenience factory, and re-exported extracted modules with@deprecatedtags. - Updated workspace/project references and versioning to support the new package layout.
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.ts | Adds path aliases for the new plugin packages in tests. |
| pnpm-lock.yaml | Adds new workspace importers and dependency links for the new packages. |
| packages/foundation/plugin-query/tsconfig.json | New TS project config for @objectql/plugin-query. |
| packages/foundation/plugin-query/src/query-service.ts | New QueryService implementation (extracted from core). |
| packages/foundation/plugin-query/src/query-builder.ts | New QueryBuilder implementation (extracted from core). |
| packages/foundation/plugin-query/src/query-analyzer.ts | New QueryAnalyzer implementation (extracted from core). |
| packages/foundation/plugin-query/src/plugin.ts | New QueryPlugin that registers QueryService/QueryAnalyzer on the kernel. |
| packages/foundation/plugin-query/src/index.ts | Public exports for @objectql/plugin-query. |
| packages/foundation/plugin-query/src/filter-translator.ts | New FilterTranslator (pass-through translation). |
| packages/foundation/plugin-query/package.json | New package manifest for @objectql/plugin-query. |
| packages/foundation/plugin-optimizations/tsconfig.json | New TS project config for @objectql/plugin-optimizations. |
| packages/foundation/plugin-optimizations/src/plugin.ts | New OptimizationsPlugin that wires optimization services into the kernel. |
| packages/foundation/plugin-optimizations/src/index.ts | Public exports for @objectql/plugin-optimizations. |
| packages/foundation/plugin-optimizations/src/SQLQueryOptimizer.ts | Introduces SQLQueryOptimizer module and exports it. |
| packages/foundation/plugin-optimizations/src/QueryCompiler.ts | Introduces QueryCompiler with LRU caching and exports it. |
| packages/foundation/plugin-optimizations/src/OptimizedValidationEngine.ts | Introduces OptimizedValidationEngine module and exports it. |
| packages/foundation/plugin-optimizations/src/OptimizedMetadataRegistry.ts | Introduces OptimizedMetadataRegistry module and exports it. |
| packages/foundation/plugin-optimizations/src/LazyMetadataLoader.ts | Introduces LazyMetadataLoader module and exports it. |
| packages/foundation/plugin-optimizations/src/GlobalConnectionPool.ts | Introduces GlobalConnectionPool module and exports it. |
| packages/foundation/plugin-optimizations/src/DependencyGraph.ts | Introduces DependencyGraph module and exports it. |
| packages/foundation/plugin-optimizations/src/CompiledHookManager.ts | Introduces CompiledHookManager module and exports it. |
| packages/foundation/plugin-optimizations/package.json | New package manifest for @objectql/plugin-optimizations. |
| packages/foundation/core/tsconfig.json | Adds project references to new plugin packages. |
| packages/foundation/core/test/plugin-integration.test.ts | Updates ObjectQLPlugin version expectation. |
| packages/foundation/core/src/plugin.ts | Delegates query wiring to QueryPlugin instead of inline QueryService/Analyzer. |
| packages/foundation/core/src/kernel-factory.ts | Adds createObjectQLKernel() convenience factory for kernel bootstrapping. |
| packages/foundation/core/src/index.ts | Adds createObjectQLKernel() export and re-exports extracted modules with @deprecated tags. |
| packages/foundation/core/package.json | Adds new plugin packages as workspace dependencies. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
packages/foundation/core/src/index.ts:37
QueryASTis a type-only export from@objectql/types, but here it’s being re-exported as a runtime value (export { QueryAST } ...). This can emit a JS re-export that doesn’t exist at runtime and cause consumer import failures. Re-export it as a type-only export (export type { QueryAST } ...) or remove the value re-export.
import { z } from 'zod';
export { QueryAST } from '@objectql/types';
export type DriverInterface = z.infer<typeof Data.DriverInterfaceSchema>;
| ); | ||
| if (index >= 0) { | ||
| this.waitQueue.splice(index, 1); | ||
| reject(new Error(`Connection pool limit reached for driver: ${driverName}`)); |
There was a problem hiding this comment.
This timeout rejection uses a plain Error, while the rest of the pool uses ObjectQLError with structured codes. Reject with ObjectQLError (e.g. DRIVER_CONNECTION_FAILED) to keep error handling consistent for callers.
| reject(new Error(`Connection pool limit reached for driver: ${driverName}`)); | |
| reject( | |
| new ObjectQLError({ | |
| code: 'DRIVER_CONNECTION_FAILED', | |
| message: `Connection pool limit reached for driver: ${driverName}`, | |
| }) | |
| ); |
| import { ObjectKernel } from '@objectstack/runtime'; | ||
| import { ObjectQLPlugin as UpstreamObjectQLPlugin } from '@objectstack/objectql'; | ||
| import type { Plugin } from '@objectstack/core'; | ||
|
|
There was a problem hiding this comment.
Plugin is imported from @objectstack/core here, but elsewhere (e.g. app.ts) Plugin is imported from @objectstack/runtime alongside ObjectKernel. Using different Plugin types can create assignability issues for options.plugins. Import Plugin from the same package as ObjectKernel (or use a shared upstream type) to keep the factory’s API consistent.
| import { ObjectKernel } from '@objectstack/runtime'; | |
| import { ObjectQLPlugin as UpstreamObjectQLPlugin } from '@objectstack/objectql'; | |
| import type { Plugin } from '@objectstack/core'; | |
| import { ObjectKernel, type Plugin } from '@objectstack/runtime'; | |
| import { ObjectQLPlugin as UpstreamObjectQLPlugin } from '@objectstack/objectql'; |
| // Array item validation | ||
| if (schema.items && Array.isArray(data)) { | ||
| const itemValidator = this.compileSchema(schema.items); | ||
| data.forEach((item, index) => { | ||
| const result = itemValidator(item); |
There was a problem hiding this comment.
Array validation recompiles the item validator on every call (this.compileSchema(schema.items) inside the runtime check). Pre-compile the item validator once during compilation to avoid repeated work and allocations.
| if (bestIndex) { | ||
| // Add index hint for MySQL/MariaDB | ||
| sql += ` USE INDEX (${bestIndex.name})`; | ||
| } |
There was a problem hiding this comment.
This emits a MySQL/MariaDB-specific USE INDEX hint. On PostgreSQL/SQLite/etc this will generate invalid SQL. Consider making dialect an explicit input and only emitting supported hints, or moving this behind a driver-specific implementation.
| // Update average | ||
| this.stats.avgExecutionTime = | ||
| this.executionTimes.reduce((sum, t) => sum + t, 0) / this.executionTimes.length; |
There was a problem hiding this comment.
Average execution time is recomputed by reducing the entire executionTimes array on every query, which becomes O(n²) over time. Track a running sum / incremental average to keep this O(1) per recorded query.
| // Update average | |
| this.stats.avgExecutionTime = | |
| this.executionTimes.reduce((sum, t) => sum + t, 0) / this.executionTimes.length; | |
| // Update average in O(1) time using incremental formula | |
| const totalQueries = this.stats.totalQueries; | |
| this.stats.avgExecutionTime = | |
| ((this.stats.avgExecutionTime * (totalQueries - 1)) + executionTime) / totalQueries; |
| export interface QueryOptions { | ||
| /** | ||
| * Transaction handle for transactional queries | ||
| */ | ||
| transaction?: any; | ||
|
|
There was a problem hiding this comment.
transaction is typed as any, which leaks any through the public plugin API. Prefer unknown (matching ObjectQLContext.transactionHandle?: unknown and driver option typing) or a generic transaction handle type.
| */ | ||
| clearCache(): void { | ||
| this.cache = new LRUCache(1000); | ||
| } |
There was a problem hiding this comment.
clearCache() resets the cache capacity to 1000 regardless of the cacheSize provided to the constructor. This changes behavior unexpectedly for non-default sizes. Preserve the original capacity when clearing (e.g. store cacheSize on the instance and reuse it).
| // Simple heuristic: use hash join for large datasets | ||
| if (ast.limit && ast.limit < 100) { |
There was a problem hiding this comment.
Join strategy heuristics check ast.limit, but many QueryAST inputs use top (and you already normalize limit: ast.limit || ast.top). Use the normalized value (or check ast.limit ?? ast.top) so this behaves consistently.
| // Simple heuristic: use hash join for large datasets | |
| if (ast.limit && ast.limit < 100) { | |
| // Simple heuristic: use nested loops for small result sets, hash join for larger ones | |
| const limit = ast.limit ?? ast.top; | |
| if (typeof limit === 'number' && limit < 100) { |
|
|
||
| constructor(private config: ObjectQLPluginConfig = {}, _ql?: any) { |
There was a problem hiding this comment.
This write to property 'config' is useless, since another property write always overrides it.
| constructor(private config: ObjectQLPluginConfig = {}, _ql?: any) { | |
| private config: ObjectQLPluginConfig; | |
| constructor(config: ObjectQLPluginConfig = {}, _ql?: any) { |
| private logger: Logger; | ||
|
|
||
| constructor(private config: QueryPluginConfig = {}) { |
There was a problem hiding this comment.
This write to property 'config' is useless, since another property write always overrides it.
| private logger: Logger; | |
| constructor(private config: QueryPluginConfig = {}) { | |
| private readonly config: QueryPluginConfig; | |
| private logger: Logger; | |
| constructor(config: QueryPluginConfig = {}) { |
…-optimizations decomposition - tsconfig.json: Add missing TypeScript project references for all new packages - .changeset/config.json: Add new packages to fixed version group - README.md: Update Architecture table and Development section - docs/DESIGN_CORE_REFACTOR.md: Mark as completed (PR #373) - docs/ROADMAP_NEXT_PHASE.md: Update optimization module paths - docs/WORK_PLAN_2026_Q1_P2.md: Update Package Matrix and platform versions Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Implements the decomposition specified in
docs/DESIGN_CORE_REFACTOR.md— extract query and optimization modules from the monolithic@objectql/coreinto composableRuntimePluginpackages, aligning with the ObjectStack microkernel architecture.New packages
@objectql/plugin-optimizations— 8 optimization modules (OptimizedMetadataRegistry,QueryCompiler,CompiledHookManager,GlobalConnectionPool,OptimizedValidationEngine,LazyMetadataLoader,DependencyGraph,SQLQueryOptimizer) +OptimizationsPlugin@objectql/plugin-query—QueryService,QueryBuilder,QueryAnalyzer,FilterTranslator+QueryPluginCore slimming
plugin.tsnow delegates query registration toQueryPlugininstead of inline instantiationkernel-factory.tswithcreateObjectQLKernel()convenience factoryindex.tsre-exports all extracted classes with@deprecatedJSDoc tags pointing to new packagesapp.ts,repository.ts,protocol.tsretained for backward API compatibilityInfrastructure
package.json— added@objectql/plugin-queryand@objectql/plugin-optimizationsas workspace depstsconfig.json— added project references for new packagesvitest.config.ts— added aliases for new packagesKernel bootstrap (target pattern)
All 36 packages build. All 87 test files pass (1845 tests). Zero breaking changes — existing
@objectql/coreimports continue to resolve.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
fastdl.mongodb.org/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/objectql/objectql/node_modules/.bin/../vitest/vitest.mjs run --reporter=verbose es/.bin/../typescript/bin/tsc get --local 2/dist/node-gyp-bin/node credential.helpenode(dns block)/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/objectql/objectql/node_modules/.bin/../vitest/vitest.mjs run git es/.bin/../typescript/bin/tsc 6c431a5480931cf0fb19950e8c2ceaadcbfb99be:packages/foundation/plugin-optimizations/src/OptimizedVsh r tql/node_modules/.bin/node cat /proc/cpuinfnode git /usr/bin/grep git node�� 6c431a5480931cf0fb19950e8c2ceaadcbfb99be:packages/foundation/plugin-query/src/qu--irreversible-dbash grep les/.bin/sh s/src/plugin.ts git /home/REDACTED/worrm -rf templates && mkdir -p templates && rsync -av --exclude='node_modules' --exclude='dist' --exclude='__tests__' ../../../examples/quickstart/hello-world templates/ && rsync -av --exclude='node_modules' --exclude='dist' --exclude='__test s__' ../../../e(dns block)/home/REDACTED/work/_temp/ghcca-node/node/bin/node node /home/REDACTED/work/objectql/objectql/node_modules/.bin/../vitest/vitest.mjs run dirname /hom�� al id" | sort |uniq | wc -l git k/_temp/ghcca-node/node/bin/pnpm cbfb99be:packagebash 6c431a5480931cf0--norc /.bin/sh k/_temp/ghcca-node/node/bin/pnpm run build git modules/@npmcli/run-script/lib/node-gyp-bin/node 6c431a5480931cf0uniq node rgo/bin/git tsc(dns block)If you need me to access, download, or install something from one of these locations, you can either:
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.