@objectql/core 3.0.1 → 4.0.0

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 (97) hide show
  1. package/IMPLEMENTATION_STATUS.md +364 -0
  2. package/README.md +31 -9
  3. package/RUNTIME_INTEGRATION.md +391 -0
  4. package/dist/ai-agent.d.ts +4 -3
  5. package/dist/ai-agent.js +10 -3
  6. package/dist/ai-agent.js.map +1 -1
  7. package/dist/app.d.ts +29 -6
  8. package/dist/app.js +117 -58
  9. package/dist/app.js.map +1 -1
  10. package/dist/formula-engine.d.ts +7 -0
  11. package/dist/formula-engine.js +9 -2
  12. package/dist/formula-engine.js.map +1 -1
  13. package/dist/formula-plugin.d.ts +52 -0
  14. package/dist/formula-plugin.js +107 -0
  15. package/dist/formula-plugin.js.map +1 -0
  16. package/dist/index.d.ts +13 -3
  17. package/dist/index.js +14 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/plugin.d.ts +89 -0
  20. package/dist/plugin.js +99 -0
  21. package/dist/plugin.js.map +1 -0
  22. package/dist/query/filter-translator.d.ts +37 -0
  23. package/dist/query/filter-translator.js +135 -0
  24. package/dist/query/filter-translator.js.map +1 -0
  25. package/dist/query/index.d.ts +22 -0
  26. package/dist/query/index.js +39 -0
  27. package/dist/query/index.js.map +1 -0
  28. package/dist/query/query-analyzer.d.ts +186 -0
  29. package/dist/query/query-analyzer.js +349 -0
  30. package/dist/query/query-analyzer.js.map +1 -0
  31. package/dist/query/query-builder.d.ts +27 -0
  32. package/dist/query/query-builder.js +71 -0
  33. package/dist/query/query-builder.js.map +1 -0
  34. package/dist/query/query-service.d.ts +150 -0
  35. package/dist/query/query-service.js +268 -0
  36. package/dist/query/query-service.js.map +1 -0
  37. package/dist/repository.d.ts +23 -2
  38. package/dist/repository.js +62 -13
  39. package/dist/repository.js.map +1 -1
  40. package/dist/util.d.ts +7 -0
  41. package/dist/util.js +18 -3
  42. package/dist/util.js.map +1 -1
  43. package/dist/validator-plugin.d.ts +56 -0
  44. package/dist/validator-plugin.js +106 -0
  45. package/dist/validator-plugin.js.map +1 -0
  46. package/dist/validator.d.ts +7 -0
  47. package/dist/validator.js +10 -8
  48. package/dist/validator.js.map +1 -1
  49. package/jest.config.js +16 -0
  50. package/package.json +8 -5
  51. package/src/ai-agent.ts +8 -0
  52. package/src/app.ts +136 -72
  53. package/src/formula-engine.ts +8 -0
  54. package/src/formula-plugin.ts +141 -0
  55. package/src/index.ts +25 -3
  56. package/src/plugin.ts +179 -0
  57. package/src/query/filter-translator.ts +147 -0
  58. package/src/query/index.ts +24 -0
  59. package/src/query/query-analyzer.ts +535 -0
  60. package/src/query/query-builder.ts +80 -0
  61. package/src/query/query-service.ts +392 -0
  62. package/src/repository.ts +81 -17
  63. package/src/util.ts +19 -3
  64. package/src/validator-plugin.ts +140 -0
  65. package/src/validator.ts +12 -5
  66. package/test/__mocks__/@objectstack/runtime.ts +255 -0
  67. package/test/app.test.ts +23 -35
  68. package/test/filter-syntax.test.ts +233 -0
  69. package/test/formula-engine.test.ts +8 -0
  70. package/test/formula-integration.test.ts +8 -0
  71. package/test/formula-plugin.test.ts +197 -0
  72. package/test/introspection.test.ts +8 -0
  73. package/test/mock-driver.ts +8 -0
  74. package/test/plugin-integration.test.ts +213 -0
  75. package/test/repository-validation.test.ts +8 -0
  76. package/test/repository.test.ts +8 -0
  77. package/test/util.test.ts +9 -1
  78. package/test/utils.ts +8 -0
  79. package/test/validator-plugin.test.ts +126 -0
  80. package/test/validator.test.ts +8 -0
  81. package/tsconfig.json +9 -0
  82. package/tsconfig.tsbuildinfo +1 -1
  83. package/dist/action.d.ts +0 -7
  84. package/dist/action.js +0 -23
  85. package/dist/action.js.map +0 -1
  86. package/dist/hook.d.ts +0 -8
  87. package/dist/hook.js +0 -25
  88. package/dist/hook.js.map +0 -1
  89. package/dist/object.d.ts +0 -3
  90. package/dist/object.js +0 -28
  91. package/dist/object.js.map +0 -1
  92. package/src/action.ts +0 -40
  93. package/src/hook.ts +0 -42
  94. package/src/object.ts +0 -26
  95. package/test/action.test.ts +0 -276
  96. package/test/hook.test.ts +0 -343
  97. package/test/object.test.ts +0 -183
