@objectql/core 3.0.1 → 4.0.1

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 (96) hide show
  1. package/CHANGELOG.md +17 -3
  2. package/README.md +31 -9
  3. package/dist/ai-agent.d.ts +4 -3
  4. package/dist/ai-agent.js +10 -3
  5. package/dist/ai-agent.js.map +1 -1
  6. package/dist/app.d.ts +29 -6
  7. package/dist/app.js +117 -58
  8. package/dist/app.js.map +1 -1
  9. package/dist/formula-engine.d.ts +7 -0
  10. package/dist/formula-engine.js +9 -2
  11. package/dist/formula-engine.js.map +1 -1
  12. package/dist/formula-plugin.d.ts +52 -0
  13. package/dist/formula-plugin.js +107 -0
  14. package/dist/formula-plugin.js.map +1 -0
  15. package/dist/index.d.ts +16 -3
  16. package/dist/index.js +14 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/plugin.d.ts +89 -0
  19. package/dist/plugin.js +136 -0
  20. package/dist/plugin.js.map +1 -0
  21. package/dist/query/filter-translator.d.ts +39 -0
  22. package/dist/query/filter-translator.js +135 -0
  23. package/dist/query/filter-translator.js.map +1 -0
  24. package/dist/query/index.d.ts +22 -0
  25. package/dist/query/index.js +39 -0
  26. package/dist/query/index.js.map +1 -0
  27. package/dist/query/query-analyzer.d.ts +188 -0
  28. package/dist/query/query-analyzer.js +349 -0
  29. package/dist/query/query-analyzer.js.map +1 -0
  30. package/dist/query/query-builder.d.ts +29 -0
  31. package/dist/query/query-builder.js +71 -0
  32. package/dist/query/query-builder.js.map +1 -0
  33. package/dist/query/query-service.d.ts +152 -0
  34. package/dist/query/query-service.js +268 -0
  35. package/dist/query/query-service.js.map +1 -0
  36. package/dist/repository.d.ts +23 -2
  37. package/dist/repository.js +81 -14
  38. package/dist/repository.js.map +1 -1
  39. package/dist/util.d.ts +7 -0
  40. package/dist/util.js +18 -3
  41. package/dist/util.js.map +1 -1
  42. package/dist/validator-plugin.d.ts +56 -0
  43. package/dist/validator-plugin.js +106 -0
  44. package/dist/validator-plugin.js.map +1 -0
  45. package/dist/validator.d.ts +7 -0
  46. package/dist/validator.js +10 -8
  47. package/dist/validator.js.map +1 -1
  48. package/jest.config.js +16 -0
  49. package/package.json +7 -5
  50. package/src/ai-agent.ts +8 -0
  51. package/src/app.ts +136 -72
  52. package/src/formula-engine.ts +8 -0
  53. package/src/formula-plugin.ts +141 -0
  54. package/src/index.ts +28 -3
  55. package/src/plugin.ts +224 -0
  56. package/src/query/filter-translator.ts +148 -0
  57. package/src/query/index.ts +24 -0
  58. package/src/query/query-analyzer.ts +537 -0
  59. package/src/query/query-builder.ts +81 -0
  60. package/src/query/query-service.ts +393 -0
  61. package/src/repository.ts +101 -18
  62. package/src/util.ts +19 -3
  63. package/src/validator-plugin.ts +140 -0
  64. package/src/validator.ts +12 -5
  65. package/test/__mocks__/@objectstack/runtime.ts +255 -0
  66. package/test/app.test.ts +23 -35
  67. package/test/filter-syntax.test.ts +233 -0
  68. package/test/formula-engine.test.ts +8 -0
  69. package/test/formula-integration.test.ts +8 -0
  70. package/test/formula-plugin.test.ts +197 -0
  71. package/test/introspection.test.ts +8 -0
  72. package/test/mock-driver.ts +8 -0
  73. package/test/plugin-integration.test.ts +213 -0
  74. package/test/repository-validation.test.ts +8 -0
  75. package/test/repository.test.ts +8 -0
  76. package/test/util.test.ts +9 -1
  77. package/test/utils.ts +8 -0
  78. package/test/validator-plugin.test.ts +126 -0
  79. package/test/validator.test.ts +8 -0
  80. package/tsconfig.json +8 -0
  81. package/tsconfig.tsbuildinfo +1 -1
  82. package/dist/action.d.ts +0 -7
  83. package/dist/action.js +0 -23
  84. package/dist/action.js.map +0 -1
  85. package/dist/hook.d.ts +0 -8
  86. package/dist/hook.js +0 -25
  87. package/dist/hook.js.map +0 -1
  88. package/dist/object.d.ts +0 -3
  89. package/dist/object.js +0 -28
  90. package/dist/object.js.map +0 -1
  91. package/src/action.ts +0 -40
  92. package/src/hook.ts +0 -42
  93. package/src/object.ts +0 -26
  94. package/test/action.test.ts +0 -276
  95. package/test/hook.test.ts +0 -343
  96. package/test/object.test.ts +0 -183
