@objectstack/objectql 3.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/objectql",
3
- "version": "3.3.1",
3
+ "version": "4.0.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Isomorphic ObjectQL Engine for ObjectStack",
6
6
  "main": "dist/index.js",
@@ -13,13 +13,13 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "@objectstack/core": "3.3.1",
17
- "@objectstack/spec": "3.3.1",
18
- "@objectstack/types": "3.3.1"
16
+ "@objectstack/core": "4.0.1",
17
+ "@objectstack/spec": "4.0.1",
18
+ "@objectstack/types": "4.0.1"
19
19
  },
20
20
  "devDependencies": {
21
- "typescript": "^5.0.0",
22
- "vitest": "^4.1.0"
21
+ "typescript": "^6.0.2",
22
+ "vitest": "^4.1.2"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup --config ../../tsup.config.ts",
@@ -248,7 +248,7 @@ describe('ObjectQL Engine', () => {
248
248
  { id: 'u2', name: 'Bob' },
249
249
  ]);
250
250
 
251
- const result = await engine.find('task', { populate: ['assignee'] });
251
+ const result = await engine.find('task', { expand: { assignee: { object: 'assignee' } } });
252
252
 
253
253
  expect(result).toHaveLength(2);
254
254
  expect(result[0].assignee).toEqual({ id: 'u1', name: 'Alice' });
@@ -288,7 +288,7 @@ describe('ObjectQL Engine', () => {
288
288
  { id: 'o1', total: 100 },
289
289
  ]);
290
290
 
291
- const result = await engine.find('order_item', { populate: ['order'] });
291
+ const result = await engine.find('order_item', { expand: { order: { object: 'order' } } });
292
292
  expect(result[0].order).toEqual({ id: 'o1', total: 100 });
293
293
  });
294
294
 
@@ -304,7 +304,7 @@ describe('ObjectQL Engine', () => {
304
304
  { id: 't1', title: 'Task 1' },
305
305
  ]);
306
306
 
307
- const result = await engine.find('task', { populate: ['title'] });
307
+ const result = await engine.find('task', { expand: { title: { object: 'title' } } });
308
308
  expect(result[0].title).toBe('Task 1'); // Unchanged
309
309
  expect(mockDriver.find).toHaveBeenCalledTimes(1); // No expand query
310
310
  });
@@ -316,7 +316,7 @@ describe('ObjectQL Engine', () => {
316
316
  { id: 't1', assignee: 'u1' },
317
317
  ]);
318
318
 
319
- const result = await engine.find('task', { populate: ['assignee'] });
319
+ const result = await engine.find('task', { expand: { assignee: { object: 'assignee' } } });
320
320
  expect(result[0].assignee).toBe('u1'); // Unchanged — raw ID
321
321
  expect(mockDriver.find).toHaveBeenCalledTimes(1);
322
322
  });
@@ -345,7 +345,7 @@ describe('ObjectQL Engine', () => {
345
345
  { id: 'u1', name: 'Alice' },
346
346
  ]);
347
347
 
348
- const result = await engine.find('task', { populate: ['assignee'] });
348
+ const result = await engine.find('task', { expand: { assignee: { object: 'assignee' } } });
349
349
  expect(result[0].assignee).toBeNull();
350
350
  expect(result[1].assignee).toEqual({ id: 'u1', name: 'Alice' });
351
351
  });
@@ -376,7 +376,7 @@ describe('ObjectQL Engine', () => {
376
376
  { id: 'u2', name: 'Bob' },
377
377
  ]);
378
378
 
379
- const result = await engine.find('task', { populate: ['assignee'] });
379
+ const result = await engine.find('task', { expand: { assignee: { object: 'assignee' } } });
380
380
 
381
381
  // Verify only 2 unique IDs queried
382
382
  expect(mockDriver.find).toHaveBeenLastCalledWith(
@@ -410,7 +410,7 @@ describe('ObjectQL Engine', () => {
410
410
  ])
411
411
  .mockResolvedValueOnce([]); // No records found
412
412
 
413
- const result = await engine.find('task', { populate: ['assignee'] });
413
+ const result = await engine.find('task', { expand: { assignee: { object: 'assignee' } } });
414
414
  expect(result[0].assignee).toBe('u_deleted'); // Fallback to raw ID
415
415
  });
