@objectql/core 4.0.0 → 4.0.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 (49) hide show
  1. package/CHANGELOG.md +31 -3
  2. package/README.md +13 -11
  3. package/dist/app.d.ts +1 -1
  4. package/dist/app.js +1 -1
  5. package/dist/app.js.map +1 -1
  6. package/dist/formula-plugin.d.ts +1 -1
  7. package/dist/index.d.ts +5 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/plugin.d.ts +2 -2
  10. package/dist/plugin.js +40 -3
  11. package/dist/plugin.js.map +1 -1
  12. package/dist/query/filter-translator.d.ts +8 -18
  13. package/dist/query/filter-translator.js +6 -103
  14. package/dist/query/filter-translator.js.map +1 -1
  15. package/dist/query/query-analyzer.d.ts +3 -1
  16. package/dist/query/query-analyzer.js +24 -25
  17. package/dist/query/query-analyzer.js.map +1 -1
  18. package/dist/query/query-builder.d.ts +6 -3
  19. package/dist/query/query-builder.js +9 -35
  20. package/dist/query/query-builder.js.map +1 -1
  21. package/dist/query/query-service.d.ts +5 -3
  22. package/dist/query/query-service.js +5 -5
  23. package/dist/query/query-service.js.map +1 -1
  24. package/dist/repository.js +25 -6
  25. package/dist/repository.js.map +1 -1
  26. package/dist/validator-plugin.d.ts +1 -1
  27. package/jest.config.js +1 -1
  28. package/package.json +4 -5
  29. package/src/app.ts +3 -3
  30. package/src/formula-plugin.ts +1 -1
  31. package/src/index.ts +5 -2
  32. package/src/plugin.ts +50 -5
  33. package/src/query/filter-translator.ts +9 -115
  34. package/src/query/query-analyzer.ts +26 -28
  35. package/src/query/query-builder.ts +11 -41
  36. package/src/query/query-service.ts +7 -6
  37. package/src/repository.ts +28 -8
  38. package/src/validator-plugin.ts +1 -1
  39. package/test/__mocks__/@objectstack/runtime.ts +1 -1
  40. package/test/app.test.ts +2 -1
  41. package/test/formula-integration.test.ts +6 -6
  42. package/test/formula-plugin.test.ts +1 -1
  43. package/test/formula-spec-compliance.test.ts +258 -0
  44. package/test/validation-spec-compliance.test.ts +440 -0
  45. package/test/validator-plugin.test.ts +1 -1
  46. package/tsconfig.json +0 -1
  47. package/tsconfig.tsbuildinfo +1 -1
  48. package/IMPLEMENTATION_STATUS.md +0 -364
  49. package/RUNTIME_INTEGRATION.md +0 -391
@@ -7,141 +7,35 @@
7
7
  */
8
8
 
9
9
  import type { Filter } from '@objectql/types';
10
- import type { FilterNode } from '@objectstack/spec';
10
+ import { Data } from '@objectstack/spec';
11
+ type FilterCondition = Data.FilterCondition;
11
12
  import { ObjectQLError } from '@objectql/types';
12
13
 
13
14
  /**
14
15
  * Filter Translator
15
16
  *
16
- * Translates ObjectQL Filter (FilterCondition) to ObjectStack FilterNode format.
17
- * Converts modern object-based syntax to legacy array-based syntax for backward compatibility.
17
+ * Translates ObjectQL Filter to ObjectStack FilterCondition format.
18
+ * Since both now use the same format, this is mostly a pass-through.
18
19
  *
19
20
  * @example
20
21
  * Input: { age: { $gte: 18 }, $or: [{ status: "active" }, { role: "admin" }] }
21
- * Output: [["age", ">=", 18], "or", [["status", "=", "active"], "or", ["role", "=", "admin"]]]
22
+ * Output: { age: { $gte: 18 }, $or: [{ status: "active" }, { role: "admin" }] }
22
23
  */