@@ -0,0 +1,393 @@
1
+ /**
2
+ * ObjectQL Query Service
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 {
10
+ Driver,
11
+ ObjectConfig,
12
+ UnifiedQuery,
13
+ Filter,
14
+ MetadataRegistry
15
+ } from '@objectql/types';
16
+ import { Data } from '@objectstack/spec';
17
+ type QueryAST = Data.QueryAST;
18
+ import { QueryBuilder } from './query-builder';
19
+
20
+ /**
21
+ * Options for query execution
22
+ */
23
+ export interface QueryOptions {
24
+ /**
25
+ * Transaction handle for transactional queries
26
+ */
27
+ transaction?: any;
28
+
29
+ /**
30
+ * Skip validation (for system operations)
31
+ */
32
+ skipValidation?: boolean;
33
+
34
+ /**
35
+ * Include profiling information
36
+ */
37
+ profile?: boolean;
38
+
39
+ /**
40
+ * Custom driver options
41
+ */
42
+ driverOptions?: Record<string, unknown>;
43
+ }
44
+
45
+ /**
46
+ * Result of a query execution with optional profiling data
47
+ */
48
+ export interface QueryResult<T = any> {
49
+ /**
50
+ * The query results
51
+ */
52
+ value: T;
53
+
54
+ /**
55
+ * Total count (for paginated queries)
56
+ */
57
+ count?: number;
58
+
59
+ /**
60
+ * Profiling information (if profile option was enabled)
61
+ */
62
+ profile?: QueryProfile;
63
+ }
64
+
65
+ /**
66
+ * Profiling information for a query
67
+ */
68
+ export interface QueryProfile {
69
+ /**
70
+ * Execution time in milliseconds
71
+ */
72
+ executionTime: number;
73
+
74
+ /**
75
+ * Number of rows scanned
76
+ */
77
+ rowsScanned?: number;
78
+
79
+ /**
80
+ * Whether an index was used
81
+ */
82
+ indexUsed?: boolean;
83
+
84
+ /**
85
+ * The generated QueryAST
86
+ */
87
+ ast?: QueryAST;
88
+ }
89
+
90
+ /**
91
+ * Query Service
92
+ *
93
+ * Handles all query execution logic, separating query concerns from
94
+ * the repository pattern. This service is responsible for:
95
+ * - Building QueryAST from UnifiedQuery
96
+ * - Executing queries via drivers
97
+ * - Optional query profiling and analysis
98
+ *
99
+ * The QueryService is registered as a service in the ObjectQLPlugin
100
+ * and can be used by Repository for all read operations.
101
+ */
102
+ export class QueryService {
103
+ private queryBuilder: QueryBuilder;
104
+
105
+ constructor(
106
+ private datasources: Record<string, Driver>,
107
+ private metadata: MetadataRegistry
108
+ ) {
109
+ this.queryBuilder = new QueryBuilder();
110
+ }
111
+
112
+ /**
113
+ * Get the driver for a specific object
114
+ * @private
115
+ */
116
+ private getDriver(objectName: string): Driver {
117
+ const obj = this.getSchema(objectName);
118
+ const datasourceName = obj.datasource || 'default';
119
+ const driver = this.datasources[datasourceName];
120
+
121
+ if (!driver) {
122
+ throw new Error(`Datasource '${datasourceName}' not found for object '${objectName}'`);
123
+ }
124
+
125
+ return driver;
126
+ }
127
+
128
+ /**
129
+ * Get the schema for an object
130
+ * @private
131
+ */
132
+ private getSchema(objectName: string): ObjectConfig {
133
+ const obj = this.metadata.get<ObjectConfig>('object', objectName);
134
+ if (!obj) {
135
+ throw new Error(`Object '${objectName}' not found in metadata`);
136
+ }
137
+ return obj;
138
+ }
139
+
140
+ /**
141
+ * Build QueryAST from UnifiedQuery
142
+ * @private
143
+ */
144
+ private buildQueryAST(objectName: string, query: UnifiedQuery): QueryAST {
145
+ return this.queryBuilder.build(objectName, query);
146
+ }
147
+
148
+ /**
149
+ * Execute a find query
150
+ *
151
+ * @param objectName - The object to query
152
+ * @param query - The unified query
153
+ * @param options - Query execution options
154
+ * @returns Array of matching records
155
+ */
156
+ async find(
157
+ objectName: string,
158
+ query: UnifiedQuery = {},
159
+ options: QueryOptions = {}
160
+ ): Promise<QueryResult<any[]>> {
161
+ const driver = this.getDriver(objectName);
162
+ const startTime = options.profile ? Date.now() : 0;
163
+
164
+ // Build QueryAST
165
+ const ast = this.buildQueryAST(objectName, query);
166
+
167
+ // Execute query via driver
168
+ const driverOptions = {
169
+ transaction: options.transaction,
170
+ ...options.driverOptions
171
+ };
172
+
173
+ let results: any[];
174
+ let count: number | undefined;
175
+
176
+ if (driver.find) {
177
+ // Legacy driver interface
178
+ const result: any = await driver.find(objectName, query, driverOptions);
179
+ results = Array.isArray(result) ? result : (result?.value || []);
180
+ count = (typeof result === 'object' && !Array.isArray(result) && result?.count !== undefined) ? result.count : undefined;
181
+ } else if (driver.executeQuery) {
182
+ // New DriverInterface
183
+ const result = await driver.executeQuery(ast, driverOptions);
184
+ results = result.value || [];
185
+ count = result.count;
186
+ } else {
187
+ throw new Error(`Driver does not support query execution`);
188
+ }
189
+
190
+ const executionTime = options.profile ? Date.now() - startTime : 0;
191
+
192
+ return {
193
+ value: results,
194
+ count,
195
+ profile: options.profile ? {
196
+ executionTime,
197
+ ast,
198
+ rowsScanned: results.length,
199
+ } : undefined
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Execute a findOne query by ID
205
+ *
206
+ * @param objectName - The object to query
207
+ * @param id - The record ID
208
+ * @param options - Query execution options
209
+ * @returns The matching record or undefined
210
+ */
211
+ async findOne(
212
+ objectName: string,
213
+ id: string | number,
214
+ options: QueryOptions = {}
215
+ ): Promise<QueryResult<any>> {
216
+ const driver = this.getDriver(objectName);
217
+ const startTime = options.profile ? Date.now() : 0;
218
+
219
+ const driverOptions = {
220
+ transaction: options.transaction,
221
+ ...options.driverOptions
222
+ };
223
+
224
+ let result: any;
225
+
226
+ if (driver.findOne) {
227
+ // Legacy driver interface
228
+ result = await driver.findOne(objectName, id, driverOptions);
229
+ } else if (driver.get) {
230
+ // Alternative method name
231
+ result = await driver.get(objectName, String(id), driverOptions);
232
+ } else if (driver.executeQuery) {
233
+ // Fallback to query with ID filter
234
+ const query: UnifiedQuery = {
235
+ filters: [['_id', '=', id]]
236
+ };
237
+ const ast = this.buildQueryAST(objectName, query);
238
+ const queryResult = await driver.executeQuery(ast, driverOptions);
239
+ result = queryResult.value?.[0];
240
+ } else {
241
+ throw new Error(`Driver does not support findOne operation`);
242
+ }
243
+
244
+ const executionTime = options.profile ? Date.now() - startTime : 0;
245
+
246
+ return {
247
+ value: result,
248
+ profile: options.profile ? {
249
+ executionTime,
250
+ rowsScanned: result ? 1 : 0,
251
+ } : undefined
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Execute a count query
257
+ *
258
+ * @param objectName - The object to query
259
+ * @param filters - Optional filters
260
+ * @param options - Query execution options
261
+ * @returns Count of matching records
262
+ */
263
+ async count(
264
+ objectName: string,
265
+ filters?: Filter[],
266
+ options: QueryOptions = {}
267
+ ): Promise<QueryResult<number>> {
268
+ const driver = this.getDriver(objectName);
269
+ const startTime = options.profile ? Date.now() : 0;
270
+
271
+ const query: UnifiedQuery = filters ? { filters } : {};
272
+ const ast = this.buildQueryAST(objectName, query);
273
+
274
+ const driverOptions = {
275
+ transaction: options.transaction,
276
+ ...options.driverOptions
277
+ };
278
+
279
+ let count: number;
280
+
281
+ if (driver.count) {
282
+ // Legacy driver interface
283
+ count = await driver.count(objectName, filters || [], driverOptions);
284
+ } else if (driver.executeQuery) {
285
+ // Use executeQuery and count results
286
+ // Note: This is inefficient for large datasets
287
+ // Ideally, driver should support count-specific optimization
288
+ const result = await driver.executeQuery(ast, driverOptions);
289
+ count = result.count ?? result.value?.length ?? 0;
290
+ } else {
291
+ throw new Error(`Driver does not support count operation`);
292
+ }
293
+
294
+ const executionTime = options.profile ? Date.now() - startTime : 0;
295
+
296
+ return {
297
+ value: count,
298
+ profile: options.profile ? {
299
+ executionTime,
300
+ ast,
301
+ } : undefined
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Execute an aggregate query
307
+ *
308
+ * @param objectName - The object to query
309
+ * @param query - The aggregation query using UnifiedQuery format
310
+ * @param options - Query execution options
311
+ * @returns Aggregation results
312
+ */
313
+ async aggregate(
314
+ objectName: string,
315
+ query: UnifiedQuery,
316
+ options: QueryOptions = {}
317
+ ): Promise<QueryResult<any[]>> {
318
+ const driver = this.getDriver(objectName);
319
+ const startTime = options.profile ? Date.now() : 0;
320
+
321
+ const driverOptions = {
322
+ transaction: options.transaction,
323
+ ...options.driverOptions
324
+ };
325
+
326
+ let results: any[];
327
+
328
+ if (driver.aggregate) {
329
+ // Driver supports aggregation
330
+ results = await driver.aggregate(objectName, query, driverOptions);
331
+ } else {
332
+ // Driver doesn't support aggregation
333
+ throw new Error(`Driver does not support aggregate operations. Consider using a driver that supports aggregation.`);
334
+ }
335
+
336
+ const executionTime = options.profile ? Date.now() - startTime : 0;
337
+
338
+ return {
339
+ value: results,
340
+ profile: options.profile ? {
341
+ executionTime,
342
+ rowsScanned: results.length,
343
+ } : undefined
344
+ };
345
+ }
346
+
347
+ /**
348
+ * Execute a direct SQL/query passthrough
349
+ *
350
+ * This bypasses ObjectQL's query builder and executes raw queries.
351
+ * Use with caution as it bypasses security and validation.
352
+ *
353
+ * @param objectName - The object (determines which datasource to use)
354
+ * @param queryString - Raw query string (SQL, MongoDB query, etc.)
355
+ * @param params - Query parameters (for parameterized queries)
356
+ * @param options - Query execution options
357
+ * @returns Query results
358
+ */
359
+ async directQuery(
360
+ objectName: string,
361
+ queryString: string,
362
+ params?: any[],
363
+ options: QueryOptions = {}
364
+ ): Promise<QueryResult<any>> {
365
+ const driver = this.getDriver(objectName);
366
+ const startTime = options.profile ? Date.now() : 0;
367
+
368
+ const driverOptions = {
369
+ transaction: options.transaction,
370
+ ...options.driverOptions
371
+ };
372
+
373
+ let results: any;
374
+
375
+ if (driver.directQuery) {
376
+ results = await driver.directQuery(queryString, params);
377
+ } else if (driver.query) {
378
+ // Alternative method name
379
+ results = await driver.query(queryString, params);
380
+ } else {
381
+ throw new Error(`Driver does not support direct query execution`);
382
+ }
383
+
384
+ const executionTime = options.profile ? Date.now() - startTime : 0;
385
+
386
+ return {
387
+ value: results,
388
+ profile: options.profile ? {
389
+ executionTime,
390
+ } : undefined
391
+ };
392
+ }
393
+ }
package/src/repository.ts CHANGED
@@ -1,18 +1,65 @@
1
- import { ObjectQLContext, IObjectQL, ObjectConfig, Driver, UnifiedQuery, ActionContext, HookAPI, RetrievalHookContext, MutationHookContext, UpdateHookContext, ValidationContext, ValidationError, ValidationRuleResult, FormulaContext } from '@objectql/types';
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
+ import { ObjectQLContext, IObjectQL, ObjectConfig, Driver, UnifiedQuery, ActionContext, HookAPI, RetrievalHookContext, MutationHookContext, UpdateHookContext, ValidationContext, ValidationError, ValidationRuleResult, FormulaContext, Filter } from '@objectql/types';
10
+ import type { ObjectStackKernel } from '@objectql/runtime';
11
+ import { Data } from '@objectstack/spec';
12
+ type QueryAST = Data.QueryAST;
13
+ type FilterNode = Data.FilterNode;
14
+ type SortNode = Data.SortNode;
2
15
  import { Validator } from './validator';
3
16
  import { FormulaEngine } from './formula-engine';
17
+ import { QueryBuilder } from './query';
18
+
19
+ /**
20
+ * Extended ObjectStack Kernel with optional ObjectQL plugin capabilities.
21
+ * These properties are attached by ValidatorPlugin and FormulaPlugin during installation.
22
+ */
23
+ interface ExtendedKernel extends ObjectStackKernel {
24
+ validator?: Validator;
25
+ formulaEngine?: FormulaEngine;
26
+ }
4
27
 
5
28
  export class ObjectRepository {
6
- private validator: Validator;
7
- private formulaEngine: FormulaEngine;
29
+ private queryBuilder: QueryBuilder;
8
30
 
9
31
  constructor(
10
32
  private objectName: string,
11
33
  private context: ObjectQLContext,
12
34
  private app: IObjectQL
13
35
  ) {
14
- this.validator = new Validator();
15
- this.formulaEngine = new FormulaEngine();
36
+ this.queryBuilder = new QueryBuilder();
37
+ }
38
+
39
+ /**
40
+ * Get validator instance from kernel (via plugin)
41
+ * Falls back to creating a new instance if not available
42
+ */
43
+ private getValidator(): Validator {
44
+ const kernel = this.getKernel() as ExtendedKernel;
45
+ if (kernel.validator) {
46
+ return kernel.validator;
47
+ }
48
+ // Fallback for backward compatibility
49
+ return new Validator();
50
+ }
51
+
52
+ /**
53
+ * Get formula engine instance from kernel (via plugin)
54
+ * Falls back to creating a new instance if not available
55
+ */
56
+ private getFormulaEngine(): FormulaEngine {
57
+ const kernel = this.getKernel() as ExtendedKernel;
58
+ if (kernel.formulaEngine) {
59
+ return kernel.formulaEngine;
60
+ }
61
+ // Fallback for backward compatibility
62
+ return new FormulaEngine();
16
63
  }
17
64
 
18
65
  private getDriver(): Driver {
@@ -21,13 +68,24 @@ export class ObjectRepository {
21
68
  return this.app.datasource(datasourceName);
22
69
  }
23
70
 
24
- private getOptions(extra: any = {}) {
71
+ private getKernel(): ObjectStackKernel {
72
+ return this.app.getKernel();
73
+ }
74
+
75
+ private getOptions(extra: Record<string, unknown> = {}) {
25
76
  return {
26
77
  transaction: this.context.transactionHandle,
27
78
  ...extra
28
79
  };
29
80
  }
30
81
 
82
+ /**
83
+ * Translates ObjectQL UnifiedQuery to ObjectStack QueryAST format
84
+ */
85
+ private buildQueryAST(query: UnifiedQuery): QueryAST {
86
+ return this.queryBuilder.build(this.objectName, query);
87
+ }
88
+
31
89
  getSchema(): ObjectConfig {
32
90
  const obj = this.app.getObject(this.objectName);
33
91
  if (!obj) {
@@ -82,7 +140,7 @@ export class ObjectRepository {
82
140
  }
83
141
 
84
142
  const value = record[fieldName];
85
- const fieldResults = await this.validator.validateField(
143
+ const fieldResults = await this.getValidator().validateField(
86
144
  fieldName,
87
145
  fieldConfig,
88
146
  value,
@@ -121,7 +179,7 @@ export class ObjectRepository {
121
179
  changedFields,
122
180
  };
123
181
 
124
- const result = await this.validator.validate(schema.validation.rules, validationContext);
182
+ const result = await this.getValidator().validate(schema.validation.rules, validationContext);
125
183
  allResults.push(...result.results);
126
184
  }
127
185
 
@@ -168,7 +226,7 @@ export class ObjectRepository {
168
226
  // Evaluate each formula field
169
227
  for (const [fieldName, fieldConfig] of Object.entries(schema.fields)) {
170
228
  if (fieldConfig.type === 'formula' && fieldConfig.formula) {
171
- const result = this.formulaEngine.evaluate(
229
+ const result = this.getFormulaEngine().evaluate(
172
230
  fieldConfig.formula,
173
231
  formulaContext,
174
232
  fieldConfig.data_type || 'text',
@@ -213,11 +271,13 @@ export class ObjectRepository {
213
271
  };
214
272
  await this.app.triggerHook('beforeFind', this.objectName, hookCtx);
215
273
 
216
- // TODO: Apply basic filters like spaceId
217
- const results = await this.getDriver().find(this.objectName, hookCtx.query || {}, this.getOptions());
274
+ // Build QueryAST and execute via kernel
275
+ const ast = this.buildQueryAST(hookCtx.query || {});
276
+ const kernelResult = await this.getKernel().find(this.objectName, ast);
277
+ const results = kernelResult.value;
218
278
 
219
279
  // Evaluate formulas for each result
220
- const resultsWithFormulas = results.map(record => this.evaluateFormulas(record));
280
+ const resultsWithFormulas = results.map((record: any) => this.evaluateFormulas(record));
221
281
 
222
282
  hookCtx.result = resultsWithFormulas;
223
283
  await this.app.triggerHook('afterFind', this.objectName, hookCtx);
@@ -238,7 +298,8 @@ export class ObjectRepository {
238
298
  };
239
299
  await this.app.triggerHook('beforeFind', this.objectName, hookCtx);
240
300
 
241
- const result = await this.getDriver().findOne(this.objectName, idOrQuery, hookCtx.query, this.getOptions());
301
+ // Use kernel.get() for direct ID lookup
302
+ const result = await this.getKernel().get(this.objectName, String(idOrQuery));
242
303
 
243
304
  // Evaluate formulas if result exists
244
305
  const resultWithFormulas = result ? this.evaluateFormulas(result) : result;
@@ -253,6 +314,22 @@ export class ObjectRepository {
253
314
  }
254
315
 
255
316
  async count(filters: any): Promise<number> {
317
+ // Normalize filters to UnifiedQuery format
318
+ // If filters is an array, wrap it in a query object
319
+ // If filters is already a UnifiedQuery (has UnifiedQuery-specific properties), use it as-is
320
+ let query: UnifiedQuery;
321
+ if (Array.isArray(filters)) {
322
+ query = { filters };
323
+ } else if (filters && typeof filters === 'object' && (filters.fields || filters.sort || filters.limit !== undefined || filters.skip !== undefined)) {
324
+ // It's already a UnifiedQuery object
325
+ query = filters;
326
+ } else if (filters) {
327
+ // It's a raw filter object, wrap it
328
+ query = { filters };
329
+ } else {
330
+ query = {};
331
+ }
332
+
256
333
  const hookCtx: RetrievalHookContext = {
257
334
  ...this.context,
258
335
  objectName: this.objectName,
@@ -260,11 +337,14 @@ export class ObjectRepository {
260
337
  api: this.getHookAPI(),
261
338
  user: this.getUserFromContext(),
262
339
  state: {},
263
- query: filters
340
+ query
264
341
  };
265
342
  await this.app.triggerHook('beforeCount', this.objectName, hookCtx);
266
343
 
267
- const result = await this.getDriver().count(this.objectName, hookCtx.query, this.getOptions());
344
+ // Build QueryAST and execute via kernel to get count
345
+ const ast = this.buildQueryAST(hookCtx.query || {});
346
+ const kernelResult = await this.getKernel().find(this.objectName, ast);
347
+ const result = kernelResult.count;
268
348
 
269
349
  hookCtx.result = result;
270
350
  await this.app.triggerHook('afterCount', this.objectName, hookCtx);
@@ -290,7 +370,8 @@ export class ObjectRepository {
290
370
  // Validate the record before creating
291
371
  await this.validateRecord('create', finalDoc);
292
372
 
293
- const result = await this.getDriver().create(this.objectName, finalDoc, this.getOptions());
373
+ // Execute via kernel
374
+ const result = await this.getKernel().create(this.objectName, finalDoc);
294
375
 
295
376
  hookCtx.result = result;
296
377
  await this.app.triggerHook('afterCreate', this.objectName, hookCtx);
@@ -316,7 +397,8 @@ export class ObjectRepository {
316
397
  // Validate the update
317
398
  await this.validateRecord('update', hookCtx.data, previousData);
318
399
 
319
- const result = await this.getDriver().update(this.objectName, id, hookCtx.data, this.getOptions(options));
400
+ // Execute via kernel
401
+ const result = await this.getKernel().update(this.objectName, String(id), hookCtx.data);
320
402
 
321
403
  hookCtx.result = result;
322
404
  await this.app.triggerHook('afterUpdate', this.objectName, hookCtx);
@@ -337,7 +419,8 @@ export class ObjectRepository {
337
419
  };
338
420
  await this.app.triggerHook('beforeDelete', this.objectName, hookCtx);
339
421
 
340
- const result = await this.getDriver().delete(this.objectName, id, this.getOptions());
422
+ // Execute via kernel
423
+ const result = await this.getKernel().delete(this.objectName, String(id));
341
424
 
342
425
  hookCtx.result = result;
343
426
  await this.app.triggerHook('afterDelete', this.objectName, hookCtx);
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