416
416
 
@@ -441,7 +441,7 @@ describe('ObjectQL Engine', () => {
441
441
  .mockResolvedValueOnce([{ id: 'u1', name: 'Alice' }])
442
442
  .mockResolvedValueOnce([{ id: 'p1', name: 'Project X' }]);
443
443
 
444
- const result = await engine.find('task', { populate: ['assignee', 'project'] });
444
+ const result = await engine.find('task', { expand: { assignee: { object: 'assignee' }, project: { object: 'project' } } });
445
445
 
446
446
  expect(result[0].assignee).toEqual({ id: 'u1', name: 'Alice' });
447
447
  expect(result[0].project).toEqual({ id: 'p1', name: 'Project X' });
@@ -470,7 +470,7 @@ describe('ObjectQL Engine', () => {
470
470
  { id: 'u1', name: 'Alice' },
471
471
  ]);
472
472
 
473
- const result = await engine.findOne('task', { populate: ['assignee'] });
473
+ const result = await engine.findOne('task', { expand: { assignee: { object: 'assignee' } } });
474
474
 
475
475
  expect(result.assignee).toEqual({ id: 'u1', name: 'Alice' });
476
476
  });
@@ -495,7 +495,7 @@ describe('ObjectQL Engine', () => {
495
495
  { id: 't1', assignee: { id: 'u1', name: 'Alice' } },
496
496
  ]);
497
497
 
498
- const result = await engine.find('task', { populate: ['assignee'] });
498
+ const result = await engine.find('task', { expand: { assignee: { object: 'assignee' } } });
499
499
 
500
500
  // No expand query should have been made — the value was already an object
501
501
  expect(mockDriver.find).toHaveBeenCalledTimes(1);
@@ -523,7 +523,7 @@ describe('ObjectQL Engine', () => {
523
523
  ])
524
524
  .mockRejectedValueOnce(new Error('Driver connection failed'));
525
525
 
526
- const result = await engine.find('task', { populate: ['assignee'] });
526
+ const result = await engine.find('task', { expand: { assignee: { object: 'assignee' } } });
527
527
  expect(result[0].assignee).toBe('u1'); // Kept raw ID
528
528
  });
529
529
 
@@ -551,7 +551,7 @@ describe('ObjectQL Engine', () => {
551
551
  { id: 'u2', name: 'Bob' },
552
552
  ]);
553
553
 
