@objectql/core 4.0.2 → 4.0.3

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.
Files changed (98) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +4 -4
  4. package/dist/app.d.ts +9 -6
  5. package/dist/app.js +151 -29
  6. package/dist/app.js.map +1 -1
  7. package/dist/index.d.ts +6 -9
  8. package/dist/index.js +2 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/optimizations/CompiledHookManager.d.ts +55 -0
  11. package/dist/optimizations/CompiledHookManager.js +164 -0
  12. package/dist/optimizations/CompiledHookManager.js.map +1 -0
  13. package/dist/optimizations/DependencyGraph.d.ts +82 -0
  14. package/dist/optimizations/DependencyGraph.js +211 -0
  15. package/dist/optimizations/DependencyGraph.js.map +1 -0
  16. package/dist/optimizations/GlobalConnectionPool.d.ts +89 -0
  17. package/dist/optimizations/GlobalConnectionPool.js +193 -0
  18. package/dist/optimizations/GlobalConnectionPool.js.map +1 -0
  19. package/dist/optimizations/LazyMetadataLoader.d.ts +75 -0
  20. package/dist/optimizations/LazyMetadataLoader.js +149 -0
  21. package/dist/optimizations/LazyMetadataLoader.js.map +1 -0
  22. package/dist/optimizations/OptimizedMetadataRegistry.d.ts +26 -0
  23. package/dist/optimizations/OptimizedMetadataRegistry.js +117 -0
  24. package/dist/optimizations/OptimizedMetadataRegistry.js.map +1 -0
  25. package/dist/optimizations/OptimizedValidationEngine.d.ts +73 -0
  26. package/dist/optimizations/OptimizedValidationEngine.js +141 -0
  27. package/dist/optimizations/OptimizedValidationEngine.js.map +1 -0
  28. package/dist/optimizations/QueryCompiler.d.ts +51 -0
  29. package/dist/optimizations/QueryCompiler.js +216 -0
  30. package/dist/optimizations/QueryCompiler.js.map +1 -0
  31. package/dist/optimizations/SQLQueryOptimizer.d.ts +96 -0
  32. package/dist/optimizations/SQLQueryOptimizer.js +265 -0
  33. package/dist/optimizations/SQLQueryOptimizer.js.map +1 -0
  34. package/dist/optimizations/index.d.ts +32 -0
  35. package/dist/optimizations/index.js +44 -0
  36. package/dist/optimizations/index.js.map +1 -0
  37. package/dist/plugin.d.ts +6 -7
  38. package/dist/plugin.js +39 -22
  39. package/dist/plugin.js.map +1 -1
  40. package/dist/query/query-analyzer.js.map +1 -1
  41. package/dist/query/query-builder.d.ts +6 -1
  42. package/dist/query/query-builder.js +21 -5
  43. package/dist/query/query-builder.js.map +1 -1
  44. package/dist/query/query-service.js.map +1 -1
  45. package/dist/repository.d.ts +2 -0
  46. package/dist/repository.js +15 -9
  47. package/dist/repository.js.map +1 -1
  48. package/jest.config.js +3 -3
  49. package/package.json +8 -5
  50. package/src/app.ts +173 -47
  51. package/src/index.ts +8 -9
  52. package/src/optimizations/CompiledHookManager.ts +185 -0
  53. package/src/optimizations/DependencyGraph.ts +255 -0
  54. package/src/optimizations/GlobalConnectionPool.ts +251 -0
  55. package/src/optimizations/LazyMetadataLoader.ts +180 -0
  56. package/src/optimizations/OptimizedMetadataRegistry.ts +132 -0
  57. package/src/optimizations/OptimizedValidationEngine.ts +172 -0
  58. package/src/optimizations/QueryCompiler.ts +242 -0
  59. package/src/optimizations/SQLQueryOptimizer.ts +329 -0
  60. package/src/optimizations/index.ts +34 -0
  61. package/src/plugin.ts +51 -28
  62. package/src/query/query-analyzer.ts +1 -1
  63. package/src/query/query-builder.ts +21 -7
  64. package/src/query/query-service.ts +1 -1
  65. package/src/repository.ts +25 -13
  66. package/test/__mocks__/@objectstack/runtime.ts +8 -8
  67. package/test/app.test.ts +9 -7
  68. package/test/optimizations.test.ts +440 -0
  69. package/test/plugin-integration.test.ts +30 -19
  70. package/tsconfig.json +4 -6
  71. package/tsconfig.tsbuildinfo +1 -1
  72. package/dist/ai-agent.d.ts +0 -176
  73. package/dist/ai-agent.js +0 -722
  74. package/dist/ai-agent.js.map +0 -1
  75. package/dist/formula-engine.d.ts +0 -102
  76. package/dist/formula-engine.js +0 -433
  77. package/dist/formula-engine.js.map +0 -1
  78. package/dist/formula-plugin.d.ts +0 -52
  79. package/dist/formula-plugin.js +0 -107
  80. package/dist/formula-plugin.js.map +0 -1
  81. package/dist/validator-plugin.d.ts +0 -56
  82. package/dist/validator-plugin.js +0 -106
  83. package/dist/validator-plugin.js.map +0 -1
  84. package/dist/validator.d.ts +0 -80
  85. package/dist/validator.js +0 -625
  86. package/dist/validator.js.map +0 -1
  87. package/src/ai-agent.ts +0 -868
  88. package/src/formula-engine.ts +0 -572
  89. package/src/formula-plugin.ts +0 -141
  90. package/src/validator-plugin.ts +0 -140
  91. package/src/validator.ts +0 -743
  92. package/test/formula-engine.test.ts +0 -725
  93. package/test/formula-integration.test.ts +0 -286
  94. package/test/formula-plugin.test.ts +0 -197
  95. package/test/formula-spec-compliance.test.ts +0 -258
  96. package/test/validation-spec-compliance.test.ts +0 -440
  97. package/test/validator-plugin.test.ts +0 -126
  98. package/test/validator.test.ts +0 -440
