Skip to content

Commit 20cceea

Browse files
Copilothotlong
andcommitted
feat: Integrate optimizations directly into core codebase
- Replaced MetadataRegistry with optimized version using secondary index - Integrated CompiledHookManager for pre-compiled hook pipelines - Added QueryCompiler caching to ObjectRepository - No longer maintaining backward compatibility as requested This directly enables optimizations instead of providing them as drop-in replacements. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 089f01c commit 20cceea

3 files changed

Lines changed: 74 additions & 36 deletions

File tree

packages/foundation/core/src/app.ts

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { ObjectQL as RuntimeObjectQL, SchemaRegistry } from '@objectstack/object
2727
import { ObjectRepository } from './repository';
2828
import { ObjectQLPlugin } from './plugin';
2929
import { convertIntrospectedSchemaToObjects } from './util';
30+
import { CompiledHookManager } from './optimizations/CompiledHookManager';
3031

3132
/**
3233
* ObjectQL
@@ -48,9 +49,9 @@ export class ObjectQL implements IObjectQL {
4849
private ql: any;
4950
private kernelPlugins: any[] = [];
5051

51-
// Local Action and Hook Registry (shim for missing/inaccessible Kernel functionality)
52+
// Optimized managers
53+
private hookManager = new CompiledHookManager();
5254
private localActions = new Map<string, any>();
53-
private localHooks = new Map<string, any[]>();
5455

5556
// Store config for lazy loading in init()
5657
private config: ObjectQLConfig;
@@ -141,31 +142,13 @@ export class ObjectQL implements IObjectQL {
141142
const kernelHooks = (this.kernel as any).hooks || {};
142143
Object.assign(kernelHooks, {
143144
register: (event: string, objectName: string, handler: any, packageName?: string) => {
144-
const key = `${event}:${objectName}`;
145-
if (!this.localHooks.has(key)) {
146-
this.localHooks.set(key, []);
147-
}
148-
const handlers = this.localHooks.get(key)!;
149-
// Store wrapper to track package
150-
(handler as any)._package = packageName;
151-
handlers.push(handler);
145+
this.hookManager.registerHook(event, objectName, handler, packageName);
152146
},
153147
removePackage: (packageName: string) => {
154-
for (const handlers of this.localHooks.values()) {
155-
// Remove handlers belonging to package
156-
for (let i = handlers.length - 1; i >= 0; i--) {
157-
if ((handlers[i] as any)._package === packageName) {
158-
handlers.splice(i, 1);
159-
}
160-
}
161-
}
148+
this.hookManager.removePackage(packageName);
162149
},
163150
trigger: async (event: string, objectName: string, ctx: any) => {
164-
const key = `${event}:${objectName}`;
165-
const handlers = this.localHooks.get(key) || [];
166-
for (const handler of handlers) {
167-
await handler(ctx);
168-
}
151+
await this.hookManager.runHooks(event, objectName, ctx);
169152
}
170153
});
171154
if (!(this.kernel as any).hooks) {

packages/foundation/core/src/repository.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type SortNode = Data.SortNode;
1414
import { Validator } from '@objectql/plugin-validator';
1515
import { FormulaEngine } from '@objectql/plugin-formula';
1616
import { QueryBuilder } from './query';
17+
import { QueryCompiler } from './optimizations/QueryCompiler';
1718

1819
/**
1920
* Extended ObjectStack Kernel with optional ObjectQL plugin capabilities.
@@ -31,6 +32,8 @@ interface ExtendedKernel extends ObjectKernel {
3132

3233
export class ObjectRepository {
3334
private queryBuilder: QueryBuilder;
35+
// Shared query compiler for caching compiled queries
36+
private static queryCompiler = new QueryCompiler(1000);
3437

3538
constructor(
3639
private objectName: string,
@@ -85,9 +88,13 @@ export class ObjectRepository {
8588

8689
/**
8790
* Translates ObjectQL UnifiedQuery to ObjectStack QueryAST format
91+
* Uses query compiler for caching and optimization
8892
*/
8993
private buildQueryAST(query: UnifiedQuery): QueryAST {
90-
return this.queryBuilder.build(this.objectName, query);
94+
const ast = this.queryBuilder.build(this.objectName, query);
95+
// Use query compiler to cache and optimize the AST
96+
const compiled = ObjectRepository.queryCompiler.compile(this.objectName, ast);
97+
return compiled.ast;
9198
}
9299

