@objectql/core 4.2.0 → 4.2.2
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 +1 -1
- package/CHANGELOG.md +34 -0
- package/README.md +5 -2
- package/dist/app.d.ts +61 -52
- package/dist/app.js +100 -435
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +14 -20
- package/dist/index.js +21 -20
- package/dist/index.js.map +1 -1
- package/dist/kernel-factory.d.ts +38 -0
- package/dist/kernel-factory.js +38 -0
- package/dist/kernel-factory.js.map +1 -0
- package/dist/plugin.d.ts +9 -16
- package/dist/plugin.js +71 -48
- package/dist/plugin.js.map +1 -1
- package/dist/repository.d.ts +4 -31
- package/dist/repository.js +7 -283
- package/dist/repository.js.map +1 -1
- package/dist/util.js +4 -2
- package/dist/util.js.map +1 -1
- package/package.json +14 -12
- package/src/app.ts +156 -539
- package/src/index.ts +18 -42
- package/src/kernel-factory.ts +47 -0
- package/src/plugin.ts +77 -85
- package/src/repository.ts +4 -320
- package/src/util.ts +5 -3
- package/test/__mocks__/@objectstack/core.ts +250 -1
- package/test/__mocks__/@objectstack/objectql.ts +273 -0
- package/test/__mocks__/@objectstack/runtime.ts +14 -5
- package/test/introspection.test.ts +1 -1
- package/test/mock-driver.ts +5 -5
- package/test/optimizations.test.ts +2 -2
- package/test/plugin-integration.test.ts +1 -3
- package/test/utils.ts +6 -6
- package/tsconfig.json +3 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/ai/index.d.ts +0 -8
- package/dist/ai/index.js +0 -25
- package/dist/ai/index.js.map +0 -1
- package/dist/ai/registry.d.ts +0 -23
- package/dist/ai/registry.js +0 -78
- package/dist/ai/registry.js.map +0 -1
- package/dist/gateway.d.ts +0 -37
- package/dist/gateway.js +0 -93
- package/dist/gateway.js.map +0 -1
- package/dist/optimizations/CompiledHookManager.d.ts +0 -56
- package/dist/optimizations/CompiledHookManager.js +0 -170
- package/dist/optimizations/CompiledHookManager.js.map +0 -1
- package/dist/optimizations/DependencyGraph.d.ts +0 -82
- package/dist/optimizations/DependencyGraph.js +0 -211
- package/dist/optimizations/DependencyGraph.js.map +0 -1
- package/dist/optimizations/GlobalConnectionPool.d.ts +0 -89
- package/dist/optimizations/GlobalConnectionPool.js +0 -193
- package/dist/optimizations/GlobalConnectionPool.js.map +0 -1
- package/dist/optimizations/LazyMetadataLoader.d.ts +0 -75
- package/dist/optimizations/LazyMetadataLoader.js +0 -149
- package/dist/optimizations/LazyMetadataLoader.js.map +0 -1
- package/dist/optimizations/OptimizedMetadataRegistry.d.ts +0 -26
- package/dist/optimizations/OptimizedMetadataRegistry.js +0 -117
- package/dist/optimizations/OptimizedMetadataRegistry.js.map +0 -1
- package/dist/optimizations/OptimizedValidationEngine.d.ts +0 -73
- package/dist/optimizations/OptimizedValidationEngine.js +0 -141
- package/dist/optimizations/OptimizedValidationEngine.js.map +0 -1
- package/dist/optimizations/QueryCompiler.d.ts +0 -51
- package/dist/optimizations/QueryCompiler.js +0 -216
- package/dist/optimizations/QueryCompiler.js.map +0 -1
- package/dist/optimizations/SQLQueryOptimizer.d.ts +0 -96
- package/dist/optimizations/SQLQueryOptimizer.js +0 -265
- package/dist/optimizations/SQLQueryOptimizer.js.map +0 -1
- package/dist/optimizations/index.d.ts +0 -32
- package/dist/optimizations/index.js +0 -44
- package/dist/optimizations/index.js.map +0 -1
- package/dist/protocol.d.ts +0 -191
- package/dist/protocol.js +0 -272
- package/dist/protocol.js.map +0 -1
- package/dist/query/filter-translator.d.ts +0 -24
- package/dist/query/filter-translator.js +0 -38
- package/dist/query/filter-translator.js.map +0 -1
- package/dist/query/index.d.ts +0 -22
- package/dist/query/index.js +0 -39
- package/dist/query/index.js.map +0 -1
- package/dist/query/query-analyzer.d.ts +0 -186
- package/dist/query/query-analyzer.js +0 -348
- package/dist/query/query-analyzer.js.map +0 -1
- package/dist/query/query-builder.d.ts +0 -27
- package/dist/query/query-builder.js +0 -69
- package/dist/query/query-builder.js.map +0 -1
- package/dist/query/query-service.d.ts +0 -151
- package/dist/query/query-service.js +0 -272
- package/dist/query/query-service.js.map +0 -1
- package/src/ai/index.ts +0 -9
- package/src/ai/registry.ts +0 -81
- package/src/gateway.ts +0 -105
- package/src/optimizations/CompiledHookManager.ts +0 -193
- package/src/optimizations/DependencyGraph.ts +0 -255
- package/src/optimizations/GlobalConnectionPool.ts +0 -251
- package/src/optimizations/LazyMetadataLoader.ts +0 -180
- package/src/optimizations/OptimizedMetadataRegistry.ts +0 -132
- package/src/optimizations/OptimizedValidationEngine.ts +0 -172
- package/src/optimizations/QueryCompiler.ts +0 -242
- package/src/optimizations/SQLQueryOptimizer.ts +0 -329
- package/src/optimizations/index.ts +0 -34
- package/src/protocol.ts +0 -304
- package/src/query/filter-translator.ts +0 -41
- package/src/query/index.ts +0 -24
- package/src/query/query-analyzer.ts +0 -532
- package/src/query/query-builder.ts +0 -64
- package/src/query/query-service.ts +0 -397
- package/test/ai-registry.test.ts +0 -42
- package/test/app.test.ts +0 -578
- package/test/filter-syntax.test.ts +0 -233
- package/test/gateway.test.ts +0 -88
- package/test/protocol.test.ts +0 -143
- package/test/repository-validation.test.ts +0 -351
- package/test/repository.test.ts +0 -151
package/src/app.ts
CHANGED
|
@@ -1,550 +1,167 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ObjectQL
|
|
2
|
+
* ObjectQL Bridge Class
|
|
3
3
|
* Copyright (c) 2026-present ObjectStack Inc.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Extends the upstream @objectstack/objectql.ObjectQL engine with:
|
|
6
|
+
* - Legacy constructor config (datasources map)
|
|
7
|
+
* - MetadataRegistry integration (for ObjectLoader filesystem loading)
|
|
8
|
+
*
|
|
9
|
+
* This allows existing consumers to keep using:
|
|
10
|
+
* const app = new ObjectQL({ datasources: { default: driver } });
|
|
11
|
+
* const loader = new ObjectLoader(app.metadata);
|
|
12
|
+
* await loader.load(dir);
|
|
13
|
+
* await app.init();
|
|
7
14
|
*/
|
|
8
15
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
LoaderPlugin,
|
|
24
|
-
Logger,
|
|
25
|
-
ConsoleLogger
|
|
26
|
-
} from '@objectql/types';
|
|
27
|
-
import { ObjectKernel, type Plugin } from '@objectstack/runtime';
|
|
28
|
-
import { ObjectQL as RuntimeObjectQL, SchemaRegistry } from '@objectstack/objectql';
|
|
29
|
-
import { ValidatorPlugin } from '@objectql/plugin-validator';
|
|
30
|
-
import { FormulaPlugin } from '@objectql/plugin-formula';
|
|
31
|
-
import { ObjectRepository } from './repository';
|
|
32
|
-
import { convertIntrospectedSchemaToObjects } from './util';
|
|
33
|
-
import { CompiledHookManager } from './optimizations/CompiledHookManager';
|
|
16
|
+
import { ObjectQL as UpstreamObjectQL, SchemaRegistry } from '@objectstack/objectql';
|
|
17
|
+
import type { ServiceObject } from '@objectstack/spec/data';
|
|
18
|
+
import type { DriverInterface } from '@objectstack/core';
|
|
19
|
+
import { MetadataRegistry } from '@objectql/types';
|
|
20
|
+
import type { Driver } from '@objectql/types';
|
|
21
|
+
|
|
22
|
+
// Runtime-safe accessor for compat methods that exist in @objectstack/objectql@3.0.1
|
|
23
|
+
// but may not be visible to TypeScript due to module resolution variance.
|
|
24
|
+
type UpstreamCompat = UpstreamObjectQL & {
|
|
25
|
+
registerObject(schema: ServiceObject, packageId?: string, namespace?: string): string;
|
|
26
|
+
getObject(name: string): ServiceObject | undefined;
|
|
27
|
+
getConfigs(): Record<string, ServiceObject>;
|
|
28
|
+
removePackage(packageId: string): void;
|
|
29
|
+
};
|
|
34
30
|
|
|
35
31
|
/**
|
|
36
|
-
* ObjectQL
|
|
37
|
-
*
|
|
38
|
-
* ObjectQL implementation that wraps ObjectKernel
|
|
39
|
-
* to provide the plugin architecture.
|
|
32
|
+
* Legacy config shape accepted by the ObjectQL bridge constructor.
|
|
40
33
|
*/
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private datasources: Record<string, Driver> = {};
|
|
48
|
-
private remotes: string[] = [];
|
|
49
|
-
|
|
50
|
-
// ObjectStack Kernel Integration
|
|
51
|
-
private kernel!: ObjectKernel & Record<string, any>;
|
|
52
|
-
private ql: any;
|
|
53
|
-
private kernelPlugins: any[] = [];
|
|
54
|
-
|
|
55
|
-
// Optimized managers
|
|
56
|
-
private hookManager = new CompiledHookManager();
|
|
57
|
-
private localActions = new Map<string, any>();
|
|
58
|
-
|
|
59
|
-
// Structured logger
|
|
60
|
-
private logger: Logger;
|
|
61
|
-
|
|
62
|
-
// Store config for lazy loading in init()
|
|
63
|
-
private config: ObjectQLConfig;
|
|
64
|
-
|
|
65
|
-
constructor(config: ObjectQLConfig) {
|
|
66
|
-
this.config = config;
|
|
67
|
-
this.datasources = config.datasources || {};
|
|
68
|
-
this.logger = config.logger ?? new ConsoleLogger({ name: '@objectql/core', level: 'info' });
|
|
69
|
-
|
|
70
|
-
if (config.connection) {
|
|
71
|
-
throw new Error("Connection strings are not supported in core directly. Use @objectql/platform-node's createDriverFromConnection or pass a driver instance to 'datasources'.");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Use the imported RuntimeObjectQL, assuming it works as intended
|
|
75
|
-
this.ql = new RuntimeObjectQL();
|
|
76
|
-
|
|
77
|
-
// Note: ObjectQLPlugin removed as it now uses RuntimePlugin interface
|
|
78
|
-
// which is incompatible with the legacy Plugin interface from @objectstack/core
|
|
79
|
-
// For new code, use the microkernel pattern with RuntimePlugin directly
|
|
80
|
-
|
|
81
|
-
// Add runtime plugins from config
|
|
82
|
-
if (config.plugins) {
|
|
83
|
-
for (const plugin of config.plugins) {
|
|
84
|
-
if (typeof plugin === 'string') {
|
|
85
|
-
throw new Error("String plugins are not supported in core. Use @objectql/platform-node or pass plugin instance.");
|
|
86
|
-
} else {
|
|
87
|
-
this.use(plugin as any);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Ensure default plugins are present
|
|
93
|
-
if (!this.kernelPlugins.some(p => p.name === 'validator')) {
|
|
94
|
-
this.use(new ValidatorPlugin());
|
|
95
|
-
}
|
|
96
|
-
if (!this.kernelPlugins.some(p => p.name === 'formula')) {
|
|
97
|
-
this.use(new FormulaPlugin());
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Create the kernel with registered plugins
|
|
101
|
-
this.kernel = new (ObjectKernel as any)(this.kernelPlugins);
|
|
102
|
-
for (const plugin of this.kernelPlugins) {
|
|
103
|
-
// Fallback for kernels that support .use() but maybe didn't take them in constructor or if we need to support both
|
|
104
|
-
// NOTE: Modern ObjectKernel takes plugins in constructor.
|
|
105
|
-
if ((this.kernel as any).use) {
|
|
106
|
-
// Try to avoid double registration if the kernel is smart, but since we don't know the kernel logic perfectly:
|
|
107
|
-
// Ideally check if already added. But for now, we leave this for backward compat
|
|
108
|
-
// if ObjectKernel DOESN't take constructor args but HAS use().
|
|
109
|
-
|
|
110
|
-
// However, we just instantiated it.
|
|
111
|
-
// Let's assume constructor is the way if available.
|
|
112
|
-
// But we keep this check for .use() just in case the constructor signature is different (e.g. empty)
|
|
113
|
-
(this.kernel as any).use(plugin);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Helper to unwrap content property (matching MetadataRegistry behavior)
|
|
118
|
-
const unwrapContent = (item: any) => {
|
|
119
|
-
if (item && item.content) {
|
|
120
|
-
return item.content;
|
|
121
|
-
}
|
|
122
|
-
return item;
|
|
123
|
-
};
|
|
34
|
+
export interface ObjectQLConfig {
|
|
35
|
+
datasources?: Record<string, Driver | DriverInterface>;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
124
38
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const metadata = (SchemaRegistry as any).metadata;
|
|
160
|
-
if (metadata && metadata instanceof Map) {
|
|
161
|
-
for (const [type, collection] of metadata.entries()) {
|
|
162
|
-
if (collection instanceof Map) {
|
|
163
|
-
for (const [key, item] of collection.entries()) {
|
|
164
|
-
if ((item as any).package === packageName) {
|
|
165
|
-
collection.delete(key);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
const kernelHooks = (this.kernel as any).hooks || {};
|
|
174
|
-
Object.assign(kernelHooks, {
|
|
175
|
-
register: (event: string, objectName: string, handler: any, packageName?: string) => {
|
|
176
|
-
this.hookManager.registerHook(event, objectName, handler, packageName);
|
|
177
|
-
},
|
|
178
|
-
removePackage: (packageName: string) => {
|
|
179
|
-
this.hookManager.removePackage(packageName);
|
|
180
|
-
},
|
|
181
|
-
trigger: async (event: string, objectName: string, ctx: any) => {
|
|
182
|
-
await this.hookManager.runHooks(event, objectName, ctx);
|
|
183
|
-
}
|
|
39
|
+
/**
|
|
40
|
+
* ObjectQL — drop-in replacement that bridges the upstream engine
|
|
41
|
+
* with the @objectql/types MetadataRegistry used by ObjectLoader.
|
|
42
|
+
*/
|
|
43
|
+
export class ObjectQL extends UpstreamObjectQL {
|
|
44
|
+
/**
|
|
45
|
+
* Filesystem metadata registry populated by ObjectLoader.
|
|
46
|
+
* After calling loader.load(), call app.init() to sync these
|
|
47
|
+
* entries into the upstream SchemaRegistry & driver layer.
|
|
48
|
+
*/
|
|
49
|
+
readonly metadata = new MetadataRegistry();
|
|
50
|
+
|
|
51
|
+
/** Typed self-reference for compat methods */
|
|
52
|
+
private get compat(): UpstreamCompat { return this as unknown as UpstreamCompat; }
|
|
53
|
+
|
|
54
|
+
private pendingDrivers: Array<{ name: string; driver: DriverInterface; isDefault: boolean }> = [];
|
|
55
|
+
|
|
56
|
+
// Explicitly declare inherited methods to ensure they're in the type definition
|
|
57
|
+
declare registerObject: (schema: ServiceObject, packageId?: string, namespace?: string) => string;
|
|
58
|
+
|
|
59
|
+
constructor(config: ObjectQLConfig = {}) {
|
|
60
|
+
// Upstream constructor only accepts hostContext
|
|
61
|
+
super();
|
|
62
|
+
|
|
63
|
+
// Store drivers for registration during init()
|
|
64
|
+
if (config.datasources) {
|
|
65
|
+
for (const [name, driver] of Object.entries(config.datasources)) {
|
|
66
|
+
// Always set driver.name to the config key so datasource(name) lookups work
|
|
67
|
+
(driver as any).name = name;
|
|
68
|
+
// Cast: local Driver interface is structurally compatible with upstream DriverInterface
|
|
69
|
+
this.pendingDrivers.push({
|
|
70
|
+
name,
|
|
71
|
+
driver: driver as DriverInterface,
|
|
72
|
+
isDefault: name === 'default'
|
|
184
73
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
isSystem: options.isSystem,
|
|
279
|
-
object: (name: string) => {
|
|
280
|
-
return new ObjectRepository(name, ctx, this);
|
|
281
|
-
},
|
|
282
|
-
transaction: async (callback: (ctx: ObjectQLContext) => Promise<any>) => {
|
|
283
|
-
const driver = this.datasources['default'];
|
|
284
|
-
if (!driver || !driver.beginTransaction) {
|
|
285
|
-
return callback(ctx);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
let trx: any;
|
|
289
|
-
try {
|
|
290
|
-
trx = await driver.beginTransaction();
|
|
291
|
-
} catch (e) {
|
|
292
|
-
throw e;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const trxCtx: ObjectQLContext = {
|
|
296
|
-
...ctx,
|
|
297
|
-
transactionHandle: trx,
|
|
298
|
-
transaction: async (cb: (ctx: ObjectQLContext) => Promise<any>) => cb(trxCtx)
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
const result = await callback(trxCtx);
|
|
303
|
-
if (driver.commitTransaction) await driver.commitTransaction(trx);
|
|
304
|
-
return result;
|
|
305
|
-
} catch (error) {
|
|
306
|
-
if (driver.rollbackTransaction) await driver.rollbackTransaction(trx);
|
|
307
|
-
throw error;
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
sudo: () => {
|
|
311
|
-
return this.createContext({ ...options, isSystem: true });
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
return ctx;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Get the underlying ObjectKernel instance
|
|
319
|
-
*
|
|
320
|
-
* This provides access to the kernel for advanced usage scenarios
|
|
321
|
-
* where you need direct access to the plugin architecture.
|
|
322
|
-
*
|
|
323
|
-
* @returns The ObjectKernel instance
|
|
324
|
-
* @throws Error if called before init()
|
|
325
|
-
*/
|
|
326
|
-
getKernel(): ObjectKernel {
|
|
327
|
-
if (!this.kernel) {
|
|
328
|
-
throw new Error('Kernel not initialized. Call init() first.');
|
|
329
|
-
}
|
|
330
|
-
return this.kernel;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
registerObject(object: ObjectConfig) {
|
|
334
|
-
// Normalize fields
|
|
335
|
-
if (object.fields) {
|
|
336
|
-
for (const [key, field] of Object.entries(object.fields)) {
|
|
337
|
-
if (field && !field.name) {
|
|
338
|
-
field.name = key;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
(this.kernel as any).metadata.register('object', object);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
unregisterObject(name: string) {
|
|
346
|
-
(this.kernel as any).metadata.unregister('object', name);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
getObject(name: string): ObjectConfig | undefined {
|
|
350
|
-
const item = (this.kernel as any).metadata.get('object', name);
|
|
351
|
-
return item?.content || item;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
getConfigs(): Record<string, ObjectConfig> {
|
|
355
|
-
const result: Record<string, ObjectConfig> = {};
|
|
356
|
-
const items = (this.kernel as any).metadata.list('object') || [];
|
|
357
|
-
for (const item of items) {
|
|
358
|
-
const config = item.content || item;
|
|
359
|
-
if (config?.name) {
|
|
360
|
-
result[config.name] = config;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return result;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
datasource(name: string): Driver {
|
|
367
|
-
const driver = this.datasources[name];
|
|
368
|
-
if (!driver) {
|
|
369
|
-
throw new Error(`Datasource '${name}' not found`);
|
|
370
|
-
}
|
|
371
|
-
return driver;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Introspect the database schema and automatically register objects.
|
|
376
|
-
* This allows connecting to an existing database without defining metadata.
|
|
377
|
-
*
|
|
378
|
-
* @param datasourceName - The name of the datasource to introspect (default: 'default')
|
|
379
|
-
* @param options - Optional configuration for schema conversion
|
|
380
|
-
* @returns Array of registered ObjectConfig
|
|
381
|
-
*/
|
|
382
|
-
async introspectAndRegister(
|
|
383
|
-
datasourceName: string = 'default',
|
|
384
|
-
options?: {
|
|
385
|
-
excludeTables?: string[];
|
|
386
|
-
includeTables?: string[];
|
|
387
|
-
skipSystemColumns?: boolean;
|
|
388
|
-
}
|
|
389
|
-
): Promise<ObjectConfig[]> {
|
|
390
|
-
const driver = this.datasource(datasourceName);
|
|
391
|
-
|
|
392
|
-
if (!driver.introspectSchema) {
|
|
393
|
-
throw new Error(`Driver for datasource '${datasourceName}' does not support schema introspection`);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
this.logger.info(`Introspecting datasource '${datasourceName}'...`);
|
|
397
|
-
const introspectedSchema = await driver.introspectSchema();
|
|
398
|
-
|
|
399
|
-
// Convert introspected schema to ObjectQL objects
|
|
400
|
-
const objects = convertIntrospectedSchemaToObjects(introspectedSchema, options);
|
|
401
|
-
|
|
402
|
-
this.logger.info(`Discovered ${objects.length} table(s), registering as objects...`);
|
|
403
|
-
|
|
404
|
-
// Register each discovered object
|
|
405
|
-
for (const obj of objects) {
|
|
406
|
-
this.registerObject(obj);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return objects;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async close() {
|
|
413
|
-
for (const [name, driver] of Object.entries(this.datasources)) {
|
|
414
|
-
if (driver.disconnect) {
|
|
415
|
-
this.logger.debug(`Closing driver '${name}'...`);
|
|
416
|
-
await driver.disconnect();
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
async init() {
|
|
422
|
-
this.logger.info('Initializing with ObjectKernel...');
|
|
423
|
-
|
|
424
|
-
// Start the kernel - this will install and start all plugins
|
|
425
|
-
if ((this.kernel as any).start) {
|
|
426
|
-
await (this.kernel as any).start();
|
|
427
|
-
} else if ((this.kernel as any).bootstrap) {
|
|
428
|
-
await (this.kernel as any).bootstrap();
|
|
429
|
-
} else {
|
|
430
|
-
this.logger.warn('ObjectKernel does not have start() or bootstrap() method');
|
|
431
|
-
|
|
432
|
-
// Manually initialize plugins if kernel doesn't support lifecycle
|
|
433
|
-
for (const plugin of this.kernelPlugins) {
|
|
434
|
-
try {
|
|
435
|
-
if (typeof (plugin as any).init === 'function') {
|
|
436
|
-
await (plugin as any).init(this.kernel);
|
|
437
|
-
}
|
|
438
|
-
if (typeof (plugin as any).start === 'function') {
|
|
439
|
-
await (plugin as any).start(this.kernel);
|
|
440
|
-
}
|
|
441
|
-
} catch (error) {
|
|
442
|
-
this.logger.error(`Failed to initialize plugin ${(plugin as any).name || 'unknown'}`, error as Error);
|
|
443
|
-
// Continue with other plugins even if one fails
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// TEMPORARY: Set driver for backward compatibility during migration
|
|
449
|
-
// This allows the kernel mock to delegate to the driver
|
|
450
|
-
const defaultDriver = this.datasources['default'];
|
|
451
|
-
if (typeof (this.kernel as any).setDriver === 'function') {
|
|
452
|
-
if (defaultDriver) {
|
|
453
|
-
(this.kernel as any).setDriver(defaultDriver);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// TEMPORARY: Patch kernel with CRUD methods dynamically if missing
|
|
458
|
-
// This ensures the repository can delegate to the kernel even if using the new @objectstack/core kernel
|
|
459
|
-
if (typeof (this.kernel as any).create !== 'function' && defaultDriver) {
|
|
460
|
-
(this.kernel as any).create = (object: string, doc: any, options: any) => defaultDriver.create(object, doc, options);
|
|
461
|
-
(this.kernel as any).update = (object: string, id: any, doc: any, options: any) => defaultDriver.update(object, id, doc, options);
|
|
462
|
-
(this.kernel as any).delete = (object: string, id: any, options: any) => defaultDriver.delete(object, id, options);
|
|
463
|
-
(this.kernel as any).find = async (object: string, query: any, options: any) => {
|
|
464
|
-
const res = await defaultDriver.find(object, query, options);
|
|
465
|
-
return { value: res || [], count: (res || []).length };
|
|
466
|
-
};
|
|
467
|
-
(this.kernel as any).findOne = (object: string, id: any, options: any) => defaultDriver.findOne(object, id, options);
|
|
468
|
-
(this.kernel as any).get = (object: string, id: any) => defaultDriver.findOne(object, id);
|
|
469
|
-
(this.kernel as any).count = (object: string, query: any, options: any) => defaultDriver.count(object, query, options);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Load In-Memory Objects (Dynamic Layer)
|
|
473
|
-
if (this.config.objects) {
|
|
474
|
-
for (const [key, obj] of Object.entries(this.config.objects)) {
|
|
475
|
-
this.registerObject(obj);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const registryItems = (this.kernel as any).metadata.list('object');
|
|
480
|
-
const objects = (registryItems || []).map((item: any) => item.content || item) as ObjectConfig[];
|
|
481
|
-
|
|
482
|
-
// Init Datasources
|
|
483
|
-
// Let's pass all objects to all configured drivers.
|
|
484
|
-
for (const [name, driver] of Object.entries(this.datasources)) {
|
|
485
|
-
if (driver.init) {
|
|
486
|
-
this.logger.debug(`Initializing driver '${name}'...`);
|
|
487
|
-
await driver.init(objects);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Process Initial Data
|
|
492
|
-
await this.processInitialData();
|
|
493
|
-
|
|
494
|
-
this.logger.info('Initialization complete');
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
private async processInitialData() {
|
|
498
|
-
const dataEntries = (this.kernel as any).metadata.list('data');
|
|
499
|
-
if (dataEntries.length === 0) return;
|
|
500
|
-
|
|
501
|
-
this.logger.info(`Processing ${dataEntries.length} initial data files...`);
|
|
502
|
-
|
|
503
|
-
// We need a system context to write data
|
|
504
|
-
const ctx = this.createContext({ isSystem: true });
|
|
505
|
-
|
|
506
|
-
for (const entry of dataEntries) {
|
|
507
|
-
// Unwrapping metadata content if present
|
|
508
|
-
const dataContent = (entry as any).content || entry;
|
|
509
|
-
|
|
510
|
-
// Expected format:
|
|
511
|
-
// 1. { object: 'User', records: [...] }
|
|
512
|
-
// 2. [ record1, record2 ] (with name property added by loader inferred from filename)
|
|
513
|
-
|
|
514
|
-
let objectName = dataContent.object;
|
|
515
|
-
let records = dataContent.records;
|
|
516
|
-
|
|
517
|
-
if (Array.isArray(dataContent)) {
|
|
518
|
-
records = dataContent;
|
|
519
|
-
if (!objectName && (dataContent as any).name) {
|
|
520
|
-
objectName = (dataContent as any).name;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (!objectName || !records || !Array.isArray(records)) {
|
|
525
|
-
this.logger.warn('Skipping invalid data entry', { entry: String(entry) });
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const repo = ctx.object(objectName);
|
|
530
|
-
|
|
531
|
-
for (const record of records) {
|
|
532
|
-
try {
|
|
533
|
-
// Check existence if a unique key is provided?
|
|
534
|
-
// For now, let's assume if it has an ID, we check it.
|
|
535
|
-
// Or we could try to find existing record by some key matching logic.
|
|
536
|
-
// Simple approach: create. If it fails (constraint), ignore.
|
|
537
|
-
|
|
538
|
-
// Actually, a better approach for initial data is "upsert" or "create if not exists".
|
|
539
|
-
// But without unique keys defined in data, we can't reliably dedup.
|
|
540
|
-
// Let's try to 'create' and catch errors.
|
|
541
|
-
await repo.create(record);
|
|
542
|
-
this.logger.debug(`Initialized record for ${objectName}`);
|
|
543
|
-
} catch (e: any) {
|
|
544
|
-
// Ignore duplicate key errors silently-ish
|
|
545
|
-
this.logger.warn(`Failed to insert initial data for ${objectName}: ${e.message}`);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Initialize the engine.
|
|
80
|
+
*
|
|
81
|
+
* Before calling the upstream init (which connects drivers and syncs schemas),
|
|
82
|
+
* bridge all objects loaded via ObjectLoader into the upstream SchemaRegistry.
|
|
83
|
+
*/
|
|
84
|
+
async init(): Promise<void> {
|
|
85
|
+
// Register any pending drivers from the constructor config
|
|
86
|
+
for (const { driver, isDefault } of this.pendingDrivers) {
|
|
87
|
+
(this as any).registerDriver(driver, isDefault);
|
|
88
|
+
}
|
|
89
|
+
this.pendingDrivers = [];
|
|
90
|
+
|
|
91
|
+
this.syncMetadataToRegistry();
|
|
92
|
+
return super.init();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Sync all filesystem-loaded metadata into the upstream SchemaRegistry.
|
|
97
|
+
* Called automatically by init(), but can also be called manually.
|
|
98
|
+
*/
|
|
99
|
+
private syncMetadataToRegistry(): void {
|
|
100
|
+
// Bridge filesystem-loaded objects → upstream SchemaRegistry
|
|
101
|
+
const objects = this.metadata.list<any>('object');
|
|
102
|
+
for (const obj of objects) {
|
|
103
|
+
if (obj && obj.name) {
|
|
104
|
+
// Only register if not already in SchemaRegistry
|
|
105
|
+
if (!SchemaRegistry.getObject(obj.name)) {
|
|
106
|
+
super.registerObject(obj as ServiceObject, '__filesystem__');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Bridge filesystem-loaded hooks → upstream hook system
|
|
112
|
+
const hooks = this.metadata.list<any>('hook');
|
|
113
|
+
for (const hookEntry of hooks) {
|
|
114
|
+
if (hookEntry && typeof hookEntry === 'object') {
|
|
115
|
+
const objectName = (hookEntry as any).id || (hookEntry as any).objectName;
|
|
116
|
+
for (const [event, handler] of Object.entries(hookEntry)) {
|
|
117
|
+
if (typeof handler === 'function' && event !== 'id' && event !== 'objectName') {
|
|
118
|
+
this.registerHook(event, handler as any, { object: objectName });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get an object definition by name.
|
|
127
|
+
*
|
|
128
|
+
* Checks the upstream SchemaRegistry first, then falls back to the
|
|
129
|
+
* local MetadataRegistry for objects loaded via ObjectLoader but
|
|
130
|
+
* not yet synced (i.e., init() hasn't been called yet).
|
|
131
|
+
*/
|
|
132
|
+
override getObject(name: string): ServiceObject | undefined {
|
|
133
|
+
// Check upstream SchemaRegistry first (call parent)
|
|
134
|
+
const upstream = super.getObject(name);
|
|
135
|
+
if (upstream) return upstream;
|
|
136
|
+
// Fallback: check local MetadataRegistry (pre-init)
|
|
137
|
+
return this.metadata.get<ServiceObject>('object', name);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get all registered object configs as a name→config map.
|
|
142
|
+
*
|
|
143
|
+
* Merges results from the upstream SchemaRegistry with the
|
|
144
|
+
* local MetadataRegistry (for pre-init objects).
|
|
145
|
+
*/
|
|
146
|
+
override getConfigs(): Record<string, ServiceObject> {
|
|
147
|
+
// Get upstream objects first (call parent)
|
|
148
|
+
const result = super.getConfigs();
|
|
149
|
+
// Merge local MetadataRegistry entries not yet synced upstream
|
|
150
|
+
const localObjects = this.metadata.list<any>('object');
|
|
151
|
+
for (const obj of localObjects) {
|
|
152
|
+
if (obj && obj.name && !result[obj.name]) {
|
|
153
|
+
result[obj.name] = obj;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Remove all hooks, actions, and objects contributed by a package.
|
|
161
|
+
* Also cleans up the local MetadataRegistry.
|
|
162
|
+
*/
|
|
163
|
+
override removePackage(packageId: string): void {
|
|
164
|
+
super.removePackage(packageId);
|
|
165
|
+
this.metadata.unregisterPackage(packageId);
|
|
166
|
+
}
|
|
550
167
|
}
|