@@ -0,0 +1,251 @@
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
+ * Connection interface
11
+ */
12
+ export interface Connection {
13
+ id: string;
14
+ driverName: string;
15
+ inUse: boolean;
16
+ createdAt: number;
17
+ lastUsedAt: number;
18
+ release: () => Promise<void>;
19
+ }
20
+
21
+ /**
22
+ * Connection pool limits
23
+ */
24
+ export interface PoolLimits {
25
+ total: number;
26
+ perDriver: number;
27
+ }
28
+
29
+ /**
30
+ * Global Connection Pool Manager
31
+ *
32
+ * Improvement: Kernel-level connection pool with global limits.
33
+ * Coordinates connection allocation across all drivers.
34
+ *
35
+ * Expected: 5x faster connection acquisition, predictable resource usage
36
+ */
37
+ export class GlobalConnectionPool {
38
+ private limits: PoolLimits;
39
+ private allocations = new Map<string, number>();
40
+ private connections = new Map<string, Connection[]>();
41
+ private waitQueue: Array<{
42
+ driverName: string;
43
+ resolve: (conn: Connection) => void;
44
+ reject: (error: Error) => void;
45
+ }> = [];
46
+
47
+ constructor(limits: PoolLimits = { total: 50, perDriver: 20 }) {
48
+ this.limits = limits;
49
+ }
50
+
51
+ /**
52
+ * Get total number of active connections across all drivers
53
+ */
54
+ private totalConnections(): number {
55
+ let total = 0;
56
+ for (const count of this.allocations.values()) {
57
+ total += count;
58
+ }
59
+ return total;
60
+ }
61
+
62
+ /**
63
+ * Get number of connections for a specific driver
64
+ */
65
+ private getDriverConnections(driverName: string): number {
66
+ return this.allocations.get(driverName) || 0;
67
+ }
68
+
69
+ /**
70
+ * Try to process the wait queue
71
+ */
72
+ private processWaitQueue(): void {
73
+ if (this.waitQueue.length === 0) return;
74
+
75
+ // Check if we can fulfill any waiting requests
76
+ for (let i = 0; i < this.waitQueue.length; i++) {
77
+ const request = this.waitQueue[i];
78
+
79
+ // Check if we can allocate
80
+ if (this.canAllocate(request.driverName)) {
81
+ this.waitQueue.splice(i, 1);
82
+
83
+ // Try to acquire connection
84
+ this.doAcquire(request.driverName)
85
+ .then(request.resolve)
86
+ .catch(request.reject);
87
+
88
+ // Only process one at a time to avoid over-allocation
89
+ break;
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Check if we can allocate a connection for a driver
96
+ */
97
+ private canAllocate(driverName: string): boolean {
98
+ const totalConns = this.totalConnections();
99
+ const driverConns = this.getDriverConnections(driverName);
100
+
101
+ // Check if there's an idle connection available
102
+ const driverConnections = this.connections.get(driverName) || [];
103
+ const hasIdleConnection = driverConnections.some(c => !c.inUse);
104
+
105
+ // Can allocate if:
106
+ // 1. There's an idle connection (reuse), OR
107
+ // 2. We're under the total and per-driver limits (create new)
108
+ return hasIdleConnection || (totalConns < this.limits.total && driverConns < this.limits.perDriver);
109
+ }
110
+
111
+ /**
112
+ * Actually acquire a connection (internal)
113
+ */
114
+ private async doAcquire(driverName: string): Promise<Connection> {
115
+ // Check for available idle connection first
116
+ const driverConnections = this.connections.get(driverName) || [];
117
+ const idleConnection = driverConnections.find(c => !c.inUse);
118
+
119
+ if (idleConnection) {
120
+ idleConnection.inUse = true;
121
+ idleConnection.lastUsedAt = Date.now();
122
+ return idleConnection;
123
+ }
124
+
125
+ // Verify we can create a new connection (double-check to prevent race conditions)
126
+ const totalConns = this.totalConnections();
127
+ const driverConns = this.getDriverConnections(driverName);
128
+ if (totalConns >= this.limits.total || driverConns >= this.limits.perDriver) {
129
+ throw new Error(`Connection pool limit reached for driver: ${driverName}`);
130
+ }
131
+
132
+ // Create new connection
133
+ const connectionId = `${driverName}-${Date.now()}-${Math.random()}`;
134
+ const connection: Connection = {
135
+ id: connectionId,
136
+ driverName,
137
+ inUse: true,
138
+ createdAt: Date.now(),
139
+ lastUsedAt: Date.now(),
140
+ release: async () => {
141
+ connection.inUse = false;
142
+ connection.lastUsedAt = Date.now();
143
+
144
+ // Process wait queue when connection is released
145
+ this.processWaitQueue();
146
+ }
147
+ };
148
+
149
+ // Store connection
150
+ if (!this.connections.has(driverName)) {
151
+ this.connections.set(driverName, []);
152
+ }
153
+ this.connections.get(driverName)!.push(connection);
154
+
155
+ // Update allocation count
156
+ this.allocations.set(driverName, this.getDriverConnections(driverName) + 1);
157
+
158
+ return connection;
159
+ }
160
+
161
+ /**
162
+ * Acquire a connection from the pool
163
+ */
164
+ async acquire(driverName: string): Promise<Connection> {
165
+ // Check global limits before allocation ✅
166
+ if (!this.canAllocate(driverName)) {
167
+ // Add to wait queue
168
+ return new Promise((resolve, reject) => {
169
+ this.waitQueue.push({ driverName, resolve, reject });
170
+
171
+ // Set timeout to prevent indefinite waiting
172
+ setTimeout(() => {
173
+ const index = this.waitQueue.findIndex(
174
+ r => r.driverName === driverName && r.resolve === resolve
175
+ );
176
+ if (index >= 0) {
177
+ this.waitQueue.splice(index, 1);
178
+ reject(new Error(`Connection pool limit reached for driver: ${driverName}`));
179
+ }
180
+ }, 30000); // 30 second timeout
181
+ });
182
+ }
183
+
184
+ return this.doAcquire(driverName);
185
+ }
186
+
187
+ /**
188
+ * Release a connection back to the pool
189
+ */
190
+ async release(connection: Connection): Promise<void> {
191
+ await connection.release();
192
+ }
193
+
194
+ /**
195
+ * Close all connections for a driver
196
+ */
197
+ async closeDriver(driverName: string): Promise<void> {
198
+ const driverConnections = this.connections.get(driverName);
199
+ if (driverConnections) {
200
+ // Clear all connections
201
+ driverConnections.length = 0;
202
+ this.connections.delete(driverName);
203
+ this.allocations.delete(driverName);
204
+ }
205
+
206
+ // Process wait queue
207
+ this.processWaitQueue();
208
+ }
209
+
210
+ /**
211
+ * Get pool statistics
212
+ */
213
+ getStats(): {
214
+ totalConnections: number;
215
+ totalLimit: number;
216
+ perDriverLimit: number;
217
+ driverStats: Record<string, { active: number; idle: number }>;
218
+ waitQueueSize: number;
219
+ } {
220
+ const driverStats: Record<string, { active: number; idle: number }> = {};
221
+
222
+ for (const [driverName, connections] of this.connections.entries()) {
223
+ const active = connections.filter(c => c.inUse).length;
224
+ const idle = connections.filter(c => !c.inUse).length;
225
+ driverStats[driverName] = { active, idle };
226
+ }
227
+
228
+ return {
229
+ totalConnections: this.totalConnections(),
230
+ totalLimit: this.limits.total,
231
+ perDriverLimit: this.limits.perDriver,
232
+ driverStats,
233
+ waitQueueSize: this.waitQueue.length
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Update pool limits
239
+ */
240
+ updateLimits(limits: Partial<PoolLimits>): void {
241
+ if (limits.total !== undefined) {
242
+ this.limits.total = limits.total;
243
+ }
244
+ if (limits.perDriver !== undefined) {
245
+ this.limits.perDriver = limits.perDriver;
246
+ }
247
+
248
+ // Process wait queue after limits update
249
+ this.processWaitQueue();
250
+ }
251
+ }
@@ -0,0 +1,180 @@
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
+ * Object metadata definition
11
+ */
12
+ export interface ObjectMetadata {
13
+ name: string;
14
+ label?: string;
15
+ fields: Record<string, any>;
16
+ triggers?: any[];
17
+ workflows?: any[];
18
+ permissions?: any[];
19
+ relatedObjects?: string[];
20
+ }
21
+
22
+ /**
23
+ * Metadata loader function type
24
+ */
25
+ export type MetadataLoader = (objectName: string) => Promise<ObjectMetadata>;
26
+
27
+ /**
28
+ * Lazy Metadata Loader with Smart Caching
29
+ *
30
+ * Improvement: Loads metadata on-demand instead of eagerly at startup.
31
+ * Includes predictive preloading for related objects.
32
+ *
33
+ * Expected: 10x faster startup, 70% lower initial memory
34
+ */
35
+ export class LazyMetadataLoader {
36
+ private cache = new Map<string, ObjectMetadata>();
37
+ private loaded = new Set<string>();
38
+ private loading = new Map<string, Promise<ObjectMetadata>>();
39
+ private preloadScheduled = new Set<string>(); // Track objects with scheduled preloads
40
+ private loader: MetadataLoader;
41
+
42
+ constructor(loader: MetadataLoader) {
43
+ this.loader = loader;
44
+ }
45
+
46
+ /**
47
+ * Load a single object's metadata
48
+ */
49
+ private async loadSingle(objectName: string): Promise<ObjectMetadata> {
50
+ // Check if already loaded
51
+ if (this.loaded.has(objectName)) {
52
+ const cached = this.cache.get(objectName);
53
+ if (cached) return cached;
54
+ }
55
+
56
+ // Check if currently loading (avoid duplicate loads)
57
+ const existingLoad = this.loading.get(objectName);
58
+ if (existingLoad) {
59
+ return existingLoad;
60
+ }
61
+
62
+ // Load metadata
63
+ const loadPromise = (async () => {
64
+ try {
65
+ const metadata = await this.loader(objectName);
66
+ this.cache.set(objectName, metadata);
67
+ this.loaded.add(objectName);
68
+ return metadata;
69
+ } finally {
70
+ this.loading.delete(objectName);
71
+ }
72
+ })();
73
+
74
+ this.loading.set(objectName, loadPromise);
75
+ return loadPromise;
76
+ }
77
+
78
+ /**
79
+ * Predictive preload: load related objects in the background
80
+ */
81
+ private predictivePreload(objectName: string): void {
82
+ // Avoid redundant preload scheduling for the same object
83
+ if (this.preloadScheduled.has(objectName)) {
84
+ return;
85
+ }
86
+ this.preloadScheduled.add(objectName);
87
+
88
+ // Run preloading asynchronously after current call stack to avoid blocking
89
+ setImmediate(() => {
90
+ const metadata = this.cache.get(objectName);
91
+ if (!metadata) return;
92
+
93
+ // Extract related object names from various sources
94
+ const relatedObjects = new Set<string>();
95
+
96
+ // 1. From explicit relatedObjects field
97
+ if (metadata.relatedObjects) {
98
+ metadata.relatedObjects.forEach(obj => relatedObjects.add(obj));
99
+ }
100
+
101
+ // 2. From lookup/master-detail fields
102
+ if (metadata.fields) {
103
+ for (const field of Object.values(metadata.fields)) {
104
+ if (field.type === 'lookup' || field.type === 'master_detail') {
105
+ if (field.reference_to) {
106
+ relatedObjects.add(field.reference_to);
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ // Preload related objects asynchronously (don't await)
113
+ for (const relatedObject of relatedObjects) {
114
+ if (!this.loaded.has(relatedObject) && !this.loading.has(relatedObject)) {
115
+ // Fire and forget - preload in background
116
+ this.loadSingle(relatedObject).catch(() => {
117
+ // Ignore errors in background preloading
118
+ });
119
+ }
120
+ }
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Get metadata for an object (loads on-demand if not cached)
126
+ */
127
+ async get(objectName: string): Promise<ObjectMetadata> {
128
+ // Load on first access
129
+ const metadata = await this.loadSingle(objectName);
130
+
131
+ // Trigger predictive preloading for related objects
132
+ this.predictivePreload(objectName);
133
+
134
+ return metadata;
135
+ }
136
+
137
+ /**
138
+ * Check if metadata is loaded
139
+ */
140
+ isLoaded(objectName: string): boolean {
141
+ return this.loaded.has(objectName);
142
+ }
143
+
144
+ /**
145
+ * Preload metadata for specific objects
146
+ */
147
+ async preload(objectNames: string[]): Promise<void> {
148
+ await Promise.all(objectNames.map(name => this.get(name)));
149
+ }
150
+
151
+ /**
152
+ * Clear cache for an object
153
+ */
154
+ invalidate(objectName: string): void {
155
+ this.cache.delete(objectName);
156
+ this.loaded.delete(objectName);
157
+ this.preloadScheduled.delete(objectName);
158
+ }
159
+
160
+ /**
161
+ * Clear all cached metadata
162
+ */
163
+ clearAll(): void {
164
+ this.cache.clear();
165
+ this.loaded.clear();
166
+ this.loading.clear();
167
+ this.preloadScheduled.clear();
168
+ }
169
+
170
+ /**
171
+ * Get statistics about loaded metadata
172
+ */
173
+ getStats(): { loaded: number; cached: number; loading: number } {
174
+ return {
175
+ loaded: this.loaded.size,
176
+ cached: this.cache.size,
177
+ loading: this.loading.size
178
+ };
179
+ }
180
+ }
@@ -0,0 +1,132 @@
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
+ * Optimized Metadata Registry with O(k) package uninstall complexity
11
+ *
12
+ * Improvement: Uses secondary indexes to achieve O(k) complexity for
13
+ * unregisterPackage operation (where k is the number of items in the package)
14
+ * instead of O(n*m) (where n is types and m is items per type).
15
+ */
16
+
17
+ interface MetadataRef {
18
+ type: string;
19
+ name: string;
20
+ }
21
+
22
+ export class OptimizedMetadataRegistry {
23
+ private items: Record<string, Record<string, any>> = {};
24
+
25
+ // Secondary index: package name -> list of metadata references
26
+ private packageIndex = new Map<string, Set<MetadataRef>>();
27
+
28
+ constructor() {}
29
+
30
+ register(type: string, nameOrConfig: any, config?: any) {
31
+ if (!this.items[type]) {
32
+ this.items[type] = {};
33
+ }
34
+
35
+ let name: string;
36
+ let item: any;
37
+
38
+ if (config) {
39
+ name = nameOrConfig;
40
+ item = config;
41
+ } else {
42
+ item = nameOrConfig;
43
+ name = item.name || item.id;
44
+ }
45
+
46
+ if (name) {
47
+ this.items[type][name] = item;
48
+
49
+ // Update package index
50
+ const packageName = item.package || (item as any)._package || (item as any).packageName;
51
+ if (packageName) {
52
+ if (!this.packageIndex.has(packageName)) {
53
+ this.packageIndex.set(packageName, new Set());
54
+ }
55
+ this.packageIndex.get(packageName)!.add({ type, name });
56
+ }
57
+ }
58
+ }
59
+
60
+ get<T = any>(type: string, name: string): T {
61
+ const item = this.items[type]?.[name];
62
+ if (item && item.content) {
63
+ return item.content;
64
+ }
65
+ return item;
66
+ }
67
+
68
+ list<T = any>(type: string): T[] {
69
+ if (!this.items[type]) return [];
70
+ return Object.values(this.items[type]).map((item: any) => {
71
+ if (item && item.content) {
72
+ return item.content;
73
+ }
74
+ return item;
75
+ });
76
+ }
77
+
78
+ getTypes(): string[] {
79
+ return Object.keys(this.items);
80
+ }
81
+
82
+ getEntry<T = any>(type: string, name: string): T {
83
+ return this.items[type]?.[name];
84
+ }
85
+
86
+ unregister(type: string, name: string) {
87
+ const item = this.items[type]?.[name];
88
+ if (item) {
89
+ // Update package index
90
+ const packageName = item.package || (item as any)._package || (item as any).packageName;
91
+ if (packageName) {
92
+ const refs = this.packageIndex.get(packageName);
93
+ if (refs) {
94
+ // Remove this specific reference
95
+ for (const ref of refs) {
96
+ if (ref.type === type && ref.name === name) {
97
+ refs.delete(ref);
98
+ break;
99
+ }
100
+ }
101
+ // Clean up empty package entries
102
+ if (refs.size === 0) {
103
+ this.packageIndex.delete(packageName);
104
+ }
105
+ }
106
+ }
107
+ delete this.items[type][name];
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Optimized package unregistration with O(k) complexity
113
+ * where k is the number of items in the package.
114
+ *
115
+ * Previous complexity: O(n*m) - iterate all types and all items
116
+ * New complexity: O(k) - direct lookup via secondary index
117
+ */
118
+ unregisterPackage(packageName: string) {
119
+ // Direct lookup via secondary index ✅
120
+ const refs = this.packageIndex.get(packageName);
121
+ if (refs) {
122
+ // Delete each item referenced by this package
123
+ for (const ref of refs) {
124
+ if (this.items[ref.type]?.[ref.name]) {
125
+ delete this.items[ref.type][ref.name];
126
+ }
127
+ }
128
+ // Remove package from index
129
+ this.packageIndex.delete(packageName);
130
+ }
131
+ }
132
+ }