@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.
Files changed (116) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +5 -2
  4. package/dist/app.d.ts +61 -52
  5. package/dist/app.js +100 -435
  6. package/dist/app.js.map +1 -1
  7. package/dist/index.d.ts +14 -20
  8. package/dist/index.js +21 -20
  9. package/dist/index.js.map +1 -1
  10. package/dist/kernel-factory.d.ts +38 -0
  11. package/dist/kernel-factory.js +38 -0
  12. package/dist/kernel-factory.js.map +1 -0
  13. package/dist/plugin.d.ts +9 -16
  14. package/dist/plugin.js +71 -48
  15. package/dist/plugin.js.map +1 -1
  16. package/dist/repository.d.ts +4 -31
  17. package/dist/repository.js +7 -283
  18. package/dist/repository.js.map +1 -1
  19. package/dist/util.js +4 -2
  20. package/dist/util.js.map +1 -1
  21. package/package.json +14 -12
  22. package/src/app.ts +156 -539
  23. package/src/index.ts +18 -42
  24. package/src/kernel-factory.ts +47 -0
  25. package/src/plugin.ts +77 -85
  26. package/src/repository.ts +4 -320
  27. package/src/util.ts +5 -3
  28. package/test/__mocks__/@objectstack/core.ts +250 -1
  29. package/test/__mocks__/@objectstack/objectql.ts +273 -0
  30. package/test/__mocks__/@objectstack/runtime.ts +14 -5
  31. package/test/introspection.test.ts +1 -1
  32. package/test/mock-driver.ts +5 -5
  33. package/test/optimizations.test.ts +2 -2
  34. package/test/plugin-integration.test.ts +1 -3
  35. package/test/utils.ts +6 -6
  36. package/tsconfig.json +3 -1
  37. package/tsconfig.tsbuildinfo +1 -1
  38. package/dist/ai/index.d.ts +0 -8
  39. package/dist/ai/index.js +0 -25
  40. package/dist/ai/index.js.map +0 -1
  41. package/dist/ai/registry.d.ts +0 -23
  42. package/dist/ai/registry.js +0 -78
  43. package/dist/ai/registry.js.map +0 -1
  44. package/dist/gateway.d.ts +0 -37
  45. package/dist/gateway.js +0 -93
  46. package/dist/gateway.js.map +0 -1
  47. package/dist/optimizations/CompiledHookManager.d.ts +0 -56
  48. package/dist/optimizations/CompiledHookManager.js +0 -170
  49. package/dist/optimizations/CompiledHookManager.js.map +0 -1
  50. package/dist/optimizations/DependencyGraph.d.ts +0 -82
  51. package/dist/optimizations/DependencyGraph.js +0 -211
  52. package/dist/optimizations/DependencyGraph.js.map +0 -1
  53. package/dist/optimizations/GlobalConnectionPool.d.ts +0 -89
  54. package/dist/optimizations/GlobalConnectionPool.js +0 -193
  55. package/dist/optimizations/GlobalConnectionPool.js.map +0 -1
  56. package/dist/optimizations/LazyMetadataLoader.d.ts +0 -75
  57. package/dist/optimizations/LazyMetadataLoader.js +0 -149
  58. package/dist/optimizations/LazyMetadataLoader.js.map +0 -1
  59. package/dist/optimizations/OptimizedMetadataRegistry.d.ts +0 -26
  60. package/dist/optimizations/OptimizedMetadataRegistry.js +0 -117
  61. package/dist/optimizations/OptimizedMetadataRegistry.js.map +0 -1
  62. package/dist/optimizations/OptimizedValidationEngine.d.ts +0 -73
  63. package/dist/optimizations/OptimizedValidationEngine.js +0 -141
  64. package/dist/optimizations/OptimizedValidationEngine.js.map +0 -1
  65. package/dist/optimizations/QueryCompiler.d.ts +0 -51
  66. package/dist/optimizations/QueryCompiler.js +0 -216
  67. package/dist/optimizations/QueryCompiler.js.map +0 -1
  68. package/dist/optimizations/SQLQueryOptimizer.d.ts +0 -96
  69. package/dist/optimizations/SQLQueryOptimizer.js +0 -265
  70. package/dist/optimizations/SQLQueryOptimizer.js.map +0 -1
  71. package/dist/optimizations/index.d.ts +0 -32
  72. package/dist/optimizations/index.js +0 -44
  73. package/dist/optimizations/index.js.map +0 -1
  74. package/dist/protocol.d.ts +0 -191
  75. package/dist/protocol.js +0 -272
  76. package/dist/protocol.js.map +0 -1
  77. package/dist/query/filter-translator.d.ts +0 -24
  78. package/dist/query/filter-translator.js +0 -38
  79. package/dist/query/filter-translator.js.map +0 -1
  80. package/dist/query/index.d.ts +0 -22
  81. package/dist/query/index.js +0 -39
  82. package/dist/query/index.js.map +0 -1
  83. package/dist/query/query-analyzer.d.ts +0 -186
  84. package/dist/query/query-analyzer.js +0 -348
  85. package/dist/query/query-analyzer.js.map +0 -1
  86. package/dist/query/query-builder.d.ts +0 -27
  87. package/dist/query/query-builder.js +0 -69
  88. package/dist/query/query-builder.js.map +0 -1
  89. package/dist/query/query-service.d.ts +0 -151
  90. package/dist/query/query-service.js +0 -272
  91. package/dist/query/query-service.js.map +0 -1
  92. package/src/ai/index.ts +0 -9
  93. package/src/ai/registry.ts +0 -81
  94. package/src/gateway.ts +0 -105
  95. package/src/optimizations/CompiledHookManager.ts +0 -193
  96. package/src/optimizations/DependencyGraph.ts +0 -255
  97. package/src/optimizations/GlobalConnectionPool.ts +0 -251
  98. package/src/optimizations/LazyMetadataLoader.ts +0 -180
  99. package/src/optimizations/OptimizedMetadataRegistry.ts +0 -132
  100. package/src/optimizations/OptimizedValidationEngine.ts +0 -172
  101. package/src/optimizations/QueryCompiler.ts +0 -242
  102. package/src/optimizations/SQLQueryOptimizer.ts +0 -329
  103. package/src/optimizations/index.ts +0 -34
  104. package/src/protocol.ts +0 -304
  105. package/src/query/filter-translator.ts +0 -41
  106. package/src/query/index.ts +0 -24
  107. package/src/query/query-analyzer.ts +0 -532
  108. package/src/query/query-builder.ts +0 -64
  109. package/src/query/query-service.ts +0 -397
  110. package/test/ai-registry.test.ts +0 -42
  111. package/test/app.test.ts +0 -578
  112. package/test/filter-syntax.test.ts +0 -233
  113. package/test/gateway.test.ts +0 -88
  114. package/test/protocol.test.ts +0 -143
  115. package/test/repository-validation.test.ts +0 -351
  116. 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
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
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
- MetadataRegistry,
11
- MetadataItem,
12
- Driver,
13
- ObjectConfig,
14
- ObjectQLContext,
15
- ObjectQLContextOptions,
16
- IObjectQL,
17
- ObjectQLConfig,
18
- HookName,
19
- HookHandler,
20
- HookContext,
21
- ActionHandler,
22
- ActionContext,
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 class ObjectQL implements IObjectQL {
42
- // Delegate to kernel for metadata, hooks, and actions
43
- public get metadata(): MetadataRegistry {
44
- return (this.kernel as any).metadata;
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
- // Stub legacy accessors
126
- (this.kernel as any).metadata = {
127
- register: (type: string, item: any) => SchemaRegistry.registerItem(type, item, item.id ? 'id' : 'name'),
128
- get: (type: string, name: string) => {
129
- const item = SchemaRegistry.getItem(type, name) as any;
130
- return unwrapContent(item);
131
- },
132
- getEntry: (type: string, name: string) => SchemaRegistry.getItem(type, name),
133
- list: (type: string) => {
134
- const items = SchemaRegistry.listItems(type);
135
- return items.map(unwrapContent);
136
- },
137
- unregister: (type: string, name: string) => {
138
- // Use the official unregisterItem API when available (added in @objectstack/objectql v0.9.2)
139
- // Fallback to direct metadata access for older versions or test mocks
140
- if (typeof SchemaRegistry.unregisterItem === 'function') {
141
- SchemaRegistry.unregisterItem(type, name);
142
- } else {
143
- // Fallback: try to access metadata Map directly
144
- const metadata = (SchemaRegistry as any).metadata;
145
- if (metadata && metadata instanceof Map) {
146
- const collection = metadata.get(type);
147
- if (collection && collection instanceof Map) {
148
- collection.delete(name);
149
- }
150
- }
151
- }
152
- },
153
- unregisterPackage: (packageName: string) => {
154
- // Use the official @objectstack/objectql 1.1.0 API for object cleanup
155
- if (typeof SchemaRegistry.unregisterObjectsByPackage === 'function') {
156
- SchemaRegistry.unregisterObjectsByPackage(packageName);
157
- }
158
- // Also clean up non-object metadata items by package
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
- if (!(this.kernel as any).hooks) {
186
- (this.kernel as any).hooks = kernelHooks;
187
- }
188
- (this.kernel as any).actions = {
189
- register: (objectName: string, actionName: string, handler: any, packageName?: string) => {
190
- const key = `${objectName}:${actionName}`;
191
- (handler as any)._package = packageName;
192
- this.localActions.set(key, handler);
193
- },
194
- removePackage: (packageName: string) => {
195
- for (const [key, handler] of this.localActions.entries()) {
196
- if ((handler as any)._package === packageName) {
197
- this.localActions.delete(key);
198
- }
199
- }
200
- },
201
- execute: async (objectName: string, actionName: string, ctx: any) => {
202
- const handler = this.localActions.get(`${objectName}:${actionName}`);
203
- if (handler) {
204
- return handler(ctx);
205
- }
206
- throw new Error(`Action '${actionName}' on object '${objectName}' not found`);
207
- }
208
- };
209
-
210
- // Register initial metadata if provided
211
- if (config.registry) {
212
- // Copy metadata from provided registry to kernel's registry
213
- for (const type of config.registry.getTypes()) {
214
- const items = config.registry.list(type);
215
- for (const item of items) {
216
- // Safely extract the item's id/name
217
- const itemId = typeof item === 'object' && item !== null
218
- ? (item as { name?: string; id?: string }).name || (item as { name?: string; id?: string }).id || 'unknown'
219
- : 'unknown';
220
-
221
- (this.kernel as any).metadata.register(type, {
222
- type,
223
- id: itemId,
224
- content: item
225
- });
226
- }
227
- }
228
- }
229
- }
230
-
231
- use(plugin: Plugin) {
232
- this.kernelPlugins.push(plugin);
233
- if (this.kernel && (this.kernel as any).use) {
234
- (this.kernel as any).use(plugin);
235
- }
236
- }
237
-
238
- removePackage(name: string) {
239
- // Delegate to kernel managers
240
- (this.kernel as any).metadata.unregisterPackage(name);
241
- (this.kernel as any).hooks.removePackage(name);
242
- (this.kernel as any).actions.removePackage(name);
243
- }
244
-
245
- on(event: HookName, objectName: string, handler: HookHandler, packageName?: string) {
246
- // Delegate to kernel hook manager
247
- // We wrap the handler to bridge ObjectQL's rich context types with runtime's base types
248
- // The runtime HookContext supports all fields via index signature, so this is safe
249
- const wrappedHandler = handler as unknown as any;
250
- (this.kernel as any).hooks.register(event, objectName, wrappedHandler, packageName);
251
- }
252
-
253
- async triggerHook(event: HookName, objectName: string, ctx: HookContext) {
254
- // Delegate to kernel hook manager
255
- // Runtime HookContext supports ObjectQL-specific fields via index signature
256
- await (this.kernel as any).hooks.trigger(event, objectName, ctx);
257
- }
258
-
259
- registerAction(objectName: string, actionName: string, handler: ActionHandler, packageName?: string) {
260
- // Delegate to kernel action manager
261
- // We wrap the handler to bridge ObjectQL's rich context types with runtime's base types
262
- // The runtime ActionContext supports all fields via index signature, so this is safe
263
- const wrappedHandler = handler as unknown as any;
264
- (this.kernel as any).actions.register(objectName, actionName, wrappedHandler, packageName);
265
- }
266
-
267
- async executeAction(objectName: string, actionName: string, ctx: ActionContext) {
268
- // Delegate to kernel action manager
269
- // Runtime ActionContext supports ObjectQL-specific fields via index signature
270
- return await (this.kernel as any).actions.execute(objectName, actionName, ctx);
271
- }
272
-
273
- createContext(options: ObjectQLContextOptions): ObjectQLContext {
274
- const ctx: ObjectQLContext = {
275
- userId: options.userId,
276
- spaceId: options.spaceId,
277
- roles: options.roles || [],
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
  }