@objectql/core 4.0.1 → 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.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +32 -0
- package/README.md +14 -12
- 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 +6 -7
- package/dist/plugin.js +39 -22
- package/dist/plugin.js.map +1 -1
- package/dist/query/filter-translator.d.ts +6 -18
- package/dist/query/filter-translator.js +6 -103
- package/dist/query/filter-translator.js.map +1 -1
- package/dist/query/query-analyzer.js +24 -25
- package/dist/query/query-analyzer.js.map +1 -1
- package/dist/query/query-builder.d.ts +9 -3
- package/dist/query/query-builder.js +25 -35
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/query-service.d.ts +2 -2
- package/dist/query/query-service.js +5 -5
- package/dist/query/query-service.js.map +1 -1
- package/dist/repository.d.ts +2 -0
- package/dist/repository.js +24 -17
- 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 +7 -8
- 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 +51 -28
- package/src/query/filter-translator.ts +8 -115
- package/src/query/query-analyzer.ts +25 -29
- package/src/query/query-builder.ts +26 -43
- package/src/query/query-service.ts +6 -6
- package/src/repository.ts +35 -22
- package/test/__mocks__/@objectstack/runtime.ts +8 -8
- package/test/app.test.ts +11 -8
- 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/validator-plugin.test.ts +0 -126
- package/test/validator.test.ts +0 -440
package/src/index.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
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';
|
|
@@ -15,21 +16,19 @@ export type { ObjectStackKernel, ObjectStackRuntimeProtocol } from '@objectql/ru
|
|
|
15
16
|
// Export ObjectStack spec types for driver development
|
|
16
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
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
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
|
+
* Dependency type
|
|
11
|
+
*/
|
|
12
|
+
export type DependencyType = 'lookup' | 'master_detail' | 'foreign_key';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Edge in the dependency graph
|
|
16
|
+
*/
|
|
17
|
+
export interface DependencyEdge {
|
|
18
|
+
from: string;
|
|
19
|
+
to: string;
|
|
20
|
+
type: DependencyType;
|
|
21
|
+
fieldName: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Smart Dependency Graph
|
|
26
|
+
*
|
|
27
|
+
* Improvement: DAG-based dependency resolution for cascading operations.
|
|
28
|
+
* Automatically handles cascade deletes and updates in correct order.
|
|
29
|
+
*
|
|
30
|
+
* Expected: Eliminates manual cascade logic, prevents orphaned data
|
|
31
|
+
*/
|
|
32
|
+
export class DependencyGraph {
|
|
33
|
+
// Adjacency list: object -> list of dependent objects
|
|
34
|
+
private graph = new Map<string, Set<string>>();
|
|
35
|
+
|
|
36
|
+
// Store edge metadata
|
|
37
|
+
private edges = new Map<string, DependencyEdge[]>();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Add an object to the graph
|
|
41
|
+
*/
|
|
42
|
+
addObject(objectName: string): void {
|
|
43
|
+
if (!this.graph.has(objectName)) {
|
|
44
|
+
this.graph.set(objectName, new Set());
|
|
45
|
+
}
|
|
46
|
+
if (!this.edges.has(objectName)) {
|
|
47
|
+
this.edges.set(objectName, []);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Add a dependency edge
|
|
53
|
+
* from -> to means "to depends on from"
|
|
54
|
+
*/
|
|
55
|
+
addDependency(from: string, to: string, type: DependencyType, fieldName: string): void {
|
|
56
|
+
this.addObject(from);
|
|
57
|
+
this.addObject(to);
|
|
58
|
+
|
|
59
|
+
// Add edge
|
|
60
|
+
this.graph.get(from)!.add(to);
|
|
61
|
+
|
|
62
|
+
// Store edge metadata
|
|
63
|
+
const edge: DependencyEdge = { from, to, type, fieldName };
|
|
64
|
+
const fromEdges = this.edges.get(from) || [];
|
|
65
|
+
fromEdges.push(edge);
|
|
66
|
+
this.edges.set(from, fromEdges);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all objects that depend on the given object
|
|
71
|
+
*/
|
|
72
|
+
getDependents(objectName: string): string[] {
|
|
73
|
+
return Array.from(this.graph.get(objectName) || []);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Topological sort using DFS
|
|
78
|
+
*/
|
|
79
|
+
topologicalSort(objects: string[]): string[] {
|
|
80
|
+
const visited = new Set<string>();
|
|
81
|
+
const stack: string[] = [];
|
|
82
|
+
|
|
83
|
+
const dfs = (node: string) => {
|
|
84
|
+
if (visited.has(node)) return;
|
|
85
|
+
visited.add(node);
|
|
86
|
+
|
|
87
|
+
const dependents = this.graph.get(node);
|
|
88
|
+
if (dependents) {
|
|
89
|
+
for (const dependent of dependents) {
|
|
90
|
+
if (objects.includes(dependent)) {
|
|
91
|
+
dfs(dependent);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
stack.push(node);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
for (const obj of objects) {
|
|
100
|
+
dfs(obj);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return stack;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check for circular dependencies
|
|
108
|
+
*/
|
|
109
|
+
hasCircularDependency(): boolean {
|
|
110
|
+
const visited = new Set<string>();
|
|
111
|
+
const recursionStack = new Set<string>();
|
|
112
|
+
|
|
113
|
+
const hasCycle = (node: string): boolean => {
|
|
114
|
+
visited.add(node);
|
|
115
|
+
recursionStack.add(node);
|
|
116
|
+
|
|
117
|
+
const dependents = this.graph.get(node);
|
|
118
|
+
if (dependents) {
|
|
119
|
+
for (const dependent of dependents) {
|
|
120
|
+
if (!visited.has(dependent)) {
|
|
121
|
+
if (hasCycle(dependent)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
} else if (recursionStack.has(dependent)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
recursionStack.delete(node);
|
|
131
|
+
return false;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
for (const node of this.graph.keys()) {
|
|
135
|
+
if (!visited.has(node)) {
|
|
136
|
+
if (hasCycle(node)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get cascade delete order for an object
|
|
147
|
+
* Returns objects in the order they should be deleted
|
|
148
|
+
*/
|
|
149
|
+
getCascadeDeleteOrder(objectName: string): string[] {
|
|
150
|
+
const dependents = this.getDependents(objectName);
|
|
151
|
+
if (dependents.length === 0) {
|
|
152
|
+
return [objectName];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Recursively get all transitive dependents
|
|
156
|
+
const allDependents = new Set<string>();
|
|
157
|
+
const collectDependents = (obj: string) => {
|
|
158
|
+
const deps = this.getDependents(obj);
|
|
159
|
+
for (const dep of deps) {
|
|
160
|
+
if (!allDependents.has(dep)) {
|
|
161
|
+
allDependents.add(dep);
|
|
162
|
+
collectDependents(dep);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
collectDependents(objectName);
|
|
167
|
+
|
|
168
|
+
// Add the original object
|
|
169
|
+
allDependents.add(objectName);
|
|
170
|
+
|
|
171
|
+
// Sort topologically to get correct deletion order
|
|
172
|
+
const sorted = this.topologicalSort(Array.from(allDependents));
|
|
173
|
+
|
|
174
|
+
return sorted;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Automatically cascade delete based on dependency graph
|
|
179
|
+
*
|
|
180
|
+
* @param objectName The object type being deleted
|
|
181
|
+
* @param id The ID of the record being deleted
|
|
182
|
+
* @param deleteFunc Function to delete a record: (objectName, id) => Promise<void>
|
|
183
|
+
*/
|
|
184
|
+
async cascadeDelete(
|
|
185
|
+
objectName: string,
|
|
186
|
+
id: string,
|
|
187
|
+
deleteFunc: (objName: string, recordId: string) => Promise<void>
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
const deleteOrder = this.getCascadeDeleteOrder(objectName);
|
|
190
|
+
|
|
191
|
+
// Delete in correct order based on DAG
|
|
192
|
+
for (const objToDelete of deleteOrder) {
|
|
193
|
+
if (objToDelete === objectName) {
|
|
194
|
+
// Delete the main record
|
|
195
|
+
await deleteFunc(objectName, id);
|
|
196
|
+
} else {
|
|
197
|
+
// Find and delete dependent records
|
|
198
|
+
// This is a simplified version - in production, you'd need to:
|
|
199
|
+
// 1. Query for records that reference the deleted record
|
|
200
|
+
// 2. Delete them based on cascade rules (CASCADE vs SET NULL vs RESTRICT)
|
|
201
|
+
|
|
202
|
+
const edgesFromParent = this.edges.get(objectName) || [];
|
|
203
|
+
for (const edge of edgesFromParent) {
|
|
204
|
+
if (edge.to === objToDelete && edge.type === 'master_detail') {
|
|
205
|
+
// For master-detail, cascade delete dependent records
|
|
206
|
+
// await deleteFunc(objToDelete, <dependent_id>);
|
|
207
|
+
// Implementation would require querying for dependent records
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get graph statistics
|
|
216
|
+
*/
|
|
217
|
+
getStats(): {
|
|
218
|
+
totalObjects: number;
|
|
219
|
+
totalDependencies: number;
|
|
220
|
+
hasCircularDependency: boolean;
|
|
221
|
+
} {
|
|
222
|
+
let totalDeps = 0;
|
|
223
|
+
for (const deps of this.graph.values()) {
|
|
224
|
+
totalDeps += deps.size;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
totalObjects: this.graph.size,
|
|
229
|
+
totalDependencies: totalDeps,
|
|
230
|
+
hasCircularDependency: this.hasCircularDependency()
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Clear the graph
|
|
236
|
+
*/
|
|
237
|
+
clear(): void {
|
|
238
|
+
this.graph.clear();
|
|
239
|
+
this.edges.clear();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Export graph as DOT format for visualization
|
|
244
|
+
*/
|
|
245
|
+
toDot(): string {
|
|
246
|
+
let dot = 'digraph Dependencies {\n';
|
|
247
|
+
for (const [from, dependents] of this.graph.entries()) {
|
|
248
|
+
for (const to of dependents) {
|
|
249
|
+
dot += ` "${from}" -> "${to}";\n`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
dot += '}';
|
|
253
|
+
return dot;
|
|
254
|
+
}
|
|
255
|
+
}
|