23
24
  export class FilterTranslator {
24
25
  /**
25
- * Translate filters from ObjectQL format to ObjectStack FilterNode format
26
+ * Translate filters from ObjectQL format to ObjectStack FilterCondition format
26
27
  */
27
- translate(filters?: Filter): FilterNode | undefined {
28
+ translate(filters?: Filter): FilterCondition | undefined {
28
29
  if (!filters) {
29
30
  return undefined;
30
31
  }
31
32
 
32
- // Backward compatibility: if it's already an array (old format), pass through
33
- if (Array.isArray(filters)) {
34
- return filters as unknown as FilterNode;
35
- }
36
-
37
33
  // If it's an empty object, return undefined
38
34
  if (typeof filters === 'object' && Object.keys(filters).length === 0) {
39
35
  return undefined;
40
36
  }
41
37
 
42
- return this.convertToNode(filters);
43
- }
44
-
45
- /**
46
- * Recursively converts FilterCondition to FilterNode array format
47
- */
48
- private convertToNode(filter: Filter): FilterNode {
49
- const nodes: any[] = [];
50
-
51
- // Process logical operators first
52
- if (filter.$and) {
53
- const andNodes = filter.$and.map((f: Filter) => this.convertToNode(f));
54
- nodes.push(...this.interleaveWithOperator(andNodes, 'and'));
55
- }
56
-
57
- if (filter.$or) {
58
- const orNodes = filter.$or.map((f: Filter) => this.convertToNode(f));
59
- if (nodes.length > 0) {
60
- nodes.push('and');
61
- }
62
- nodes.push(...this.interleaveWithOperator(orNodes, 'or'));
63
- }
64
-
65
- // Note: $not operator is not currently supported in the legacy FilterNode format
66
- if (filter.$not) {
67
- throw new ObjectQLError({
68
- code: 'UNSUPPORTED_OPERATOR',
69
- message: '$not operator is not supported. Use $ne for field negation instead.'
70
- });
71
- }
72
-
73
- // Process field conditions
74
- for (const [field, value] of Object.entries(filter)) {
75
- if (field.startsWith('$')) {
76
- continue; // Skip logical operators (already processed)
77
- }
78
-
79
- if (nodes.length > 0) {
80
- nodes.push('and');
81
- }
82
-
83
- // Handle field value
84
- if (value === null || value === undefined) {
85
- nodes.push([field, '=', value]);
86
- } else if (typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
87
- // Explicit operators - multiple operators on same field are AND-ed together
88
- const entries = Object.entries(value);
89
- for (let i = 0; i < entries.length; i++) {
90
- const [op, opValue] = entries[i];
91
-
92
- // Add 'and' before each operator (except the very first node)
93
- if (nodes.length > 0 || i > 0) {
94
- nodes.push('and');
95
- }
96
-
97
- const legacyOp = this.mapOperatorToLegacy(op);
98
- nodes.push([field, legacyOp, opValue]);
99
- }
100
- } else {
101
- // Implicit equality
102
- nodes.push([field, '=', value]);
103
- }
104
- }
105
-
106
- // Return as FilterNode (type assertion for backward compatibility)
107
- return (nodes.length === 1 ? nodes[0] : nodes) as unknown as FilterNode;
108
- }
109
-
110
- /**
111
- * Interleaves filter nodes with a logical operator
112
- */
113
- private interleaveWithOperator(nodes: FilterNode[], operator: string): any[] {
114
- if (nodes.length === 0) return [];
115
- if (nodes.length === 1) return [nodes[0]];
116
-
117
- const result: any[] = [nodes[0]];
118
- for (let i = 1; i < nodes.length; i++) {
119
- result.push(operator, nodes[i]);
120
- }
121
- return result;
122
- }
123
-
124
- /**
125
- * Maps modern $-prefixed operators to legacy format
126
- */
127
- private mapOperatorToLegacy(operator: string): string {
128
- const mapping: Record<string, string> = {
129
- '$eq': '=',
130
- '$ne': '!=',
131
- '$gt': '>',
132
- '$gte': '>=',
133
- '$lt': '<',
134
- '$lte': '<=',
135
- '$in': 'in',
136
- '$nin': 'nin',
137
- '$contains': 'contains',
138
- '$startsWith': 'startswith',
139
- '$endsWith': 'endswith',
140
- '$null': 'is_null',
141
- '$exist': 'is_not_null',
142
- '$between': 'between',
143
- };
144
-
145
- return mapping[operator] || operator.replace('$', '');
38
+ // Both ObjectQL Filter and ObjectStack FilterCondition use the same format now
39
+ return filters as unknown as FilterCondition;
146
40
  }
147
41
  }
@@ -7,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import type { UnifiedQuery, ObjectConfig, MetadataRegistry } from '@objectql/types';
10
- import type { QueryAST, FilterNode } from '@objectstack/spec';
10
+ import { Data } from '@objectstack/spec';
11
+ type QueryAST = Data.QueryAST;
11
12
  import { QueryService, QueryOptions } from './query-service';
12
13
 
13
14
  /**
@@ -165,10 +166,10 @@ export class QueryAnalyzer {
165
166
  // Build the QueryAST (without executing)
166
167
  const ast: QueryAST = {
167
168
  object: objectName,
168
- filters: query.filters as any, // FilterCondition is compatible with FilterNode
169
- sort: query.sort as any, // Will be converted to SortNode[] format
170
- top: query.limit, // Changed from limit to top (QueryAST uses 'top')
171
- skip: query.skip,
169
+ where: query.where as any, // FilterCondition format
170
+ orderBy: query.orderBy as any, // Will be converted to SortNode[] format
171
+ limit: query.limit,
172
+ offset: query.offset,
172
173
  fields: query.fields
173
174
  };
174
175
 
@@ -285,7 +286,7 @@ export class QueryAnalyzer {
285
286
  private findApplicableIndexes(schema: ObjectConfig, query: UnifiedQuery): string[] {
286
287
  const indexes: string[] = [];
287
288
 
288
- if (!schema.indexes || !query.filters) {
289
+ if (!schema.indexes || !query.where) {
289
290
  return indexes;
290
291
  }
291
292
 
@@ -314,7 +315,7 @@ export class QueryAnalyzer {
314
315
  }
315
316
  };
316
317
 
317
- extractFieldsFromFilter(query.filters);
318
+ extractFieldsFromFilter(query.where);
318
319
 
319
320
  // Check which indexes could be used
320
321
  const indexesArray = Array.isArray(schema.indexes) ? schema.indexes : Object.values(schema.indexes || {});
@@ -341,7 +342,7 @@ export class QueryAnalyzer {
341
342
  const warnings: string[] = [];
342
343
 
343
344
  // Warning: No filters (full table scan)
344
- if (!query.filters || query.filters.length === 0) {
345
+ if (!query.where || Object.keys(query.where).length === 0) {
345
346
  warnings.push('No filters specified - this will scan all records');
346
347
  }
347
348
 
@@ -359,7 +360,7 @@ export class QueryAnalyzer {
359
360
  }
360
361
 
361
362
  // Warning: Complex filters without indexes
362
- if (query.filters && query.filters.length > 5) {
363
+ if (query.where && Object.keys(query.where).length > 5) {
363
364
  const indexes = this.findApplicableIndexes(schema, query);
364
365
  if (indexes.length === 0) {
365
366
  warnings.push('Complex filters without matching indexes - consider adding indexes');
@@ -386,10 +387,8 @@ export class QueryAnalyzer {
386
387
  }
387
388
 
388
389
  // Suggest adding indexes
389
- if (query.filters && query.filters.length > 0 && indexes.length === 0) {
390
- const filterFields = query.filters
391
- .filter((f: any) => Array.isArray(f) && f.length >= 1)
392
- .map((f: any) => String(f[0]));
390
+ if (query.where && Object.keys(query.where).length > 0 && indexes.length === 0) {
391
+ const filterFields = Object.keys(query.where).filter(k => !k.startsWith('$'));
393
392
 
394
393
  if (filterFields.length > 0) {
395
394
  suggestions.push(`Consider adding an index on: ${filterFields.join(', ')}`);
@@ -402,10 +401,9 @@ export class QueryAnalyzer {
402
401
  }
403
402
 
404
403
  // Suggest composite index for multiple filters
405
- if (query.filters && query.filters.length > 1 && indexes.length < 2) {
406
- const filterFields = query.filters
407
- .filter((f: any) => Array.isArray(f) && f.length >= 1)
408
- .map((f: any) => String(f[0]))
404
+ if (query.where && Object.keys(query.where).length > 1 && indexes.length < 2) {
405
+ const filterFields = Object.keys(query.where)
406
+ .filter(k => !k.startsWith('$'))
409
407
  .slice(0, 3); // Top 3 fields
410
408
 
411
409
  if (filterFields.length > 1) {
@@ -427,21 +425,20 @@ export class QueryAnalyzer {
427
425
  complexity += 10;
428
426
 
429
427
  // Filters add complexity
430
- if (query.filters) {
431
- complexity += query.filters.length * 5;
428
+ if (query.where) {
429
+ const filterCount = Object.keys(query.where).length;
430
+ complexity += filterCount * 5;
432
431
 
433
- // Nested filters (OR conditions) add more
434
- const hasNestedFilters = query.filters.some((f: any) =>
435
- Array.isArray(f) && Array.isArray(f[0])
436
- );
437
- if (hasNestedFilters) {
432
+ // Nested filters (OR/AND conditions) add more
433
+ const hasLogicalOps = query.where.$and || query.where.$or;
434
+ if (hasLogicalOps) {
438
435
  complexity += 15;
439
436
  }
440
437
  }
441
438
 
442
439
  // Sorting adds complexity
443
- if (query.sort && query.sort.length > 0) {
444
- complexity += query.sort.length * 3;
440
+ if (query.orderBy && query.orderBy.length > 0) {
441
+ complexity += query.orderBy.length * 3;
445
442
  }
446
443
 
447
444
  // Field selection reduces complexity slightly
@@ -467,13 +464,14 @@ export class QueryAnalyzer {
467
464
  // from the database (row count, index selectivity, etc.)
468
465
 
469
466
  // Default to unknown
470
- if (!query.filters || query.filters.length === 0) {
467
+ if (!query.where || Object.keys(query.where).length === 0) {
471
468
  return -1; // Unknown, full scan
472
469
  }
473
470
 
474
471
  // Very rough estimate based on filter count
475
472
  const baseEstimate = 1000;
476
- const filterReduction = Math.pow(0.5, query.filters.length);
473
+ const filterCount = Object.keys(query.where).length;
474
+ const filterReduction = Math.pow(0.5, filterCount);
477
475
  const estimated = Math.floor(baseEstimate * filterReduction);
478
476
 
479
477
  // Apply limit if present
@@ -7,14 +7,16 @@
7
7
  */
8
8
 
9
9
  import type { UnifiedQuery } from '@objectql/types';
10
- import type { QueryAST } from '@objectstack/spec';
10
+ import { Data } from '@objectstack/spec';
11
+ type QueryAST = Data.QueryAST;
11
12
  import { FilterTranslator } from './filter-translator';
12
13
 
13
14
  /**
14
15
  * Query Builder
15
16
  *
16
17
  * Builds ObjectStack QueryAST from ObjectQL UnifiedQuery.
17
- * This is the central query construction module for ObjectQL.
18
+ * Since UnifiedQuery now uses the standard protocol format directly,
19
+ * this is now a simple pass-through with object name injection.
18
20
  */
19
21
  export class QueryBuilder {
20
22
  private filterTranslator: FilterTranslator;
@@ -27,52 +29,20 @@ export class QueryBuilder {
27
29
  * Build a QueryAST from a UnifiedQuery
28
30
  *
29
31
  * @param objectName - Target object name
30
- * @param query - ObjectQL UnifiedQuery
32
+ * @param query - ObjectQL UnifiedQuery (now in standard QueryAST format)
31
33
  * @returns ObjectStack QueryAST
32
34
  */
33
35
  build(objectName: string, query: UnifiedQuery): QueryAST {
36
+ // UnifiedQuery now uses the same format as QueryAST
37
+ // Just add the object name and pass through
34
38
  const ast: QueryAST = {
35
39
  object: objectName,
40
+ ...query
36
41
  };
37
42
 
38
- // Map fields
39
- if (query.fields) {
40
- ast.fields = query.fields;
41
- }
42
-
43
- // Map filters using FilterTranslator
44
- if (query.filters) {
45
- ast.filters = this.filterTranslator.translate(query.filters);
46
- }
47
-
48
- // Map sort
49
- if (query.sort) {
50
- ast.sort = query.sort.map(([field, order]) => ({
51
- field,
52
- order: order as 'asc' | 'desc'
53
- }));
54
- }
55
-
56
- // Map pagination
57
- if (query.limit !== undefined) {
58
- ast.top = query.limit;
59
- }
60
- if (query.skip !== undefined) {
61
- ast.skip = query.skip;
62
- }
63
-
64
- // Map groupBy
65
- if (query.groupBy) {
66
- ast.groupBy = query.groupBy;
67
- }
68
-
69
- // Map aggregations
70
- if (query.aggregate) {
71
- ast.aggregations = query.aggregate.map(agg => ({
72
- function: agg.func as any,
73
- field: agg.field,
74
- alias: agg.alias || `${agg.func}_${agg.field}`
75
- }));
43
+ // Ensure where is properly formatted
44
+ if (query.where) {
45
+ ast.where = this.filterTranslator.translate(query.where);
76
46
  }
77
47
 
78
48
  return ast;
@@ -13,7 +13,8 @@ import type {
13
13
  Filter,
14
14
  MetadataRegistry
15
15
  } from '@objectql/types';
16
- import type { QueryAST } from '@objectstack/spec';
16
+ import { Data } from '@objectstack/spec';
17
+ type QueryAST = Data.QueryAST;
17
18
  import { QueryBuilder } from './query-builder';
18
19
 
19
20
  /**
@@ -231,7 +232,7 @@ export class QueryService {
231
232
  } else if (driver.executeQuery) {
232
233
  // Fallback to query with ID filter
233
234
  const query: UnifiedQuery = {
234
- filters: [['_id', '=', id]]
235
+ where: { _id: id }
235
236
  };
236
237
  const ast = this.buildQueryAST(objectName, query);
237
238
  const queryResult = await driver.executeQuery(ast, driverOptions);
@@ -255,19 +256,19 @@ export class QueryService {
255
256
  * Execute a count query
256
257
  *
257
258
  * @param objectName - The object to query
258
- * @param filters - Optional filters
259
+ * @param where - Optional filter condition
259
260
  * @param options - Query execution options
260
261
  * @returns Count of matching records
261
262
  */
262
263
  async count(
263
264
  objectName: string,
264
- filters?: Filter[],
265
+ where?: Filter,
265
266
  options: QueryOptions = {}
266
267
  ): Promise<QueryResult<number>> {
267
268
  const driver = this.getDriver(objectName);
268
269
  const startTime = options.profile ? Date.now() : 0;
269
270
 
270
- const query: UnifiedQuery = filters ? { filters } : {};
271
+ const query: UnifiedQuery = where ? { where } : {};
271
272
  const ast = this.buildQueryAST(objectName, query);
272
273
 
273
274
  const driverOptions = {
@@ -279,7 +280,7 @@ export class QueryService {
279
280
 
280
281
  if (driver.count) {
281
282
  // Legacy driver interface
282
- count = await driver.count(objectName, filters || [], driverOptions);
283
+ count = await driver.count(objectName, where || {}, driverOptions);
283
284
  } else if (driver.executeQuery) {
284
285
  // Use executeQuery and count results
285
286
  // Note: This is inefficient for large datasets
package/src/repository.ts CHANGED
@@ -7,8 +7,10 @@
7
7
  */
8
8
 
9
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 '@objectstack/runtime';
11
- import type { QueryAST, FilterNode, SortNode } from '@objectstack/spec';
10
+ import type { ObjectStackKernel } from '@objectql/runtime';
11
+ import { Data } from '@objectstack/spec';
12
+ type QueryAST = Data.QueryAST;
13
+ type SortNode = Data.SortNode;
12
14
  import { Validator } from './validator';
13
15
  import { FormulaEngine } from './formula-engine';
14
16
  import { QueryBuilder } from './query';
@@ -222,9 +224,11 @@ export class ObjectRepository {
222
224
 
223
225
  // Evaluate each formula field
224
226
  for (const [fieldName, fieldConfig] of Object.entries(schema.fields)) {
225
- if (fieldConfig.type === 'formula' && fieldConfig.formula) {
227
+ const formulaExpression = fieldConfig.expression;
228
+
229
+ if (fieldConfig.type === 'formula' && formulaExpression) {
226
230
  const result = this.getFormulaEngine().evaluate(
227
- fieldConfig.formula,
231
+ formulaExpression,
228
232
  formulaContext,
229
233
  fieldConfig.data_type || 'text',
230
234
  { strict: true }
@@ -244,7 +248,7 @@ export class ObjectRepository {
244
248
  objectName: this.objectName,
245
249
  fieldName,
246
250
  recordId: formulaContext.record_id,
247
- formula: fieldConfig.formula,
251
+ expression: formulaExpression,
248
252
  error: result.error,
249
253
  stack: result.stack,
250
254
  }
@@ -311,6 +315,22 @@ export class ObjectRepository {
311
315
  }
312
316
 
313
317
  async count(filters: any): Promise<number> {
318
+ // Normalize filters to UnifiedQuery format
319
+ // If filters is an array, wrap it in a query object
320
+ // If filters is already a UnifiedQuery (has UnifiedQuery-specific properties), use it as-is
321
+ let query: UnifiedQuery;
322
+ if (Array.isArray(filters)) {
323
+ query = { where: filters };
324
+ } else if (filters && typeof filters === 'object' && (filters.fields || filters.orderBy || filters.limit !== undefined || filters.offset !== undefined)) {
325
+ // It's already a UnifiedQuery object
326
+ query = filters;
327
+ } else if (filters) {
328
+ // It's a raw filter object, wrap it
329
+ query = { where: filters };
330
+ } else {
331
+ query = {};
332
+ }
333
+
314
334
  const hookCtx: RetrievalHookContext = {
315
335
  ...this.context,
316
336
  objectName: this.objectName,
@@ -318,7 +338,7 @@ export class ObjectRepository {
318
338
  api: this.getHookAPI(),
319
339
  user: this.getUserFromContext(),
320
340
  state: {},
321
- query: filters
341
+ query
322
342
  };
323
343
  await this.app.triggerHook('beforeCount', this.objectName, hookCtx);
324
344
 
@@ -444,7 +464,7 @@ export class ObjectRepository {
444
464
  async updateMany(filters: any, data: any): Promise<any> {
445
465
  // Find all matching records and update them individually
446
466
  // to ensure validation and hooks are executed
447
- const records = await this.find({ filters });
467
+ const records = await this.find({ where: filters });
448
468
  let count = 0;
449
469
  for (const record of records) {
450
470
  if (record && record._id) {
@@ -458,7 +478,7 @@ export class ObjectRepository {
458
478
  async deleteMany(filters: any): Promise<any> {
459
479
  // Find all matching records and delete them individually
460
480
  // to ensure hooks are executed
461
- const records = await this.find({ filters });
481
+ const records = await this.find({ where: filters });
462
482
  let count = 0;
463
483
  for (const record of records) {
464
484
  if (record && record._id) {
@@ -6,7 +6,7 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
 
9
- import type { RuntimePlugin, RuntimeContext, ObjectStackKernel } from '@objectstack/runtime';
9
+ import type { RuntimePlugin, RuntimeContext, ObjectStackKernel } from '@objectql/runtime';
10
10
  import { Validator, ValidatorOptions } from './validator';
11
11
 
12
12
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Mock for @objectstack/runtime
2
+ * Mock for @objectql/runtime
3
3
  * This mock is needed because the npm package has issues with Jest
4
4
  * and we want to focus on testing ObjectQL's logic, not the kernel integration.
5
5
  *
package/test/app.test.ts CHANGED
@@ -9,7 +9,8 @@
9
9
  import { ObjectQL } from '../src/app';
10
10
  import { MockDriver } from './mock-driver';
11
11
  import { ObjectConfig, HookContext, ActionContext, Metadata } from '@objectql/types';
12
- import type { PluginDefinition } from '@objectstack/spec';
12
+ import type { Kernel } from '@objectstack/spec';
13
+ type PluginDefinition = Kernel.PluginDefinition;
13
14
 
14
15
  const todoObject: ObjectConfig = {
15
16
  name: 'todo',
@@ -42,7 +42,7 @@ describe('Formula Integration', () => {
42
42
  },
43
43
  full_name: {
44
44
  type: 'formula',
45
- formula: 'first_name + " " + last_name',
45
+ expression: 'first_name + " " + last_name',
46
46
  data_type: 'text',
47
47
  label: 'Full Name',
48
48
  },
@@ -54,7 +54,7 @@ describe('Formula Integration', () => {
54
54
  },
55
55
  total: {
56
56
  type: 'formula',
57
- formula: 'quantity * unit_price',
57
+ expression: 'quantity * unit_price',
58
58
  data_type: 'currency',
59
59
  label: 'Total',
60
60
  },
@@ -63,7 +63,7 @@ describe('Formula Integration', () => {
63
63
  },
64
64
  status_label: {
65
65
  type: 'formula',
66
- formula: 'is_active ? "Active" : "Inactive"',
66
+ expression: 'is_active ? "Active" : "Inactive"',
67
67
  data_type: 'text',
68
68
  label: 'Status',
69
69
  },
@@ -165,7 +165,7 @@ describe('Formula Integration', () => {
165
165
  tax_rate: { type: 'percent' },
166
166
  final_price: {
167
167
  type: 'formula',
168
- formula: 'subtotal * (1 - discount_rate / 100) * (1 + tax_rate / 100)',
168
+ expression: 'subtotal * (1 - discount_rate / 100) * (1 + tax_rate / 100)',
169
169
  data_type: 'currency',
170
170
  label: 'Final Price',
171
171
  },
@@ -173,7 +173,7 @@ describe('Formula Integration', () => {
173
173
  status: { type: 'select', options: ['draft', 'confirmed', 'shipped'] },
174
174
  risk_level: {
175
175
  type: 'formula',
176
- formula: `
176
+ expression: `
177
177
  if (subtotal > 10000) {
178
178
  return 'High';
179
179
  } else if (subtotal > 1000) {
@@ -256,7 +256,7 @@ describe('Formula Integration', () => {
256
256
  price: { type: 'currency' },
257
257
  invalid_formula: {
258
258
  type: 'formula',
259
- formula: 'nonexistent_field * 2',
259
+ expression: 'nonexistent_field * 2',
260
260
  data_type: 'number',
261
261
  },
262
262
  },
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { FormulaPlugin } from '../src/formula-plugin';
10
- import { ObjectStackKernel } from '@objectstack/runtime';
10
+ import { ObjectStackKernel } from '@objectql/runtime';
11
11
 
12
12
  describe('FormulaPlugin', () => {
13
13
  let plugin: FormulaPlugin;