554
- const result = await engine.find('task', { populate: ['watchers'] });
554
+ const result = await engine.find('task', { expand: { watchers: { object: 'watchers' } } });
555
555
  expect(result[0].watchers).toEqual([
556
556
  { id: 'u1', name: 'Alice' },
557
557
  { id: 'u2', name: 'Bob' },
@@ -574,7 +574,7 @@ describe('ObjectQL Engine', () => {
574
574
  .mockResolvedValueOnce([{ id: 'p1', org: 'o1' }]); // expand project (depth 0)
575
575
  // org should NOT be expanded further — flat populate doesn't create nested expand
576
576
 
577
- const result = await engine.find('task', { populate: ['project'] });
577
+ const result = await engine.find('task', { expand: { project: { object: 'project' } } });
578
578
 
579
579
  // Project expanded, but org inside project remains as raw ID
580
580
  expect(result[0].project).toEqual({ id: 'p1', org: 'o1' });
package/src/engine.ts CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  import { QueryAST, HookContext, ServiceObject } from '@objectstack/spec/data';
4
4
  import {
5
- DataEngineQueryOptions,
5
+ EngineQueryOptions,
6
6
  DataEngineInsertOptions,
7
- DataEngineUpdateOptions,
8
- DataEngineDeleteOptions,
9
- DataEngineAggregateOptions,
10
- DataEngineCountOptions
7
+ EngineUpdateOptions,
8
+ EngineDeleteOptions,
9
+ EngineAggregateOptions,
10
+ EngineCountOptions
11
11
  } from '@objectstack/spec/data';
12
12
  import { ExecutionContext, ExecutionContextSchema } from '@objectstack/spec/kernel';
13
13
  import { DriverInterface, IDataEngine, Logger, createLogger } from '@objectstack/core';
@@ -741,59 +741,22 @@ export class ObjectQL implements IDataEngine {
741
741
  return records;
742
742
  }
743
743
 
744
- // ============================================
745
- // Helper: Query Conversion
746
- // ============================================
747
-
748
- private toQueryAST(object: string, options?: DataEngineQueryOptions): QueryAST {
749
- const ast: QueryAST = { object };
750
- if (!options) return ast;
751
-
752
- if (options.filter) {
753
- ast.where = options.filter;
754
- }
755
- if (options.select) {
756
- ast.fields = options.select;
757
- }
758
- if (options.sort) {
759
- // Support DataEngineSortSchema variant
760
- if (Array.isArray(options.sort)) {
761
- // [{ field: 'a', order: 'asc' }]
762
- ast.orderBy = options.sort;
763
- } else {
764
- // Record<string, 'asc' | 'desc' | 1 | -1>
765
- ast.orderBy = Object.entries(options.sort).map(([field, order]) => ({
766
- field,
767
- order: (order === -1 || order === 'desc') ? 'desc' : 'asc'
768
- }));
769
- }
770
- }
771
-
772
- if (options.top !== undefined) ast.limit = options.top;
773
- else if (options.limit !== undefined) ast.limit = options.limit;
774
-
775
- if (options.skip !== undefined) ast.offset = options.skip;
776
-
777
- // Map populate (relationship field names) to QueryAST expand entries
778
- if (options.populate && options.populate.length > 0) {
779
- ast.expand = {};
780
- for (const rel of options.populate) {
781
- ast.expand[rel] = { object: rel };
782
- }
783
- }
784
-
785
- return ast;
786
- }
787
-
788
744
  // ============================================
789
745
  // Data Access Methods (IDataEngine Interface)
790
746
  // ============================================
791
747
 
792
- async find(object: string, query?: DataEngineQueryOptions): Promise<any[]> {
748
+ async find(object: string, query?: EngineQueryOptions): Promise<any[]> {
793
749
  object = this.resolveObjectName(object);
794
750
  this.logger.debug('Find operation starting', { object, query });
795
751
  const driver = this.getDriver(object);
796
- const ast = this.toQueryAST(object, query);
752
+ const ast: QueryAST = { object, ...query };
753
+ // Remove context from the AST — it's not a driver concern
754
+ delete (ast as any).context;
755
+ // Normalize OData `top` alias → standard `limit`
756
+ if ((ast as any).top != null && ast.limit == null) {
757
+ ast.limit = (ast as any).top;
758
+ }
759
+ delete (ast as any).top;
797
760
 
798
761
  const opCtx: OperationContext = {
799
762
  object,
@@ -836,12 +799,14 @@ export class ObjectQL implements IDataEngine {
836
799
  return opCtx.result as any[];
837
800
  }
838
801
 
839
- async findOne(objectName: string, query?: DataEngineQueryOptions): Promise<any> {
802
+ async findOne(objectName: string, query?: EngineQueryOptions): Promise<any> {
840
803
  objectName = this.resolveObjectName(objectName);
841
804
  this.logger.debug('FindOne operation', { objectName });
842
805
  const driver = this.getDriver(objectName);
843
- const ast = this.toQueryAST(objectName, query);
844
- ast.limit = 1;
806
+ const ast: QueryAST = { object: objectName, ...query, limit: 1 };
807
+ // Remove context and top alias from the AST
808
+ delete (ast as any).context;
809
+ delete (ast as any).top;
845
810
 
846
811
  const opCtx: OperationContext = {
847
812
  object: objectName,
@@ -918,16 +883,15 @@ export class ObjectQL implements IDataEngine {
918
883
  return opCtx.result;
919
884
  }
920
885
 
921
- async update(object: string, data: any, options?: DataEngineUpdateOptions): Promise<any> {
886
+ async update(object: string, data: any, options?: EngineUpdateOptions): Promise<any> {
922
887
  object = this.resolveObjectName(object);
923
888
  this.logger.debug('Update operation starting', { object });
924
889
  const driver = this.getDriver(object);
925
890
 
926
- // 1. Extract ID from data or filter if it's a single update by ID
891
+ // 1. Extract ID from data or where if it's a single update by ID
927
892
  let id = data.id;
928
- if (!id && options?.filter) {
929
- if (typeof options.filter === 'string') id = options.filter;
930
- else if (options.filter.id) id = options.filter.id;
893
+ if (!id && options?.where && typeof options.where === 'object' && 'id' in options.where) {
894
+ id = (options.where as Record<string, unknown>).id;
931
895
  }
932
896
 
933
897
  const opCtx: OperationContext = {
@@ -954,7 +918,7 @@ export class ObjectQL implements IDataEngine {
954
918
  if (hookContext.input.id) {
955
919
  result = await driver.update(object, hookContext.input.id as string, hookContext.input.data as Record<string, unknown>, hookContext.input.options as any);
956
920
  } else if (options?.multi && driver.updateMany) {
957
- const ast = this.toQueryAST(object, { filter: options.filter });
921
+ const ast: QueryAST = { object, where: options.where };
958
922
  result = await driver.updateMany(object, ast, hookContext.input.data as Record<string, unknown>, hookContext.input.options as any);
959
923
  } else {
960
924
  throw new Error('Update requires an ID or options.multi=true');
@@ -973,16 +937,15 @@ export class ObjectQL implements IDataEngine {
973
937
  return opCtx.result;
974
938
  }
975
939
 
976
- async delete(object: string, options?: DataEngineDeleteOptions): Promise<any> {
940
+ async delete(object: string, options?: EngineDeleteOptions): Promise<any> {
977
941
  object = this.resolveObjectName(object);
978
942
  this.logger.debug('Delete operation starting', { object });
979
943
  const driver = this.getDriver(object);
980
944
 
981
945
  // Extract ID logic similar to update
982
946
  let id: any = undefined;
983
- if (options?.filter) {
984
- if (typeof options.filter === 'string') id = options.filter;
985
- else if (options.filter.id) id = options.filter.id;
947
+ if (options?.where && typeof options.where === 'object' && 'id' in options.where) {
948
+ id = (options.where as Record<string, unknown>).id;
986
949
  }
987
950
 
988
951
  const opCtx: OperationContext = {
@@ -1008,7 +971,7 @@ export class ObjectQL implements IDataEngine {
1008
971
  if (hookContext.input.id) {
1009
972
  result = await driver.delete(object, hookContext.input.id as string, hookContext.input.options as any);
1010
973
  } else if (options?.multi && driver.deleteMany) {
1011
- const ast = this.toQueryAST(object, { filter: options.filter });
974
+ const ast: QueryAST = { object, where: options.where };
1012
975
  result = await driver.deleteMany(object, ast, hookContext.input.options as any);
1013
976
  } else {
1014
977
  throw new Error('Delete requires an ID or options.multi=true');
@@ -1027,7 +990,7 @@ export class ObjectQL implements IDataEngine {
1027
990
  return opCtx.result;
1028
991
  }
1029
992
 
1030
- async count(object: string, query?: DataEngineCountOptions): Promise<number> {
993
+ async count(object: string, query?: EngineCountOptions): Promise<number> {
1031
994
  object = this.resolveObjectName(object);
1032
995
  const driver = this.getDriver(object);
1033
996
 
@@ -1040,18 +1003,18 @@ export class ObjectQL implements IDataEngine {
1040
1003
 
1041
1004
  await this.executeWithMiddleware(opCtx, async () => {
1042
1005
  if (driver.count) {
1043
- const ast = this.toQueryAST(object, { filter: query?.filter });
1006
+ const ast: QueryAST = { object, where: query?.where };
1044
1007
  return driver.count(object, ast);
1045
1008
  }
1046
1009
  // Fallback to find().length
1047
- const res = await this.find(object, { filter: query?.filter, select: ['id'] });
1010
+ const res = await this.find(object, { where: query?.where, fields: ['id'] });
1048
1011
  return res.length;
1049
1012
  });
1050
1013
 
1051
1014
  return opCtx.result as number;
1052
1015
  }
1053
1016
 
1054
- async aggregate(object: string, query: DataEngineAggregateOptions): Promise<any[]> {
1017
+ async aggregate(object: string, query: EngineAggregateOptions): Promise<any[]> {
1055
1018
  object = this.resolveObjectName(object);
1056
1019
  const driver = this.getDriver(object);
1057
1020
  this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
@@ -1066,13 +1029,9 @@ export class ObjectQL implements IDataEngine {
1066
1029
  await this.executeWithMiddleware(opCtx, async () => {
1067
1030
  const ast: QueryAST = {
1068
1031
  object,
1069
- where: query.filter,
1032
+ where: query.where,
1070
1033
  groupBy: query.groupBy,
1071
- aggregations: query.aggregations?.map(agg => ({
1072
- function: agg.method,
1073
- field: agg.field,
1074
- alias: agg.alias || `${agg.method}_${agg.field || 'all'}`,
1075
- })),
1034
+ aggregations: query.aggregations,
1076
1035
  };
1077
1036
 
1078
1037
  return driver.find(object, ast);
@@ -1355,7 +1314,7 @@ export class ObjectRepository {
1355
1314
  /** Update a single record by ID */
1356
1315
  async updateById(id: string | number, data: any): Promise<any> {
1357
1316
  return this.engine.update(this.objectName, { ...data, id: id }, {
1358
- filter: { id: id },
1317
+ where: { id: id },
1359
1318
  context: this.context,
1360
1319
  });
1361
1320
  }
@@ -1370,7 +1329,7 @@ export class ObjectRepository {
1370
1329
  /** Delete a single record by ID */
1371
1330
  async deleteById(id: string | number): Promise<any> {
1372
1331
  return this.engine.delete(this.objectName, {
1373
- filter: { id: id },
1332
+ where: { id: id },
1374
1333
  context: this.context,
1375
1334
  });
1376
1335
  }
package/src/plugin.ts CHANGED
@@ -175,7 +175,7 @@ export class ObjectQLPlugin implements Plugin {
175
175
  if (hookCtx.input?.id && !hookCtx.previous) {
176
176
  try {
177
177
  const existing = await this.ql!.findOne(hookCtx.object, {
178
- filter: { id: hookCtx.input.id }
178
+ where: { id: hookCtx.input.id }
179
179
  });
180
180
  if (existing) {
181
181
  hookCtx.previous = existing;
@@ -191,7 +191,7 @@ export class ObjectQLPlugin implements Plugin {
191
191
  if (hookCtx.input?.id && !hookCtx.previous) {
192
192
  try {
193
193
  const existing = await this.ql!.findOne(hookCtx.object, {
194
- filter: { id: hookCtx.input.id }
194
+ where: { id: hookCtx.input.id }
195
195
  });
196
196
  if (existing) {
197
197
  hookCtx.previous = existing;
@@ -25,87 +25,88 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
25
25
  // ═══════════════════════════════════════════════════════════════
26
26
 
27
27
  describe('findData', () => {
28
- it('should normalize expand string to populate array', async () => {
29
- await protocol.findData({ object: 'order_item', query: { expand: 'order,product' } });
28
+ it('should normalize $expand (OData) string to expand Record', async () => {
29
+ await protocol.findData({ object: 'order_item', query: { $expand: 'order,product' } });
30
30
 
31
31
  expect(mockEngine.find).toHaveBeenCalledWith(
32
32
  'order_item',
33
33
  expect.objectContaining({
34
- populate: ['order', 'product'],
34
+ expand: { order: { object: 'order' }, product: { object: 'product' } },
35
35
  }),
36
36
  );
37
- // expand should be deleted from options
37
+ // $expand should be deleted from options
38
38
  const callArgs = mockEngine.find.mock.calls[0][1];
39
- expect(callArgs.expand).toBeUndefined();
40
39
  expect(callArgs.$expand).toBeUndefined();
41
40
  });
42
41
 
43
- it('should normalize $expand (OData) to populate array', async () => {
42
+ it('should normalize $expand (OData) with different fields to expand Record', async () => {
44
43
  await protocol.findData({ object: 'task', query: { $expand: 'assignee,project' } });
45
44
 
46
45
  expect(mockEngine.find).toHaveBeenCalledWith(
47
46
  'task',
48
47
  expect.objectContaining({
49
- populate: ['assignee', 'project'],
48
+ expand: { assignee: { object: 'assignee' }, project: { object: 'project' } },
50
49
  }),
51
50
  );
52
51
  });
53
52
 
54
- it('should pass populate array as-is if already an array', async () => {
53
+ it('should normalize populate array to expand Record', async () => {
55
54
  await protocol.findData({ object: 'task', query: { populate: ['assignee'] } });
56
55
 
57
56
  expect(mockEngine.find).toHaveBeenCalledWith(
58
57
  'task',
59
58
  expect.objectContaining({
60
- populate: ['assignee'],
59
+ expand: { assignee: { object: 'assignee' } },
61
60
  }),
62
61
  );
63
62
  });
64
63
 
65
- it('should normalize populate string to array', async () => {
64
+ it('should normalize populate string to expand Record', async () => {
66
65
  await protocol.findData({ object: 'task', query: { populate: 'assignee,project' } });
67
66
 
68
67
  expect(mockEngine.find).toHaveBeenCalledWith(
69
68
  'task',
70
69
  expect.objectContaining({
71
- populate: ['assignee', 'project'],
70
+ expand: { assignee: { object: 'assignee' }, project: { object: 'project' } },
72
71
  }),
73
72
  );
74
73
  });
75
74
 
76
- it('should prefer explicit populate over expand', async () => {
75
+ it('should prefer populate names over expand string when both provided', async () => {
77
76
  await protocol.findData({
78
77
  object: 'task',
79
78
  query: { populate: ['assignee'], expand: 'project' },
80
79
  });
81
80
 
82
- // populate takes precedence; expand is not converted
83
- expect(mockEngine.find).toHaveBeenCalledWith(
84
- 'task',
85
- expect.objectContaining({
86
- populate: ['assignee'],
87
- }),
88
- );
81
+ // populate names take precedence; the non-object expand string is
82
+ // cleaned up first, then populate-derived names create the Record.
83
+ const callArgs = mockEngine.find.mock.calls[0][1];
84
+ expect(callArgs.populate).toBeUndefined();
85
+ expect(callArgs.$expand).toBeUndefined();
86
+ expect(callArgs.expand).toEqual({ assignee: { object: 'assignee' } });
89
87
  });
90
88
 
91
- it('should normalize expand array to populate array', async () => {
92
- await protocol.findData({ object: 'task', query: { expand: ['owner', 'team'] } });
89
+ it('should pass expand Record object through as-is', async () => {
90
+ await protocol.findData({
91
+ object: 'task',
92
+ query: { expand: { owner: { object: 'owner' }, team: { object: 'team' } } },
93
+ });
93
94
 
94
95
  expect(mockEngine.find).toHaveBeenCalledWith(
95
96
  'task',
96
97
  expect.objectContaining({
97
- populate: ['owner', 'team'],
98
+ expand: { owner: { object: 'owner' }, team: { object: 'team' } },
98
99
  }),
99
100
  );
100
101
  });
101
102
 
102
- it('should normalize select string to array', async () => {
103
+ it('should normalize select string to fields array', async () => {
103
104
  await protocol.findData({ object: 'task', query: { select: 'name,status,assignee' } });
104
105
 
105
106
  expect(mockEngine.find).toHaveBeenCalledWith(
106
107
  'task',
107
108
  expect.objectContaining({
108
- select: ['name', 'status', 'assignee'],
109
+ fields: ['name', 'status', 'assignee'],
109
110
  }),
110
111
  );
111
112
  });
@@ -116,8 +117,8 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
116
117
  expect(mockEngine.find).toHaveBeenCalledWith(
117
118
  'task',
118
119
  expect.objectContaining({
119
- top: 10,
120
- skip: 20,
120
+ limit: 10,
121
+ offset: 20,
121
122
  }),
122
123
  );
123
124
  });
@@ -148,7 +149,7 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
148
149
  // ═══════════════════════════════════════════════════════════════
149
150
 
150
151
  describe('getData', () => {
151
- it('should convert expand string to populate array', async () => {
152
+ it('should convert expand string to expand Record', async () => {
152
153
  mockEngine.findOne.mockResolvedValue({ id: 'oi_1', name: 'Item 1' });
153
154
 
154
155
  await protocol.getData({ object: 'order_item', id: 'oi_1', expand: 'order,product' });
@@ -156,13 +157,13 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
156
157
  expect(mockEngine.findOne).toHaveBeenCalledWith(
157
158
  'order_item',
158
159
  expect.objectContaining({
159
- filter: { id: 'oi_1' },
160
- populate: ['order', 'product'],
160
+ where: { id: 'oi_1' },
161
+ expand: { order: { object: 'order' }, product: { object: 'product' } },
161
162
  }),
162
163
  );
163
164
  });
164
165
 
165
- it('should convert expand array to populate array', async () => {
166
+ it('should convert expand array to expand Record', async () => {
166
167
  mockEngine.findOne.mockResolvedValue({ id: 't1' });
167
168
 
168
169
  await protocol.getData({ object: 'task', id: 't1', expand: ['assignee', 'project'] });
@@ -170,12 +171,13 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
170
171
  expect(mockEngine.findOne).toHaveBeenCalledWith(
171
172
  'task',
172
173
  expect.objectContaining({
173
- populate: ['assignee', 'project'],
174
+ where: { id: 't1' },
175
+ expand: { assignee: { object: 'assignee' }, project: { object: 'project' } },
174
176
  }),
175
177
  );
176
178
  });
177
179
 
178
- it('should convert select string to array', async () => {
180
+ it('should convert select string to fields array', async () => {
179
181
  mockEngine.findOne.mockResolvedValue({ id: 't1', name: 'Test' });
180
182
 
181
183
  await protocol.getData({ object: 'task', id: 't1', select: 'name,status' });
@@ -183,12 +185,13 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
183
185
  expect(mockEngine.findOne).toHaveBeenCalledWith(
184
186
  'task',
185
187
  expect.objectContaining({
186
- select: ['name', 'status'],
188
+ where: { id: 't1' },
189
+ fields: ['name', 'status'],
187
190
  }),
188
191
  );
189
192
  });
190
193
 
191
- it('should pass both expand and select together', async () => {
194
+ it('should pass both expand and fields together', async () => {
192
195
  mockEngine.findOne.mockResolvedValue({ id: 'oi_1' });
193
196
 
194
197
  await protocol.getData({
@@ -201,9 +204,9 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
201
204
  expect(mockEngine.findOne).toHaveBeenCalledWith(
202
205
  'order_item',
203
206
  expect.objectContaining({
204
- filter: { id: 'oi_1' },
205
- populate: ['order'],
206
- select: ['name', 'total'],
207
+ where: { id: 'oi_1' },
208
+ expand: { order: { object: 'order' } },
209
+ fields: ['name', 'total'],
207
210
  }),
208
211
  );
209
212
  });
@@ -215,7 +218,7 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
215
218
 
216
219
  expect(mockEngine.findOne).toHaveBeenCalledWith(
217
220
  'task',
218
- { filter: { id: 't1' } },
221
+ { where: { id: 't1' } },
219
222
  );
220
223
  });
221
224