@objectql/core 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +18 -0
- package/README.md +4 -4
- package/dist/app.d.ts +9 -6
- package/dist/app.js +151 -29
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/optimizations/CompiledHookManager.d.ts +55 -0
- package/dist/optimizations/CompiledHookManager.js +164 -0
- package/dist/optimizations/CompiledHookManager.js.map +1 -0
- package/dist/optimizations/DependencyGraph.d.ts +82 -0
- package/dist/optimizations/DependencyGraph.js +211 -0
- package/dist/optimizations/DependencyGraph.js.map +1 -0
- package/dist/optimizations/GlobalConnectionPool.d.ts +89 -0
- package/dist/optimizations/GlobalConnectionPool.js +193 -0
- package/dist/optimizations/GlobalConnectionPool.js.map +1 -0
- package/dist/optimizations/LazyMetadataLoader.d.ts +75 -0
- package/dist/optimizations/LazyMetadataLoader.js +149 -0
- package/dist/optimizations/LazyMetadataLoader.js.map +1 -0
- package/dist/optimizations/OptimizedMetadataRegistry.d.ts +26 -0
- package/dist/optimizations/OptimizedMetadataRegistry.js +117 -0
- package/dist/optimizations/OptimizedMetadataRegistry.js.map +1 -0
- package/dist/optimizations/OptimizedValidationEngine.d.ts +73 -0
- package/dist/optimizations/OptimizedValidationEngine.js +141 -0
- package/dist/optimizations/OptimizedValidationEngine.js.map +1 -0
- package/dist/optimizations/QueryCompiler.d.ts +51 -0
- package/dist/optimizations/QueryCompiler.js +216 -0
- package/dist/optimizations/QueryCompiler.js.map +1 -0
- package/dist/optimizations/SQLQueryOptimizer.d.ts +96 -0
- package/dist/optimizations/SQLQueryOptimizer.js +265 -0
- package/dist/optimizations/SQLQueryOptimizer.js.map +1 -0
- package/dist/optimizations/index.d.ts +32 -0
- package/dist/optimizations/index.js +44 -0
- package/dist/optimizations/index.js.map +1 -0
- package/dist/plugin.d.ts +8 -7
- package/dist/plugin.js +57 -22
- package/dist/plugin.js.map +1 -1
- package/dist/query/query-analyzer.js.map +1 -1
- package/dist/query/query-builder.d.ts +6 -1
- package/dist/query/query-builder.js +21 -5
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/query-service.js.map +1 -1
- package/dist/repository.d.ts +2 -0
- package/dist/repository.js +15 -9
- package/dist/repository.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +8 -5
- package/src/app.ts +173 -47
- package/src/index.ts +8 -9
- package/src/optimizations/CompiledHookManager.ts +185 -0
- package/src/optimizations/DependencyGraph.ts +255 -0
- package/src/optimizations/GlobalConnectionPool.ts +251 -0
- package/src/optimizations/LazyMetadataLoader.ts +180 -0
- package/src/optimizations/OptimizedMetadataRegistry.ts +132 -0
- package/src/optimizations/OptimizedValidationEngine.ts +172 -0
- package/src/optimizations/QueryCompiler.ts +242 -0
- package/src/optimizations/SQLQueryOptimizer.ts +329 -0
- package/src/optimizations/index.ts +34 -0
- package/src/plugin.ts +71 -28
- package/src/query/query-analyzer.ts +1 -1
- package/src/query/query-builder.ts +21 -7
- package/src/query/query-service.ts +1 -1
- package/src/repository.ts +25 -13
- package/test/__mocks__/@objectstack/runtime.ts +8 -8
- package/test/app.test.ts +9 -7
- package/test/optimizations.test.ts +440 -0
- package/test/plugin-integration.test.ts +30 -19
- package/tsconfig.json +4 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/ai-agent.d.ts +0 -176
- package/dist/ai-agent.js +0 -722
- package/dist/ai-agent.js.map +0 -1
- package/dist/formula-engine.d.ts +0 -102
- package/dist/formula-engine.js +0 -433
- package/dist/formula-engine.js.map +0 -1
- package/dist/formula-plugin.d.ts +0 -52
- package/dist/formula-plugin.js +0 -107
- package/dist/formula-plugin.js.map +0 -1
- package/dist/validator-plugin.d.ts +0 -56
- package/dist/validator-plugin.js +0 -106
- package/dist/validator-plugin.js.map +0 -1
- package/dist/validator.d.ts +0 -80
- package/dist/validator.js +0 -625
- package/dist/validator.js.map +0 -1
- package/src/ai-agent.ts +0 -868
- package/src/formula-engine.ts +0 -572
- package/src/formula-plugin.ts +0 -141
- package/src/validator-plugin.ts +0 -140
- package/src/validator.ts +0 -743
- package/test/formula-engine.test.ts +0 -725
- package/test/formula-integration.test.ts +0 -286
- package/test/formula-plugin.test.ts +0 -197
- package/test/formula-spec-compliance.test.ts +0 -258
- package/test/validation-spec-compliance.test.ts +0 -440
- package/test/validator-plugin.test.ts +0 -126
- package/test/validator.test.ts +0 -440
package/src/app.ts
CHANGED
|
@@ -22,29 +22,35 @@ import {
|
|
|
22
22
|
ActionContext,
|
|
23
23
|
LoaderPlugin
|
|
24
24
|
} from '@objectql/types';
|
|
25
|
-
import {
|
|
25
|
+
import { ObjectKernel, type Plugin } from '@objectstack/core';
|
|
26
|
+
import { ObjectQL as RuntimeObjectQL, SchemaRegistry } from '@objectstack/objectql';
|
|
26
27
|
import { ObjectRepository } from './repository';
|
|
27
|
-
import { ObjectQLPlugin } from './plugin';
|
|
28
28
|
import { convertIntrospectedSchemaToObjects } from './util';
|
|
29
|
+
import { CompiledHookManager } from './optimizations/CompiledHookManager';
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* ObjectQL
|
|
32
33
|
*
|
|
33
|
-
* ObjectQL implementation that wraps
|
|
34
|
+
* ObjectQL implementation that wraps ObjectKernel
|
|
34
35
|
* to provide the plugin architecture.
|
|
35
36
|
*/
|
|
36
37
|
export class ObjectQL implements IObjectQL {
|
|
37
38
|
// Delegate to kernel for metadata, hooks, and actions
|
|
38
39
|
public get metadata(): MetadataRegistry {
|
|
39
|
-
return this.kernel.metadata;
|
|
40
|
+
return (this.kernel as any).metadata;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
private datasources: Record<string, Driver> = {};
|
|
43
44
|
private remotes: string[] = [];
|
|
44
45
|
|
|
45
46
|
// ObjectStack Kernel Integration
|
|
46
|
-
private kernel!:
|
|
47
|
-
private
|
|
47
|
+
private kernel!: ObjectKernel & Record<string, any>;
|
|
48
|
+
private ql: any;
|
|
49
|
+
private kernelPlugins: any[] = [];
|
|
50
|
+
|
|
51
|
+
// Optimized managers
|
|
52
|
+
private hookManager = new CompiledHookManager();
|
|
53
|
+
private localActions = new Map<string, any>();
|
|
48
54
|
|
|
49
55
|
// Store config for lazy loading in init()
|
|
50
56
|
private config: ObjectQLConfig;
|
|
@@ -57,10 +63,12 @@ export class ObjectQL implements IObjectQL {
|
|
|
57
63
|
throw new Error("Connection strings are not supported in core directly. Use @objectql/platform-node's createDriverFromConnection or pass a driver instance to 'datasources'.");
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
//
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
// Use the imported RuntimeObjectQL, assuming it works as intended
|
|
67
|
+
this.ql = new RuntimeObjectQL();
|
|
68
|
+
|
|
69
|
+
// Note: ObjectQLPlugin removed as it now uses RuntimePlugin interface
|
|
70
|
+
// which is incompatible with the legacy Plugin interface from @objectstack/core
|
|
71
|
+
// For new code, use the microkernel pattern with RuntimePlugin directly
|
|
64
72
|
|
|
65
73
|
// Add runtime plugins from config
|
|
66
74
|
if (config.plugins) {
|
|
@@ -68,13 +76,103 @@ export class ObjectQL implements IObjectQL {
|
|
|
68
76
|
if (typeof plugin === 'string') {
|
|
69
77
|
throw new Error("String plugins are not supported in core. Use @objectql/platform-node or pass plugin instance.");
|
|
70
78
|
} else {
|
|
71
|
-
this.use(plugin);
|
|
79
|
+
this.use(plugin as any);
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
// Create the kernel
|
|
77
|
-
this.kernel = new
|
|
84
|
+
// Create the kernel with registered plugins
|
|
85
|
+
this.kernel = new (ObjectKernel as any)();
|
|
86
|
+
for (const plugin of this.kernelPlugins) {
|
|
87
|
+
if ((this.kernel as any).use) {
|
|
88
|
+
(this.kernel as any).use(plugin);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Helper to unwrap content property (matching MetadataRegistry behavior)
|
|
93
|
+
const unwrapContent = (item: any) => {
|
|
94
|
+
if (item && item.content) {
|
|
95
|
+
return item.content;
|
|
96
|
+
}
|
|
97
|
+
return item;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Stub legacy accessors
|
|
101
|
+
(this.kernel as any).metadata = {
|
|
102
|
+
register: (type: string, item: any) => SchemaRegistry.registerItem(type, item, item.id ? 'id' : 'name'),
|
|
103
|
+
get: (type: string, name: string) => {
|
|
104
|
+
const item = SchemaRegistry.getItem(type, name) as any;
|
|
105
|
+
return unwrapContent(item);
|
|
106
|
+
},
|
|
107
|
+
getEntry: (type: string, name: string) => SchemaRegistry.getItem(type, name),
|
|
108
|
+
list: (type: string) => {
|
|
109
|
+
const items = SchemaRegistry.listItems(type);
|
|
110
|
+
return items.map(unwrapContent);
|
|
111
|
+
},
|
|
112
|
+
unregister: (type: string, name: string) => {
|
|
113
|
+
// Access private static storage using any cast
|
|
114
|
+
const metadata = (SchemaRegistry as any).metadata;
|
|
115
|
+
if (metadata instanceof Map) {
|
|
116
|
+
const collection = metadata.get(type);
|
|
117
|
+
if (collection instanceof Map) {
|
|
118
|
+
collection.delete(name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
unregisterPackage: (packageName: string) => {
|
|
123
|
+
const metadata = (SchemaRegistry as any).metadata;
|
|
124
|
+
if (metadata instanceof Map) {
|
|
125
|
+
for (const [type, collection] of metadata.entries()) {
|
|
126
|
+
if (collection instanceof Map) {
|
|
127
|
+
for (const [key, item] of collection.entries()) {
|
|
128
|
+
// console.log(`[App] Check ${type} ${key} pkg=${(item as any).package}`);
|
|
129
|
+
if ((item as any).package === packageName) {
|
|
130
|
+
collection.delete(key);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
console.warn('Metadata is not a Map');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const kernelHooks = (this.kernel as any).hooks || {};
|
|
141
|
+
Object.assign(kernelHooks, {
|
|
142
|
+
register: (event: string, objectName: string, handler: any, packageName?: string) => {
|
|
143
|
+
this.hookManager.registerHook(event, objectName, handler, packageName);
|
|
144
|
+
},
|
|
145
|
+
removePackage: (packageName: string) => {
|
|
146
|
+
this.hookManager.removePackage(packageName);
|
|
147
|
+
},
|
|
148
|
+
trigger: async (event: string, objectName: string, ctx: any) => {
|
|
149
|
+
await this.hookManager.runHooks(event, objectName, ctx);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
if (!(this.kernel as any).hooks) {
|
|
153
|
+
(this.kernel as any).hooks = kernelHooks;
|
|
154
|
+
}
|
|
155
|
+
(this.kernel as any).actions = {
|
|
156
|
+
register: (objectName: string, actionName: string, handler: any, packageName?: string) => {
|
|
157
|
+
const key = `${objectName}:${actionName}`;
|
|
158
|
+
(handler as any)._package = packageName;
|
|
159
|
+
this.localActions.set(key, handler);
|
|
160
|
+
},
|
|
161
|
+
removePackage: (packageName: string) => {
|
|
162
|
+
for (const [key, handler] of this.localActions.entries()) {
|
|
163
|
+
if ((handler as any)._package === packageName) {
|
|
164
|
+
this.localActions.delete(key);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
execute: async (objectName: string, actionName: string, ctx: any) => {
|
|
169
|
+
const handler = this.localActions.get(`${objectName}:${actionName}`);
|
|
170
|
+
if (handler) {
|
|
171
|
+
return handler(ctx);
|
|
172
|
+
}
|
|
173
|
+
throw new Error(`Action '${actionName}' on object '${objectName}' not found`);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
78
176
|
|
|
79
177
|
// Register initial metadata if provided
|
|
80
178
|
if (config.registry) {
|
|
@@ -87,7 +185,7 @@ export class ObjectQL implements IObjectQL {
|
|
|
87
185
|
? (item as { name?: string; id?: string }).name || (item as { name?: string; id?: string }).id || 'unknown'
|
|
88
186
|
: 'unknown';
|
|
89
187
|
|
|
90
|
-
this.kernel.metadata.register(type, {
|
|
188
|
+
(this.kernel as any).metadata.register(type, {
|
|
91
189
|
type,
|
|
92
190
|
id: itemId,
|
|
93
191
|
content: item
|
|
@@ -97,43 +195,46 @@ export class ObjectQL implements IObjectQL {
|
|
|
97
195
|
}
|
|
98
196
|
}
|
|
99
197
|
|
|
100
|
-
use(plugin:
|
|
198
|
+
use(plugin: Plugin) {
|
|
101
199
|
this.kernelPlugins.push(plugin);
|
|
200
|
+
if (this.kernel && (this.kernel as any).use) {
|
|
201
|
+
(this.kernel as any).use(plugin);
|
|
202
|
+
}
|
|
102
203
|
}
|
|
103
204
|
|
|
104
205
|
removePackage(name: string) {
|
|
105
206
|
// Delegate to kernel managers
|
|
106
|
-
this.kernel.metadata.unregisterPackage(name);
|
|
107
|
-
this.kernel.hooks.removePackage(name);
|
|
108
|
-
this.kernel.actions.removePackage(name);
|
|
207
|
+
(this.kernel as any).metadata.unregisterPackage(name);
|
|
208
|
+
(this.kernel as any).hooks.removePackage(name);
|
|
209
|
+
(this.kernel as any).actions.removePackage(name);
|
|
109
210
|
}
|
|
110
211
|
|
|
111
212
|
on(event: HookName, objectName: string, handler: HookHandler, packageName?: string) {
|
|
112
213
|
// Delegate to kernel hook manager
|
|
113
214
|
// We wrap the handler to bridge ObjectQL's rich context types with runtime's base types
|
|
114
215
|
// The runtime HookContext supports all fields via index signature, so this is safe
|
|
115
|
-
const wrappedHandler = handler as unknown as
|
|
116
|
-
this.kernel.hooks.register(event, objectName, wrappedHandler, packageName);
|
|
216
|
+
const wrappedHandler = handler as unknown as any;
|
|
217
|
+
(this.kernel as any).hooks.register(event, objectName, wrappedHandler, packageName);
|
|
117
218
|
}
|
|
118
219
|
|
|
119
220
|
async triggerHook(event: HookName, objectName: string, ctx: HookContext) {
|
|
120
221
|
// Delegate to kernel hook manager
|
|
121
222
|
// Runtime HookContext supports ObjectQL-specific fields via index signature
|
|
122
|
-
await this.kernel.hooks.trigger(event, objectName, ctx);
|
|
223
|
+
await (this.kernel as any).hooks.trigger(event, objectName, ctx);
|
|
123
224
|
}
|
|
124
225
|
|
|
125
226
|
registerAction(objectName: string, actionName: string, handler: ActionHandler, packageName?: string) {
|
|
126
227
|
// Delegate to kernel action manager
|
|
127
228
|
// We wrap the handler to bridge ObjectQL's rich context types with runtime's base types
|
|
128
229
|
// The runtime ActionContext supports all fields via index signature, so this is safe
|
|
129
|
-
const wrappedHandler = handler as unknown as
|
|
130
|
-
this.kernel.actions.register(objectName, actionName, wrappedHandler, packageName);
|
|
230
|
+
const wrappedHandler = handler as unknown as any;
|
|
231
|
+
(this.kernel as any).actions.register(objectName, actionName, wrappedHandler, packageName);
|
|
131
232
|
}
|
|
132
233
|
|
|
133
234
|
async executeAction(objectName: string, actionName: string, ctx: ActionContext) {
|
|
134
235
|
// Delegate to kernel action manager
|
|
135
236
|
// Runtime ActionContext supports ObjectQL-specific fields via index signature
|
|
136
|
-
return await this.kernel.actions.execute(objectName, actionName, ctx);
|
|
237
|
+
return await (this.kernel as any).actions.execute(objectName, actionName, ctx);
|
|
137
238
|
}
|
|
138
239
|
|
|
139
240
|
createContext(options: ObjectQLContextOptions): ObjectQLContext {
|
|
@@ -181,15 +282,15 @@ export class ObjectQL implements IObjectQL {
|
|
|
181
282
|
}
|
|
182
283
|
|
|
183
284
|
/**
|
|
184
|
-
* Get the underlying
|
|
285
|
+
* Get the underlying ObjectKernel instance
|
|
185
286
|
*
|
|
186
287
|
* This provides access to the kernel for advanced usage scenarios
|
|
187
288
|
* where you need direct access to the plugin architecture.
|
|
188
289
|
*
|
|
189
|
-
* @returns The
|
|
290
|
+
* @returns The ObjectKernel instance
|
|
190
291
|
* @throws Error if called before init()
|
|
191
292
|
*/
|
|
192
|
-
getKernel():
|
|
293
|
+
getKernel(): ObjectKernel {
|
|
193
294
|
if (!this.kernel) {
|
|
194
295
|
throw new Error('Kernel not initialized. Call init() first.');
|
|
195
296
|
}
|
|
@@ -205,26 +306,26 @@ export class ObjectQL implements IObjectQL {
|
|
|
205
306
|
}
|
|
206
307
|
}
|
|
207
308
|
}
|
|
208
|
-
this.kernel.metadata.register('object',
|
|
209
|
-
type: 'object',
|
|
210
|
-
id: object.name,
|
|
211
|
-
content: object
|
|
212
|
-
});
|
|
309
|
+
(this.kernel as any).metadata.register('object', object);
|
|
213
310
|
}
|
|
214
311
|
|
|
215
312
|
unregisterObject(name: string) {
|
|
216
|
-
this.kernel.metadata.unregister('object', name);
|
|
313
|
+
(this.kernel as any).metadata.unregister('object', name);
|
|
217
314
|
}
|
|
218
315
|
|
|
219
316
|
getObject(name: string): ObjectConfig | undefined {
|
|
220
|
-
|
|
317
|
+
const item = (this.kernel as any).metadata.get('object', name);
|
|
318
|
+
return item?.content || item;
|
|
221
319
|
}
|
|
222
320
|
|
|
223
321
|
getConfigs(): Record<string, ObjectConfig> {
|
|
224
322
|
const result: Record<string, ObjectConfig> = {};
|
|
225
|
-
const items = this.kernel.metadata.list
|
|
323
|
+
const items = (this.kernel as any).metadata.list('object') || [];
|
|
226
324
|
for (const item of items) {
|
|
227
|
-
|
|
325
|
+
const config = item.content || item;
|
|
326
|
+
if (config?.name) {
|
|
327
|
+
result[config.name] = config;
|
|
328
|
+
}
|
|
228
329
|
}
|
|
229
330
|
return result;
|
|
230
331
|
}
|
|
@@ -285,20 +386,41 @@ export class ObjectQL implements IObjectQL {
|
|
|
285
386
|
}
|
|
286
387
|
|
|
287
388
|
async init() {
|
|
288
|
-
console.log('[ObjectQL] Initializing with
|
|
389
|
+
console.log('[ObjectQL] Initializing with ObjectKernel...');
|
|
289
390
|
|
|
290
391
|
// Start the kernel - this will install and start all plugins
|
|
291
|
-
|
|
392
|
+
if ((this.kernel as any).start) {
|
|
393
|
+
await (this.kernel as any).start();
|
|
394
|
+
} else if ((this.kernel as any).bootstrap) {
|
|
395
|
+
await (this.kernel as any).bootstrap();
|
|
396
|
+
} else {
|
|
397
|
+
console.warn('ObjectKernel does not have start() or bootstrap() method');
|
|
398
|
+
}
|
|
292
399
|
|
|
293
400
|
// TEMPORARY: Set driver for backward compatibility during migration
|
|
294
401
|
// This allows the kernel mock to delegate to the driver
|
|
402
|
+
const defaultDriver = this.datasources['default'];
|
|
295
403
|
if (typeof (this.kernel as any).setDriver === 'function') {
|
|
296
|
-
const defaultDriver = this.datasources['default'];
|
|
297
404
|
if (defaultDriver) {
|
|
298
405
|
(this.kernel as any).setDriver(defaultDriver);
|
|
299
406
|
}
|
|
300
407
|
}
|
|
301
408
|
|
|
409
|
+
// TEMPORARY: Patch kernel with CRUD methods dynamically if missing
|
|
410
|
+
// This ensures the repository can delegate to the kernel even if using the new @objectstack/core kernel
|
|
411
|
+
if (typeof (this.kernel as any).create !== 'function' && defaultDriver) {
|
|
412
|
+
(this.kernel as any).create = (object: string, doc: any, options: any) => defaultDriver.create(object, doc, options);
|
|
413
|
+
(this.kernel as any).update = (object: string, id: any, doc: any, options: any) => defaultDriver.update(object, id, doc, options);
|
|
414
|
+
(this.kernel as any).delete = (object: string, id: any, options: any) => defaultDriver.delete(object, id, options);
|
|
415
|
+
(this.kernel as any).find = async (object: string, query: any, options: any) => {
|
|
416
|
+
const res = await defaultDriver.find(object, query, options);
|
|
417
|
+
return { value: res || [], count: (res || []).length };
|
|
418
|
+
};
|
|
419
|
+
(this.kernel as any).findOne = (object: string, id: any, options: any) => defaultDriver.findOne(object, id, options);
|
|
420
|
+
(this.kernel as any).get = (object: string, id: any) => defaultDriver.findOne(object, id);
|
|
421
|
+
(this.kernel as any).count = (object: string, query: any, options: any) => defaultDriver.count(object, query, options);
|
|
422
|
+
}
|
|
423
|
+
|
|
302
424
|
// Load In-Memory Objects (Dynamic Layer)
|
|
303
425
|
if (this.config.objects) {
|
|
304
426
|
for (const [key, obj] of Object.entries(this.config.objects)) {
|
|
@@ -306,7 +428,8 @@ export class ObjectQL implements IObjectQL {
|
|
|
306
428
|
}
|
|
307
429
|
}
|
|
308
430
|
|
|
309
|
-
const
|
|
431
|
+
const registryItems = (this.kernel as any).metadata.list('object');
|
|
432
|
+
const objects = (registryItems || []).map((item: any) => item.content || item) as ObjectConfig[];
|
|
310
433
|
|
|
311
434
|
// Init Datasources
|
|
312
435
|
// Let's pass all objects to all configured drivers.
|
|
@@ -324,7 +447,7 @@ export class ObjectQL implements IObjectQL {
|
|
|
324
447
|
}
|
|
325
448
|
|
|
326
449
|
private async processInitialData() {
|
|
327
|
-
const dataEntries = this.kernel.metadata.list
|
|
450
|
+
const dataEntries = (this.kernel as any).metadata.list('data');
|
|
328
451
|
if (dataEntries.length === 0) return;
|
|
329
452
|
|
|
330
453
|
console.log(`Processing ${dataEntries.length} initial data files...`);
|
|
@@ -333,17 +456,20 @@ export class ObjectQL implements IObjectQL {
|
|
|
333
456
|
const ctx = this.createContext({ isSystem: true });
|
|
334
457
|
|
|
335
458
|
for (const entry of dataEntries) {
|
|
459
|
+
// Unwrapping metadata content if present
|
|
460
|
+
const dataContent = (entry as any).content || entry;
|
|
461
|
+
|
|
336
462
|
// Expected format:
|
|
337
463
|
// 1. { object: 'User', records: [...] }
|
|
338
464
|
// 2. [ record1, record2 ] (with name property added by loader inferred from filename)
|
|
339
465
|
|
|
340
|
-
let objectName =
|
|
341
|
-
let records =
|
|
466
|
+
let objectName = dataContent.object;
|
|
467
|
+
let records = dataContent.records;
|
|
342
468
|
|
|
343
|
-
if (Array.isArray(
|
|
344
|
-
records =
|
|
345
|
-
if (!objectName && (
|
|
346
|
-
objectName = (
|
|
469
|
+
if (Array.isArray(dataContent)) {
|
|
470
|
+
records = dataContent;
|
|
471
|
+
if (!objectName && (dataContent as any).name) {
|
|
472
|
+
objectName = (dataContent as any).name;
|
|
347
473
|
}
|
|
348
474
|
}
|
|
349
475
|
|
package/src/index.ts
CHANGED
|
@@ -7,29 +7,28 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Re-export types from @objectstack packages for API compatibility
|
|
10
|
-
export type {
|
|
10
|
+
export type { ObjectKernel } from '@objectstack/runtime';
|
|
11
|
+
export type { ObjectStackProtocolImplementation } from '@objectstack/objectql';
|
|
11
12
|
// Note: @objectstack/objectql types temporarily commented out due to type incompatibilities
|
|
12
13
|
// in the published package. Will be re-enabled when package is updated.
|
|
13
14
|
// export type { ObjectQL as ObjectQLEngine, SchemaRegistry } from '@objectstack/objectql';
|
|
14
15
|
|
|
15
16
|
// Export ObjectStack spec types for driver development
|
|
16
|
-
import { Data,
|
|
17
|
+
import { Data, System } from '@objectstack/spec';
|
|
17
18
|
export type QueryAST = Data.QueryAST;
|
|
18
|
-
export type DriverInterface =
|
|
19
|
-
export type DriverOptions =
|
|
19
|
+
export type DriverInterface = Data.DriverInterface;
|
|
20
|
+
export type DriverOptions = Data.DriverOptions;
|
|
20
21
|
|
|
21
22
|
// Export our enhanced runtime components (actual implementations)
|
|
22
23
|
export * from './repository';
|
|
23
24
|
export * from './app';
|
|
24
25
|
export * from './plugin';
|
|
25
|
-
export * from './validator-plugin';
|
|
26
|
-
export * from './formula-plugin';
|
|
27
26
|
|
|
28
27
|
// Export query-specific modules (ObjectQL core competency)
|
|
29
28
|
export * from './query';
|
|
30
29
|
|
|
31
30
|
// Export utilities
|
|
32
|
-
export * from './validator';
|
|
33
31
|
export * from './util';
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
|
|
33
|
+
// Export kernel optimizations
|
|
34
|
+
export * from './optimizations';
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectQL
|
|
3
|
+
* Copyright (c) 2026-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook definition
|
|
11
|
+
*/
|
|
12
|
+
export interface Hook {
|
|
13
|
+
pattern: string;
|
|
14
|
+
handler: (context: any) => Promise<void> | void;
|
|
15
|
+
packageName?: string;
|
|
16
|
+
priority?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compiled Hook Manager
|
|
21
|
+
*
|
|
22
|
+
* Improvement: Pre-compiles hook pipelines by event pattern at registration time.
|
|
23
|
+
* No runtime pattern matching required.
|
|
24
|
+
*
|
|
25
|
+
* Expected: 5x faster hook execution, parallel async support
|
|
26
|
+
*/
|
|
27
|
+
export class CompiledHookManager {
|
|
28
|
+
// Direct event -> hooks mapping (no pattern matching at runtime)
|
|
29
|
+
private pipelines = new Map<string, Hook[]>();
|
|
30
|
+
|
|
31
|
+
// Keep track of all registered hooks for management
|
|
32
|
+
private allHooks = new Map<string, Hook>();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Expand a pattern like "before*" to all matching events
|
|
36
|
+
*/
|
|
37
|
+
private expandPattern(pattern: string): string[] {
|
|
38
|
+
// Common event patterns
|
|
39
|
+
const eventTypes = [
|
|
40
|
+
'beforeCreate', 'afterCreate',
|
|
41
|
+
'beforeUpdate', 'afterUpdate',
|
|
42
|
+
'beforeDelete', 'afterDelete',
|
|
43
|
+
'beforeFind', 'afterFind',
|
|
44
|
+
'beforeCount', 'afterCount'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// Handle wildcards
|
|
48
|
+
if (pattern === '*') {
|
|
49
|
+
return eventTypes;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (pattern.includes('*')) {
|
|
53
|
+
// Use global replace to handle all occurrences of *
|
|
54
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
55
|
+
return eventTypes.filter(event => regex.test(event));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Exact match
|
|
59
|
+
return [pattern];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Register a hook - pre-groups by event pattern
|
|
64
|
+
*/
|
|
65
|
+
registerHook(event: string, objectName: string, handler: any, packageName?: string): void {
|
|
66
|
+
const hook: Hook = {
|
|
67
|
+
pattern: `${event}:${objectName}`,
|
|
68
|
+
handler,
|
|
69
|
+
packageName,
|
|
70
|
+
priority: 0
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Store in all hooks registry
|
|
74
|
+
const hookId = `${event}:${objectName}:${Date.now()}`;
|
|
75
|
+
this.allHooks.set(hookId, hook);
|
|
76
|
+
|
|
77
|
+
// Expand event patterns
|
|
78
|
+
const events = this.expandPattern(event);
|
|
79
|
+
|
|
80
|
+
// Handle wildcard object names
|
|
81
|
+
if (objectName === '*') {
|
|
82
|
+
for (const concreteEvent of events) {
|
|
83
|
+
// Register for all potential object names
|
|
84
|
+
// Since we don't know all object names upfront, we keep a special '*' pipeline
|
|
85
|
+
const wildcardKey = `${concreteEvent}:*`;
|
|
86
|
+
if (!this.pipelines.has(wildcardKey)) {
|
|
87
|
+
this.pipelines.set(wildcardKey, []);
|
|
88
|
+
}
|
|
89
|
+
this.pipelines.get(wildcardKey)!.push(hook);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
// Pre-group hooks by concrete event names (only for non-wildcard objects)
|
|
93
|
+
for (const concreteEvent of events) {
|
|
94
|
+
const key = `${concreteEvent}:${objectName}`;
|
|
95
|
+
if (!this.pipelines.has(key)) {
|
|
96
|
+
this.pipelines.set(key, []);
|
|
97
|
+
}
|
|
98
|
+
this.pipelines.get(key)!.push(hook);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Run hooks for an event - direct lookup, no pattern matching
|
|
105
|
+
*/
|
|
106
|
+
async runHooks(event: string, objectName: string, context: any): Promise<void> {
|
|
107
|
+
const key = `${event}:${objectName}`;
|
|
108
|
+
const wildcardKey = `${event}:*`;
|
|
109
|
+
|
|
110
|
+
// Collect all applicable hooks
|
|
111
|
+
const hooks: Hook[] = [];
|
|
112
|
+
|
|
113
|
+
// Add object-specific hooks
|
|
114
|
+
const objectHooks = this.pipelines.get(key);
|
|
115
|
+
if (objectHooks) {
|
|
116
|
+
hooks.push(...objectHooks);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add wildcard hooks
|
|
120
|
+
const wildcardHooks = this.pipelines.get(wildcardKey);
|
|
121
|
+
if (wildcardHooks) {
|
|
122
|
+
hooks.push(...wildcardHooks);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (hooks.length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Sort by priority (higher priority first)
|
|
130
|
+
hooks.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
131
|
+
|
|
132
|
+
// Execute hooks in parallel for better performance
|
|
133
|
+
// Note: If order matters, change to sequential execution
|
|
134
|
+
await Promise.all(hooks.map(hook => {
|
|
135
|
+
try {
|
|
136
|
+
return Promise.resolve(hook.handler(context));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(`Hook execution failed for ${event}:${objectName}`, error);
|
|
139
|
+
return Promise.resolve();
|
|
140
|
+
}
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Remove all hooks from a package
|
|
146
|
+
*/
|
|
147
|
+
removePackage(packageName: string): void {
|
|
148
|
+
// Remove from all hooks registry
|
|
149
|
+
const hooksToRemove: string[] = [];
|
|
150
|
+
for (const [hookId, hook] of this.allHooks.entries()) {
|
|
151
|
+
if (hook.packageName === packageName) {
|
|
152
|
+
hooksToRemove.push(hookId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
hooksToRemove.forEach(id => this.allHooks.delete(id));
|
|
156
|
+
|
|
157
|
+
// Remove from pipelines
|
|
158
|
+
for (const [key, hooks] of this.pipelines.entries()) {
|
|
159
|
+
const filtered = hooks.filter(h => h.packageName !== packageName);
|
|
160
|
+
if (filtered.length === 0) {
|
|
161
|
+
this.pipelines.delete(key);
|
|
162
|
+
} else {
|
|
163
|
+
this.pipelines.set(key, filtered);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Clear all hooks
|
|
170
|
+
*/
|
|
171
|
+
clear(): void {
|
|
172
|
+
this.pipelines.clear();
|
|
173
|
+
this.allHooks.clear();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get statistics about registered hooks
|
|
178
|
+
*/
|
|
179
|
+
getStats(): { totalHooks: number; totalPipelines: number } {
|
|
180
|
+
return {
|
|
181
|
+
totalHooks: this.allHooks.size,
|
|
182
|
+
totalPipelines: this.pipelines.size
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|