93100
getSchema(): ObjectConfig {

packages/foundation/types/src/registry.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,25 @@
77
*/
88

99
/**
10-
* Re-export MetadataRegistry and MetadataItem from @objectstack/runtime
10+
* Metadata reference for secondary index
11+
*/
12+
interface MetadataRef {
13+
type: string;
14+
name: string;
15+
}
16+
17+
/**
18+
* Optimized Metadata Registry with O(k) package uninstall complexity
1119
*
12-
* As of Week 3 refactoring, metadata management has been moved to the
13-
* @objectstack/runtime package to enable sharing across the ecosystem.
20+
* Uses secondary indexes to achieve O(k) complexity for unregisterPackage
21+
* operation (where k is the number of items in the package) instead of
22+
* O(n*m) (where n is types and m is items per type).
1423
*/
1524
export class MetadataRegistry {
1625
private items: any = {};
26+
27+
// Secondary index: package name -> list of metadata references
28+
private packageIndex = new Map<string, Set<MetadataRef>>();
1729

1830
constructor() {}
1931

@@ -35,6 +47,15 @@ export class MetadataRegistry {
3547

3648
if (name) {
3749
this.items[type][name] = item;
50+
51+
// Update package index
52+
const packageName = item.package || (item as any)._package || (item as any).packageName;
53+
if (packageName) {
54+
if (!this.packageIndex.has(packageName)) {
55+
this.packageIndex.set(packageName, new Set());
56+
}
57+
this.packageIndex.get(packageName)!.add({ type, name });
58+
}
3859
}
3960
}
4061

@@ -65,22 +86,49 @@ export class MetadataRegistry {
6586
}
6687

6788
unregister(type: string, name: string) {
68-
if (this.items[type]?.[name]) {
89+
const item = this.items[type]?.[name];
90+
if (item) {
91+
// Update package index
92+
const packageName = item.package || (item as any)._package || (item as any).packageName;
93+
if (packageName) {
94+
const refs = this.packageIndex.get(packageName);
95+
if (refs) {
96+
// Remove this specific reference
97+
for (const ref of refs) {
98+
if (ref.type === type && ref.name === name) {
99+
refs.delete(ref);
100+
break;
101+
}
102+
}
103+
// Clean up empty package entries
104+
if (refs.size === 0) {
105+
this.packageIndex.delete(packageName);
106+
}
107+
}
108+
}
69109
delete this.items[type][name];
70110
}
71111
}
72112

113+
/**
114+
* Optimized package unregistration with O(k) complexity
115+
* where k is the number of items in the package.
116+
*
117+
* Previous complexity: O(n*m) - iterate all types and all items
118+
* New complexity: O(k) - direct lookup via secondary index
119+
*/
73120
unregisterPackage(packageName: string) {
74-
// Optimized implementation using secondary index
75-
// This is now O(k) instead of O(n*m) where k is items in package
76-
for (const type of Object.keys(this.items)) {
77-
for (const key of Object.keys(this.items[type])) {
78-
const item = this.items[type][key];
79-
// Check widely for packaging metadata (package or _package)
80-
if (item.package === packageName || (item as any)._package === packageName || (item as any).packageName === packageName) {
81-
delete this.items[type][key];
121+
// Direct lookup via secondary index ✅
122+
const refs = this.packageIndex.get(packageName);
123+
if (refs) {
124+
// Delete each item referenced by this package
125+
for (const ref of refs) {
126+
if (this.items[ref.type]?.[ref.name]) {
127+
delete this.items[ref.type][ref.name];
82128
}
83129
}
130+
// Remove package from index
131+
this.packageIndex.delete(packageName);
84132
}
85133
}
86134
}

0 commit comments

Comments
 (0)