package/src/util.ts CHANGED
@@ -1,3 +1,11 @@
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
+
1
9
  import { ObjectConfig, FieldConfig, FieldType, IntrospectedSchema, IntrospectedColumn, IntrospectedTable } from '@objectql/types';
2
10
 
3
11
  export function toTitleCase(str: string): string {
@@ -102,20 +110,28 @@ export function convertIntrospectedSchemaToObjects(
102
110
 
103
111
  if (foreignKey) {
104
112
  // This is a lookup field
113
+ // Note: name must be set explicitly here since we're creating the config programmatically.
114
+ // When defined in YAML (ObjectConfig.fields Record), the name is auto-populated from the key.
105
115
  fieldConfig = {
116
+ name: column.name,
106
117
  type: 'lookup',
107
118
  reference_to: foreignKey.referencedTable,
108
119
  label: toTitleCase(column.name),
109
- required: !column.nullable
120
+ required: !column.nullable,
121
+ searchable: false
110
122
  };
111
123
  } else {
112
124
  // Regular field
113
125
  const fieldType = mapDatabaseTypeToFieldType(column.type);
114
126
 
127
+ // Note: name must be set explicitly here since we're creating the config programmatically.
128
+ // When defined in YAML (ObjectConfig.fields Record), the name is auto-populated from the key.
115
129
  fieldConfig = {
130
+ name: column.name,
116
131
  type: fieldType,
117
132
  label: toTitleCase(column.name),
118
- required: !column.nullable
133
+ required: !column.nullable,
134
+ searchable: false
119
135
  };
120
136
 
121
137
  // Add unique constraint
@@ -125,7 +141,7 @@ export function convertIntrospectedSchemaToObjects(
125
141
 
126
142
  // Add max length for text fields
127
143
  if (column.maxLength && (fieldType === 'text' || fieldType === 'textarea')) {
128
- fieldConfig.max_length = column.maxLength;
144
+ fieldConfig.maxLength = column.maxLength;
129
145
  }
130
146
 
131
147
  // Add default value
@@ -0,0 +1,140 @@
1
+ /**
2
+ * ObjectQL Validator Plugin
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
+ import type { RuntimePlugin, RuntimeContext, ObjectStackKernel } from '@objectstack/runtime';
10
+ import { Validator, ValidatorOptions } from './validator';
11
+
12
+ /**
13
+ * Extended ObjectStack Kernel with validator capability
14
+ */
15
+ interface KernelWithValidator extends ObjectStackKernel {
16
+ validator?: Validator;
17
+ }
18
+
19
+ /**
20
+ * Configuration for the Validator Plugin
21
+ */
22
+ export interface ValidatorPluginConfig extends ValidatorOptions {
23
+ /**
24
+ * Enable validation on queries
25
+ * @default true
26
+ */
27
+ enableQueryValidation?: boolean;
28
+
29
+ /**
30
+ * Enable validation on mutations
31
+ * @default true
32
+ */
33
+ enableMutationValidation?: boolean;
34
+ }
35
+
36
+ /**
37
+ * Validator Plugin
38
+ *
39
+ * Wraps the ObjectQL Validator engine as an ObjectStack plugin.
40
+ * Registers validation middleware hooks into the kernel lifecycle.
41
+ */
42
+ export class ValidatorPlugin implements RuntimePlugin {
43
+ name = '@objectql/validator';
44
+ version = '4.0.0';
45
+
46
+ private validator: Validator;
47
+ private config: ValidatorPluginConfig;
48
+
49
+ constructor(config: ValidatorPluginConfig = {}) {
50
+ this.config = {
51
+ enableQueryValidation: true,
52
+ enableMutationValidation: true,
53
+ ...config
54
+ };
55
+
56
+ // Initialize the validator with language options
57
+ this.validator = new Validator({
58
+ language: config.language,
59
+ languageFallback: config.languageFallback,
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Install the plugin into the kernel
65
+ * Registers validation middleware for queries and mutations
66
+ */
67
+ async install(ctx: RuntimeContext): Promise<void> {
68
+ const kernel = ctx.engine as KernelWithValidator;
69
+
70
+ console.log(`[${this.name}] Installing validator plugin...`);
71
+
72
+ // Make validator accessible from the kernel for direct usage
73
+ kernel.validator = this.validator;
74
+
75
+ // Register validation middleware for queries (if enabled)
76
+ if (this.config.enableQueryValidation !== false) {
77
+ this.registerQueryValidation(kernel);
78
+ }
79
+
80
+ // Register validation middleware for mutations (if enabled)
81
+ if (this.config.enableMutationValidation !== false) {
82
+ this.registerMutationValidation(kernel);
83
+ }
84
+
85
+ console.log(`[${this.name}] Validator plugin installed`);
86
+ }
87
+
88
+ /**
89
+ * Register query validation middleware
90
+ * @private
91
+ */
92
+ private registerQueryValidation(kernel: KernelWithValidator): void {
93
+ // Check if kernel supports middleware hooks
94
+ if (typeof (kernel as any).use === 'function') {
95
+ (kernel as any).use('beforeQuery', async (context: any) => {
96
+ // Query validation logic
97
+ // In a real implementation, this would validate query parameters
98
+ // For now, this is a placeholder that demonstrates the integration pattern
99
+ if (context.query && context.metadata?.validation_rules) {
100
+ // Validation would happen here
101
+ // const result = await this.validator.validate(
102
+ // context.metadata.validation_rules,
103
+ // { /* validation context */ }
104
+ // );
105
+ }
106
+ });
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Register mutation validation middleware
112
+ * @private
113
+ */
114
+ private registerMutationValidation(kernel: KernelWithValidator): void {
115
+ // Check if kernel supports middleware hooks
116
+ if (typeof (kernel as any).use === 'function') {
117
+ (kernel as any).use('beforeMutation', async (context: any) => {
118
+ // Mutation validation logic
119
+ // This would validate data before create/update operations
120
+ if (context.data && context.metadata?.validation_rules) {
121
+ // Validation would happen here
122
+ // const result = await this.validator.validate(
123
+ // context.metadata.validation_rules,
124
+ // { /* validation context */ }
125
+ // );
126
+ // if (!result.valid) {
127
+ // throw new Error('Validation failed: ' + result.errors.map(e => e.message).join(', '));
128
+ // }
129
+ }
130
+ });
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get the validator instance for direct access
136
+ */
137
+ getValidator(): Validator {
138
+ return this.validator;
139
+ }
140
+ }
package/src/validator.ts CHANGED
@@ -1,3 +1,11 @@
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
+
1
9
  /**
2
10
  * Validation engine for ObjectQL.
3
11
  * Executes validation rules based on metadata configuration.
@@ -182,11 +190,10 @@ export class Validator {
182
190
  }
183
191
  }
184
192
 
185
- // Pattern validation (supports both pattern and deprecated regex)
186
- const patternValue = validation.pattern ?? validation.regex;
187
- if (patternValue) {
193
+ // Pattern validation
194
+ if (validation.pattern) {
188
195
  try {
189
- const pattern = new RegExp(patternValue);
196
+ const pattern = new RegExp(validation.pattern);
190
197
  if (!pattern.test(String(value))) {
191
198
  results.push({
192
199
  rule: `${fieldName}_pattern`,
@@ -200,7 +207,7 @@ export class Validator {
200
207
  results.push({
201
208
  rule: `${fieldName}_pattern`,
202
209
  valid: false,
203
- message: `Invalid regex pattern: ${patternValue}`,
210
+ message: `Invalid regex pattern: ${validation.pattern}`,
204
211
  severity: 'error',
205
212
  fields: [fieldName],
206
213
  });
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Mock for @objectstack/runtime
3
+ * This mock is needed because the npm package has issues with Jest
4
+ * and we want to focus on testing ObjectQL's logic, not the kernel integration.
5
+ *
6
+ * For now, this mock delegates to the legacy driver to maintain backward compatibility
7
+ * during the migration phase.
8
+ */
9
+
10
+ // Simple mock implementations of runtime managers
11
+ class MockMetadataRegistry {
12
+ private store = new Map<string, Map<string, any>>();
13
+
14
+ register(type: string, item: any): void {
15
+ if (!this.store.has(type)) {
16
+ this.store.set(type, new Map());
17
+ }
18
+ const typeMap = this.store.get(type)!;
19
+ typeMap.set(item.id || item.name, item);
20
+ }
21
+
22
+ get<T = any>(type: string, id: string): T | undefined {
23
+ const typeMap = this.store.get(type);
24
+ const item = typeMap?.get(id);
25
+ return item?.content as T;
26
+ }
27
+
28
+ list<T = any>(type: string): T[] {
29
+ const typeMap = this.store.get(type);
30
+ if (!typeMap) return [];
31
+ return Array.from(typeMap.values()).map(item => item.content as T);
32
+ }
33
+
34
+ unregister(type: string, id: string): boolean {
35
+ const typeMap = this.store.get(type);
36
+ if (!typeMap) return false;
37
+ return typeMap.delete(id);
38
+ }
39
+
40
+ getTypes(): string[] {
41
+ return Array.from(this.store.keys());
42
+ }
43
+
44
+ unregisterPackage(packageName: string): void {
45
+ // Simple implementation - in real runtime this would filter by package
46
+ for (const [type, typeMap] of this.store.entries()) {
47
+ const toDelete: string[] = [];
48
+ for (const [id, item] of typeMap.entries()) {
49
+ if (item.packageName === packageName || item.package === packageName) {
50
+ toDelete.push(id);
51
+ }
52
+ }
53
+ toDelete.forEach(id => typeMap.delete(id));
54
+ }
55
+ }
56
+ }
57
+
58
+ class MockHookManager {
59
+ private hooks = new Map<string, any[]>();
60
+
61
+ register(hookName: string, objectName: string, handler: any, packageName?: string): void {
62
+ if (!this.hooks.has(hookName)) {
63
+ this.hooks.set(hookName, []);
64
+ }
65
+ this.hooks.get(hookName)!.push({ objectName, handler, packageName });
66
+ }
67
+
68
+ async trigger(hookName: string, objectName: string, context: any): Promise<void> {
69
+ const handlers = this.hooks.get(hookName) || [];
70
+ for (const entry of handlers) {
71
+ // Match on wildcard '*' or specific object name
72
+ if (entry.objectName === '*' || entry.objectName === objectName) {
73
+ await entry.handler(context);
74
+ }
75
+ }
76
+ }
77
+
78
+ removePackage(packageName: string): void {
79
+ for (const [hookName, handlers] of this.hooks.entries()) {
80
+ this.hooks.set(hookName, handlers.filter(h => h.packageName !== packageName));
81
+ }
82
+ }
83
+
84
+ clear(): void {
85
+ this.hooks.clear();
86
+ }
87
+ }
88
+
89
+ class MockActionManager {
90
+ private actions = new Map<string, any>();
91
+
92
+ register(objectName: string, actionName: string, handler: any, packageName?: string): void {
93
+ const key = `${objectName}.${actionName}`;
94
+ this.actions.set(key, { handler, packageName });
95
+ }
96
+
97
+ async execute(objectName: string, actionName: string, context: any): Promise<any> {
98
+ const key = `${objectName}.${actionName}`;
99
+ const action = this.actions.get(key);
100
+ if (!action) {
101
+ throw new Error(`Action ${actionName} not found for object ${objectName}`);
102
+ }
103
+ return await action.handler(context);
104
+ }
105
+
106
+ get(objectName: string, actionName: string): any {
107
+ const key = `${objectName}.${actionName}`;
108
+ return this.actions.get(key)?.handler;
109
+ }
110
+
111
+ removePackage(packageName: string): void {
112
+ const toDelete: string[] = [];
113
+ for (const [key, action] of this.actions.entries()) {
114
+ if (action.packageName === packageName) {
115
+ toDelete.push(key);
116
+ }
117
+ }
118
+ toDelete.forEach(key => this.actions.delete(key));
119
+ }
120
+
121
+ clear(): void {
122
+ this.actions.clear();
123
+ }
124
+ }
125
+
126
+ export class ObjectStackKernel {
127
+ public ql: unknown = null;
128
+ public metadata: MockMetadataRegistry;
129
+ public hooks: MockHookManager;
130
+ public actions: MockActionManager;
131
+
132
+ private plugins: any[] = [];
133
+ private driver: any = null; // Will be set by the ObjectQL app
134
+
135
+ constructor(plugins: any[] = []) {
136
+ this.plugins = plugins;
137
+ this.metadata = new MockMetadataRegistry();
138
+ this.hooks = new MockHookManager();
139
+ this.actions = new MockActionManager();
140
+ }
141
+
142
+ // Method to set the driver for delegation during migration
143
+ setDriver(driver: any): void {
144
+ this.driver = driver;
145
+ }
146
+
147
+ async start(): Promise<void> {
148
+ // Mock implementation that calls plugin lifecycle methods
149
+ for (const plugin of this.plugins) {
150
+ if (plugin.install) {
151
+ await plugin.install({ engine: this });
152
+ }
153
+ }
154
+ for (const plugin of this.plugins) {
155
+ if (plugin.onStart) {
156
+ await plugin.onStart({ engine: this });
157
+ }
158
+ }
159
+ }
160
+
161
+ async seed(): Promise<void> {
162
+ // Mock implementation
163
+ }
164
+
165
+ async find(objectName: string, query: any): Promise<{ value: Record<string, any>[]; count: number }> {
166
+ // Delegate to driver during migration phase
167
+ if (this.driver) {
168
+ // Convert QueryAST back to UnifiedQuery format for driver
169
+ const unifiedQuery: any = {};
170
+
171
+ if (query.fields) {
172
+ unifiedQuery.fields = query.fields;
173
+ }
174
+ if (query.filters) {
175
+ unifiedQuery.filters = query.filters;
176
+ }
177
+ if (query.sort) {
178
+ unifiedQuery.sort = query.sort.map((s: any) => [s.field, s.order]);
179
+ }
180
+ if (query.top !== undefined) {
181
+ unifiedQuery.limit = query.top;
182
+ }
183
+ if (query.skip !== undefined) {
184
+ unifiedQuery.skip = query.skip;
185
+ }
186
+ if (query.aggregations) {
187
+ unifiedQuery.aggregate = query.aggregations.map((agg: any) => ({
188
+ func: agg.function,
189
+ field: agg.field,
190
+ alias: agg.alias
191
+ }));
192
+ }
193
+ if (query.groupBy) {
194
+ unifiedQuery.groupBy = query.groupBy;
195
+ }
196
+
197
+ const results = await this.driver.find(objectName, unifiedQuery, {});
198
+ return { value: results, count: results.length };
199
+ }
200
+ return { value: [], count: 0 };
201
+ }
202
+
203
+ async get(objectName: string, id: string): Promise<Record<string, any>> {
204
+ // Delegate to driver during migration phase
205
+ if (this.driver) {
206
+ return await this.driver.findOne(objectName, id, {}, {});
207
+ }
208
+ return {};
209
+ }
210
+
211
+ async create(objectName: string, data: any): Promise<Record<string, any>> {
212
+ // Delegate to driver during migration phase
213
+ if (this.driver) {
214
+ return await this.driver.create(objectName, data, {});
215
+ }
216
+ return data;
217
+ }
218
+
219
+ async update(objectName: string, id: string, data: any): Promise<Record<string, any>> {
220
+ // Delegate to driver during migration phase
221
+ if (this.driver) {
222
+ return await this.driver.update(objectName, id, data, {});
223
+ }
224
+ return data;
225
+ }
226
+
227
+ async delete(objectName: string, id: string): Promise<boolean> {
228
+ // Delegate to driver during migration phase
229
+ if (this.driver) {
230
+ await this.driver.delete(objectName, id, {});
231
+ return true;
232
+ }
233
+ return true;
234
+ }
235
+
236
+ getMetadata(objectName: string): any {
237
+ return {};
238
+ }
239
+
240
+ getView(objectName: string, viewType?: 'list' | 'form'): any {
241
+ return null;
242
+ }
243
+ }
244
+
245
+ export class ObjectStackRuntimeProtocol {}
246
+
247
+ export interface RuntimeContext {
248
+ engine: ObjectStackKernel;
249
+ }
250
+
251
+ export interface RuntimePlugin {
252
+ name: string;
253
+ install?: (ctx: RuntimeContext) => void | Promise<void>;
254
+ onStart?: (ctx: RuntimeContext) => void | Promise<void>;
255
+ }
package/test/app.test.ts CHANGED
@@ -1,6 +1,15 @@
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
+
1
9
  import { ObjectQL } from '../src/app';
2
10
  import { MockDriver } from './mock-driver';
3
- import { ObjectConfig, ObjectQLPlugin, HookContext, ActionContext, Metadata } from '@objectql/types';
11
+ import { ObjectConfig, HookContext, ActionContext, Metadata } from '@objectql/types';
12
+ import type { PluginDefinition } from '@objectstack/spec';
4
13
 
5
14
  const todoObject: ObjectConfig = {
6
15
  name: 'todo',
@@ -57,9 +66,9 @@ describe('ObjectQL App', () => {
57
66
  });
58
67
 
59
68
  it('should accept plugin instances', () => {
60
- const mockPlugin: ObjectQLPlugin = {
61
- name: 'test-plugin',
62
- setup: jest.fn()
69
+ const mockPlugin: PluginDefinition = {
70
+ id: 'test-plugin',
71
+ onEnable: jest.fn()
63
72
  };
64
73
  const app = new ObjectQL({
65
74
  datasources: {},
@@ -282,11 +291,13 @@ describe('ObjectQL App', () => {
282
291
  });
283
292
 
284
293
  describe('Plugin System', () => {
285
- it('should initialize plugins on init', async () => {
286
- const setupFn = jest.fn();
287
- const mockPlugin: ObjectQLPlugin = {
294
+ it('should initialize runtime plugins on init', async () => {
295
+ const installFn = jest.fn();
296
+ const onStartFn = jest.fn();
297
+ const mockPlugin = {
288
298
  name: 'test-plugin',
289
- setup: setupFn
299
+ install: installFn,
300
+ onStart: onStartFn
290
301
  };
291
302
 
292
303
  const app = new ObjectQL({
@@ -295,43 +306,20 @@ describe('ObjectQL App', () => {
295
306
  });
296
307
 
297
308
  await app.init();
298
- expect(setupFn).toHaveBeenCalledWith(app);
309
+ expect(installFn).toHaveBeenCalled();
310
+ expect(onStartFn).toHaveBeenCalled();
299
311
  });
300
312
 
301
313
  it('should use plugin method', () => {
302
- const mockPlugin: ObjectQLPlugin = {
314
+ const mockPlugin = {
303
315
  name: 'test-plugin',
304
- setup: jest.fn()
316
+ install: jest.fn()
305
317
  };
306
318
 
307
319
  const app = new ObjectQL({ datasources: {} });
308
320
  app.use(mockPlugin);
309
321
  expect(app).toBeDefined();
310
322
  });
311
-
312
- it('should provide package-scoped proxy for plugins', async () => {
313
- let capturedApp: any;
314
- const mockPlugin: ObjectQLPlugin = {
315
- name: 'test-plugin',
316
- setup: async (app) => {
317
- capturedApp = app;
318
- }
319
- };
320
- (mockPlugin as any)._packageName = 'test-package';
321
-
322
- const app = new ObjectQL({
323
- datasources: {},
324
- plugins: [mockPlugin]
325
- });
326
- app.registerObject(todoObject);
327
-
328
- await app.init();
329
-
330
- // Test proxied methods
331
- const handler = jest.fn();
332
- capturedApp.on('beforeCreate', 'todo', handler);
333
- capturedApp.registerAction('todo', 'test', handler);
334
- });
335
323
  });
336
324
 
337
325
  describe('Initialization', () => {