@objectstack/objectql 3.2.1 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/objectql",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Isomorphic ObjectQL Engine for ObjectStack",
6
6
  "main": "dist/index.js",
@@ -13,9 +13,9 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "@objectstack/spec": "3.2.1",
17
- "@objectstack/core": "3.2.1",
18
- "@objectstack/types": "3.2.1"
16
+ "@objectstack/core": "3.2.2",
17
+ "@objectstack/spec": "3.2.2",
18
+ "@objectstack/types": "3.2.2"
19
19
  },
20
20
  "devDependencies": {
21
21
  "typescript": "^5.0.0",
@@ -239,20 +239,20 @@ describe('ObjectQL Engine', () => {
239
239
  // Primary find returns tasks with assignee IDs
240
240
  vi.mocked(mockDriver.find)
241
241
  .mockResolvedValueOnce([
242
- { _id: 't1', title: 'Task 1', assignee: 'u1' },
243
- { _id: 't2', title: 'Task 2', assignee: 'u2' },
242
+ { id: 't1', title: 'Task 1', assignee: 'u1' },
243
+ { id: 't2', title: 'Task 2', assignee: 'u2' },
244
244
  ])
245
245
  // Second call (expand): returns user records
246
246
  .mockResolvedValueOnce([
247
- { _id: 'u1', name: 'Alice' },
248
- { _id: 'u2', name: 'Bob' },
247
+ { id: 'u1', name: 'Alice' },
248
+ { id: 'u2', name: 'Bob' },
249
249
  ]);
250
250
 
251
251
  const result = await engine.find('task', { populate: ['assignee'] });
252
252
 
253
253
  expect(result).toHaveLength(2);
254
- expect(result[0].assignee).toEqual({ _id: 'u1', name: 'Alice' });
255
- expect(result[1].assignee).toEqual({ _id: 'u2', name: 'Bob' });
254
+ expect(result[0].assignee).toEqual({ id: 'u1', name: 'Alice' });
255
+ expect(result[1].assignee).toEqual({ id: 'u2', name: 'Bob' });
256
256
 
257
257
  // Verify the expand query used $in
258
258
  expect(mockDriver.find).toHaveBeenCalledTimes(2);
@@ -260,7 +260,7 @@ describe('ObjectQL Engine', () => {
260
260
  'user',
261
261
  expect.objectContaining({
262
262
  object: 'user',
263
- where: { _id: { $in: ['u1', 'u2'] } },
263
+ where: { id: { $in: ['u1', 'u2'] } },
264
264
  }),
265
265
  );
266
266
  });
@@ -282,14 +282,14 @@ describe('ObjectQL Engine', () => {
282
282
 
283
283
  vi.mocked(mockDriver.find)
284
284
  .mockResolvedValueOnce([
285
- { _id: 'oi1', order: 'o1' },
285
+ { id: 'oi1', order: 'o1' },
286
286
  ])
287
287
  .mockResolvedValueOnce([
288
- { _id: 'o1', total: 100 },
288
+ { id: 'o1', total: 100 },
289
289
  ]);
290
290
 
291
291
  const result = await engine.find('order_item', { populate: ['order'] });
292
- expect(result[0].order).toEqual({ _id: 'o1', total: 100 });
292
+ expect(result[0].order).toEqual({ id: 'o1', total: 100 });
293
293
  });
294
294
 
295
295
  it('should skip expand for fields without reference definition', async () => {
@@ -301,7 +301,7 @@ describe('ObjectQL Engine', () => {
301
301
  } as any);
302
302
 
303
303
  vi.mocked(mockDriver.find).mockResolvedValueOnce([
304
- { _id: 't1', title: 'Task 1' },
304
+ { id: 't1', title: 'Task 1' },
305
305
  ]);
306
306
 
307
307
  const result = await engine.find('task', { populate: ['title'] });
@@ -313,7 +313,7 @@ describe('ObjectQL Engine', () => {
313
313
  vi.mocked(SchemaRegistry.getObject).mockReturnValue(undefined);
314
314
 
315
315
  vi.mocked(mockDriver.find).mockResolvedValueOnce([
316
- { _id: 't1', assignee: 'u1' },
316
+ { id: 't1', assignee: 'u1' },
317
317
  ]);
318
318
 
319
319
  const result = await engine.find('task', { populate: ['assignee'] });
@@ -338,16 +338,16 @@ describe('ObjectQL Engine', () => {
338
338
 
339
339
  vi.mocked(mockDriver.find)
340
340
  .mockResolvedValueOnce([
341
- { _id: 't1', assignee: null },
342
- { _id: 't2', assignee: 'u1' },
341
+ { id: 't1', assignee: null },
342
+ { id: 't2', assignee: 'u1' },
343
343
  ])
344
344
  .mockResolvedValueOnce([
345
- { _id: 'u1', name: 'Alice' },
345
+ { id: 'u1', name: 'Alice' },
346
346
  ]);
347
347
 
348
348
  const result = await engine.find('task', { populate: ['assignee'] });
349
349
  expect(result[0].assignee).toBeNull();
350
- expect(result[1].assignee).toEqual({ _id: 'u1', name: 'Alice' });
350
+ expect(result[1].assignee).toEqual({ id: 'u1', name: 'Alice' });
351
351
  });
352
352
 
353
353
  it('should de-duplicate foreign key IDs in batch query', async () => {
@@ -367,13 +367,13 @@ describe('ObjectQL Engine', () => {
367
367
 
368
368
  vi.mocked(mockDriver.find)
369
369
  .mockResolvedValueOnce([
370
- { _id: 't1', assignee: 'u1' },
371
- { _id: 't2', assignee: 'u1' }, // Same user
372
- { _id: 't3', assignee: 'u2' },
370
+ { id: 't1', assignee: 'u1' },
371
+ { id: 't2', assignee: 'u1' }, // Same user
372
+ { id: 't3', assignee: 'u2' },
373
373
  ])
374
374
  .mockResolvedValueOnce([
375
- { _id: 'u1', name: 'Alice' },
376
- { _id: 'u2', name: 'Bob' },
375
+ { id: 'u1', name: 'Alice' },
376
+ { id: 'u2', name: 'Bob' },
377
377
  ]);
378
378
 
379
379
  const result = await engine.find('task', { populate: ['assignee'] });
@@ -382,11 +382,11 @@ describe('ObjectQL Engine', () => {
382
382
  expect(mockDriver.find).toHaveBeenLastCalledWith(
383
383
  'user',
384
384
  expect.objectContaining({
385
- where: { _id: { $in: ['u1', 'u2'] } },
385
+ where: { id: { $in: ['u1', 'u2'] } },
386
386
  }),
387
387
  );
388
- expect(result[0].assignee).toEqual({ _id: 'u1', name: 'Alice' });
389
- expect(result[1].assignee).toEqual({ _id: 'u1', name: 'Alice' });
388
+ expect(result[0].assignee).toEqual({ id: 'u1', name: 'Alice' });
389
+ expect(result[1].assignee).toEqual({ id: 'u1', name: 'Alice' });
390
390
  });
391
391
 
392
392
  it('should keep raw ID when referenced record not found', async () => {
@@ -406,7 +406,7 @@ describe('ObjectQL Engine', () => {
406
406
 
407
407
  vi.mocked(mockDriver.find)
408
408
  .mockResolvedValueOnce([
409
- { _id: 't1', assignee: 'u_deleted' },
409
+ { id: 't1', assignee: 'u_deleted' },
410
410
  ])
411
411
  .mockResolvedValueOnce([]); // No records found
412
412
 
@@ -436,15 +436,15 @@ describe('ObjectQL Engine', () => {
436
436
 
437
437
  vi.mocked(mockDriver.find)
438
438
  .mockResolvedValueOnce([
439
- { _id: 't1', assignee: 'u1', project: 'p1' },
439
+ { id: 't1', assignee: 'u1', project: 'p1' },
440
440
  ])
441
- .mockResolvedValueOnce([{ _id: 'u1', name: 'Alice' }])
442
- .mockResolvedValueOnce([{ _id: 'p1', name: 'Project X' }]);
441
+ .mockResolvedValueOnce([{ id: 'u1', name: 'Alice' }])
442
+ .mockResolvedValueOnce([{ id: 'p1', name: 'Project X' }]);
443
443
 
444
444
  const result = await engine.find('task', { populate: ['assignee', 'project'] });
445
445
 
446
- expect(result[0].assignee).toEqual({ _id: 'u1', name: 'Alice' });
447
- expect(result[0].project).toEqual({ _id: 'p1', name: 'Project X' });
446
+ expect(result[0].assignee).toEqual({ id: 'u1', name: 'Alice' });
447
+ expect(result[0].project).toEqual({ id: 'p1', name: 'Project X' });
448
448
  expect(mockDriver.find).toHaveBeenCalledTimes(3);
449
449
  });
450
450
 
@@ -464,15 +464,15 @@ describe('ObjectQL Engine', () => {
464
464
  });
465
465
 
466
466
  vi.mocked(mockDriver.findOne as any).mockResolvedValueOnce(
467
- { _id: 't1', title: 'Task 1', assignee: 'u1' },
467
+ { id: 't1', title: 'Task 1', assignee: 'u1' },
468
468
  );
469
469
  vi.mocked(mockDriver.find).mockResolvedValueOnce([
470
- { _id: 'u1', name: 'Alice' },
470
+ { id: 'u1', name: 'Alice' },
471
471
  ]);
472
472
 
473
473
  const result = await engine.findOne('task', { populate: ['assignee'] });
474
474
 
475
- expect(result.assignee).toEqual({ _id: 'u1', name: 'Alice' });
475
+ expect(result.assignee).toEqual({ id: 'u1', name: 'Alice' });
476
476
  });
477
477
 
478
478
  it('should handle already-expanded objects (skip re-expansion)', async () => {
@@ -492,14 +492,14 @@ describe('ObjectQL Engine', () => {
492
492
 
493
493
  // Driver returns an already-expanded object
494
494
  vi.mocked(mockDriver.find).mockResolvedValueOnce([
495
- { _id: 't1', assignee: { _id: 'u1', name: 'Alice' } },
495
+ { id: 't1', assignee: { id: 'u1', name: 'Alice' } },
496
496
  ]);
497
497
 
498
498
  const result = await engine.find('task', { populate: ['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);
502
- expect(result[0].assignee).toEqual({ _id: 'u1', name: 'Alice' });
502
+ expect(result[0].assignee).toEqual({ id: 'u1', name: 'Alice' });
503
503
  });
504
504
 
505
505
  it('should gracefully handle expand errors and keep raw IDs', async () => {
@@ -519,7 +519,7 @@ describe('ObjectQL Engine', () => {
519
519
 
520
520
  vi.mocked(mockDriver.find)
521
521
  .mockResolvedValueOnce([
522
- { _id: 't1', assignee: 'u1' },
522
+ { id: 't1', assignee: 'u1' },
523
523
  ])
524
524
  .mockRejectedValueOnce(new Error('Driver connection failed'));
525
525
 
@@ -544,17 +544,17 @@ describe('ObjectQL Engine', () => {
544
544
 
545
545
  vi.mocked(mockDriver.find)
546
546
  .mockResolvedValueOnce([
547
- { _id: 't1', watchers: ['u1', 'u2'] },
547
+ { id: 't1', watchers: ['u1', 'u2'] },
548
548
  ])
549
549
  .mockResolvedValueOnce([
550
- { _id: 'u1', name: 'Alice' },
551
- { _id: 'u2', name: 'Bob' },
550
+ { id: 'u1', name: 'Alice' },
551
+ { id: 'u2', name: 'Bob' },
552
552
  ]);
553
553
 
554
554
  const result = await engine.find('task', { populate: ['watchers'] });
555
555
  expect(result[0].watchers).toEqual([
556
- { _id: 'u1', name: 'Alice' },
557
- { _id: 'u2', name: 'Bob' },
556
+ { id: 'u1', name: 'Alice' },
557
+ { id: 'u2', name: 'Bob' },
558
558
  ]);
559
559
  });
560
560
 
@@ -570,14 +570,14 @@ describe('ObjectQL Engine', () => {
570
570
  });
571
571
 
572
572
  vi.mocked(mockDriver.find)
573
- .mockResolvedValueOnce([{ _id: 't1', project: 'p1' }]) // find task
574
- .mockResolvedValueOnce([{ _id: 'p1', org: 'o1' }]); // expand project (depth 0)
573
+ .mockResolvedValueOnce([{ id: 't1', project: 'p1' }]) // find task
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
577
  const result = await engine.find('task', { populate: ['project'] });
578
578
 
579
579
  // Project expanded, but org inside project remains as raw ID
580
- expect(result[0].project).toEqual({ _id: 'p1', org: 'o1' });
580
+ expect(result[0].project).toEqual({ id: 'p1', org: 'o1' });
581
581
  expect(mockDriver.find).toHaveBeenCalledTimes(2); // Only primary + 1 expand query
582
582
  });
583
583
 
@@ -588,11 +588,11 @@ describe('ObjectQL Engine', () => {
588
588
  } as any);
589
589
 
590
590
  vi.mocked(mockDriver.find).mockResolvedValueOnce([
591
- { _id: 't1', title: 'Task 1' },
591
+ { id: 't1', title: 'Task 1' },
592
592
  ]);
593
593
 
594
594
  const result = await engine.find('task', {});
595
- expect(result).toEqual([{ _id: 't1', title: 'Task 1' }]);
595
+ expect(result).toEqual([{ id: 't1', title: 'Task 1' }]);
596
596
  expect(mockDriver.find).toHaveBeenCalledTimes(1);
597
597
  });
598
598
  });
package/src/engine.ts CHANGED
@@ -681,7 +681,7 @@ export class ObjectQL implements IDataEngine {
681
681
  try {
682
682
  const relatedQuery: QueryAST = {
683
683
  object: referenceObject,
684
- where: { _id: { $in: uniqueIds } },
684
+ where: { id: { $in: uniqueIds } },
685
685
  ...(nestedAST.fields ? { fields: nestedAST.fields } : {}),
686
686
  ...(nestedAST.orderBy ? { orderBy: nestedAST.orderBy } : {}),
687
687
  };
@@ -692,7 +692,7 @@ export class ObjectQL implements IDataEngine {
692
692
  // Build a lookup map: id → record
693
693
  const recordMap = new Map<string, any>();
694
694
  for (const rec of relatedRecords) {
695
- const id = rec._id ?? rec.id;
695
+ const id = rec.id;
696
696
  if (id != null) recordMap.set(String(id), rec);
697
697
  }
698
698
 
@@ -707,7 +707,7 @@ export class ObjectQL implements IDataEngine {
707
707
  // Rebuild map with expanded records
708
708
  recordMap.clear();
709
709
  for (const rec of expandedRelated) {
710
- const id = rec._id ?? rec.id;
710
+ const id = rec.id;
711
711
  if (id != null) recordMap.set(String(id), rec);
712
712
  }
713
713
  }
@@ -921,10 +921,9 @@ export class ObjectQL implements IDataEngine {
921
921
  const driver = this.getDriver(object);
922
922
 
923
923
  // 1. Extract ID from data or filter if it's a single update by ID
924
- let id = data.id || data._id;
924
+ let id = data.id;
925
925
  if (!id && options?.filter) {
926
926
  if (typeof options.filter === 'string') id = options.filter;
927
- else if (options.filter._id) id = options.filter._id;
928
927
  else if (options.filter.id) id = options.filter.id;
929
928
  }
930
929
 
@@ -980,7 +979,6 @@ export class ObjectQL implements IDataEngine {
980
979
  let id: any = undefined;
981
980
  if (options?.filter) {
982
981
  if (typeof options.filter === 'string') id = options.filter;
983
- else if (options.filter._id) id = options.filter._id;
984
982
  else if (options.filter.id) id = options.filter.id;
985
983
  }
986
984
 
@@ -1043,7 +1041,7 @@ export class ObjectQL implements IDataEngine {
1043
1041
  return driver.count(object, ast);
1044
1042
  }
1045
1043
  // Fallback to find().length
1046
- const res = await this.find(object, { filter: query?.filter, select: ['_id'] });
1044
+ const res = await this.find(object, { filter: query?.filter, select: ['id'] });
1047
1045
  return res.length;
1048
1046
  });
1049
1047
 
@@ -1335,8 +1333,8 @@ export class ObjectRepository {
1335
1333
 
1336
1334
  /** Update a single record by ID */
1337
1335
  async updateById(id: string | number, data: any): Promise<any> {
1338
- return this.engine.update(this.objectName, { ...data, _id: id }, {
1339
- filter: { _id: id },
1336
+ return this.engine.update(this.objectName, { ...data, id: id }, {
1337
+ filter: { id: id },
1340
1338
  context: this.context,
1341
1339
  });
1342
1340
  }
@@ -1351,7 +1349,7 @@ export class ObjectRepository {
1351
1349
  /** Delete a single record by ID */
1352
1350
  async deleteById(id: string | number): Promise<any> {
1353
1351
  return this.engine.delete(this.objectName, {
1354
- filter: { _id: id },
1352
+ filter: { id: id },
1355
1353
  context: this.context,
1356
1354
  });
1357
1355
  }
package/src/plugin.ts CHANGED
@@ -129,35 +129,35 @@ export class ObjectQLPlugin implements Plugin {
129
129
  }
130
130
 
131
131
  /**
132
- * Register built-in audit hooks for auto-stamping createdBy/modifiedBy
132
+ * Register built-in audit hooks for auto-stamping created_by/updated_by
133
133
  * and fetching previousData for update/delete operations.
134
134
  */
135
135
  private registerAuditHooks(ctx: PluginContext) {
136
136
  if (!this.ql) return;
137
137
 
138
- // Auto-stamp createdBy/modifiedBy on insert
138
+ // Auto-stamp created_by/updated_by on insert
139
139
  this.ql.registerHook('beforeInsert', async (hookCtx) => {
140
140
  if (hookCtx.session?.userId && hookCtx.input?.data) {
141
141
  const data = hookCtx.input.data as Record<string, any>;
142
142
  if (typeof data === 'object' && data !== null) {
143
143
  data.created_by = data.created_by ?? hookCtx.session.userId;
144
- data.modified_by = hookCtx.session.userId;
144
+ data.updated_by = hookCtx.session.userId;
145
145
  data.created_at = data.created_at ?? new Date().toISOString();
146
- data.modified_at = new Date().toISOString();
146
+ data.updated_at = new Date().toISOString();
147
147
  if (hookCtx.session.tenantId) {
148
- data.space_id = data.space_id ?? hookCtx.session.tenantId;
148
+ data.tenant_id = data.tenant_id ?? hookCtx.session.tenantId;
149
149
  }
150
150
  }
151
151
  }
152
152
  }, { object: '*', priority: 10 });
153
153
 
154
- // Auto-stamp modifiedBy on update
154
+ // Auto-stamp updated_by on update
155
155
  this.ql.registerHook('beforeUpdate', async (hookCtx) => {
156
156
  if (hookCtx.session?.userId && hookCtx.input?.data) {
157
157
  const data = hookCtx.input.data as Record<string, any>;
158
158
  if (typeof data === 'object' && data !== null) {
159
- data.modified_by = hookCtx.session.userId;
160
- data.modified_at = new Date().toISOString();
159
+ data.updated_by = hookCtx.session.userId;
160
+ data.updated_at = new Date().toISOString();
161
161
  }
162
162
  }
163
163
  }, { object: '*', priority: 10 });
@@ -167,7 +167,7 @@ export class ObjectQLPlugin implements Plugin {
167
167
  if (hookCtx.input?.id && !hookCtx.previous) {
168
168
  try {
169
169
  const existing = await this.ql!.findOne(hookCtx.object, {
170
- filter: { _id: hookCtx.input.id }
170
+ filter: { id: hookCtx.input.id }
171
171
  });
172
172
  if (existing) {
173
173
  hookCtx.previous = existing;
@@ -183,7 +183,7 @@ export class ObjectQLPlugin implements Plugin {
183
183
  if (hookCtx.input?.id && !hookCtx.previous) {
184
184
  try {
185
185
  const existing = await this.ql!.findOne(hookCtx.object, {
186
- filter: { _id: hookCtx.input.id }
186
+ filter: { id: hookCtx.input.id }
187
187
  });
188
188
  if (existing) {
189
189
  hookCtx.previous = existing;
@@ -194,11 +194,11 @@ export class ObjectQLPlugin implements Plugin {
194
194
  }
195
195
  }, { object: '*', priority: 5 });
196
196
 
197
- ctx.logger.debug('Audit hooks registered (createdBy/modifiedBy, previousData)');
197
+ ctx.logger.debug('Audit hooks registered (created_by/updated_by, previousData)');
198
198
  }
199
199
 
200
200
  /**
201
- * Register tenant isolation middleware that auto-injects space_id filter
201
+ * Register tenant isolation middleware that auto-injects tenant_id filter
202
202
  * for multi-tenant operations.
203
203
  */
204
204
  private registerTenantMiddleware(ctx: PluginContext) {
@@ -210,10 +210,10 @@ export class ObjectQLPlugin implements Plugin {
210
210
  return next();
211
211
  }
212
212
 
213
- // Read operations: inject space_id filter into AST
213
+ // Read operations: inject tenant_id filter into AST
214
214
  if (['find', 'findOne', 'count', 'aggregate'].includes(opCtx.operation)) {
215
215
  if (opCtx.ast) {
216
- const tenantFilter = { space_id: opCtx.context.tenantId };
216
+ const tenantFilter = { tenant_id: opCtx.context.tenantId };
217
217
  if (opCtx.ast.where) {
218
218
  opCtx.ast.where = { $and: [opCtx.ast.where, tenantFilter] };
219
219
  } else {
@@ -129,14 +129,14 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
129
129
  });
130
130
 
131
131
  it('should return records and standard response shape', async () => {
132
- mockEngine.find.mockResolvedValue([{ _id: 't1', name: 'Task 1' }]);
132
+ mockEngine.find.mockResolvedValue([{ id: 't1', name: 'Task 1' }]);
133
133
 
134
134
  const result = await protocol.findData({ object: 'task', query: {} });
135
135
 
136
136
  expect(result).toEqual(
137
137
  expect.objectContaining({
138
138
  object: 'task',
139
- records: [{ _id: 't1', name: 'Task 1' }],
139
+ records: [{ id: 't1', name: 'Task 1' }],
140
140
  total: 1,
141
141
  }),
142
142
  );
@@ -149,21 +149,21 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
149
149
 
150
150
  describe('getData', () => {
151
151
  it('should convert expand string to populate array', async () => {
152
- mockEngine.findOne.mockResolvedValue({ _id: 'oi_1', name: 'Item 1' });
152
+ mockEngine.findOne.mockResolvedValue({ id: 'oi_1', name: 'Item 1' });
153
153
 
154
154
  await protocol.getData({ object: 'order_item', id: 'oi_1', expand: 'order,product' });
155
155
 
156
156
  expect(mockEngine.findOne).toHaveBeenCalledWith(
157
157
  'order_item',
158
158
  expect.objectContaining({
159
- filter: { _id: 'oi_1' },
159
+ filter: { id: 'oi_1' },
160
160
  populate: ['order', 'product'],
161
161
  }),
162
162
  );
163
163
  });
164
164
 
165
165
  it('should convert expand array to populate array', async () => {
166
- mockEngine.findOne.mockResolvedValue({ _id: 't1' });
166
+ mockEngine.findOne.mockResolvedValue({ id: 't1' });
167
167
 
168
168
  await protocol.getData({ object: 'task', id: 't1', expand: ['assignee', 'project'] });
169
169
 
@@ -176,7 +176,7 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
176
176
  });
177
177
 
178
178
  it('should convert select string to array', async () => {
179
- mockEngine.findOne.mockResolvedValue({ _id: 't1', name: 'Test' });
179
+ mockEngine.findOne.mockResolvedValue({ id: 't1', name: 'Test' });
180
180
 
181
181
  await protocol.getData({ object: 'task', id: 't1', select: 'name,status' });
182
182
 
@@ -189,7 +189,7 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
189
189
  });
190
190
 
191
191
  it('should pass both expand and select together', async () => {
192
- mockEngine.findOne.mockResolvedValue({ _id: 'oi_1' });
192
+ mockEngine.findOne.mockResolvedValue({ id: 'oi_1' });
193
193
 
194
194
  await protocol.getData({
195
195
  object: 'order_item',
@@ -201,7 +201,7 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
201
201
  expect(mockEngine.findOne).toHaveBeenCalledWith(
202
202
  'order_item',
203
203
  expect.objectContaining({
204
- filter: { _id: 'oi_1' },
204
+ filter: { id: 'oi_1' },
205
205
  populate: ['order'],
206
206
  select: ['name', 'total'],
207
207
  }),
@@ -209,25 +209,25 @@ describe('ObjectStackProtocolImplementation - Data Operations', () => {
209
209
  });
210
210
 
211
211
  it('should work without expand or select', async () => {
212
- mockEngine.findOne.mockResolvedValue({ _id: 't1' });
212
+ mockEngine.findOne.mockResolvedValue({ id: 't1' });
213
213
 
214
214
  await protocol.getData({ object: 'task', id: 't1' });
215
215
 
216
216
  expect(mockEngine.findOne).toHaveBeenCalledWith(
217
217
  'task',
218
- { filter: { _id: 't1' } },
218
+ { filter: { id: 't1' } },
219
219
  );
220
220
  });
221
221
 
222
222
  it('should return standard GetDataResponse shape', async () => {
223
- mockEngine.findOne.mockResolvedValue({ _id: 'oi_1', name: 'Item 1' });
223
+ mockEngine.findOne.mockResolvedValue({ id: 'oi_1', name: 'Item 1' });
224
224
 
225
225
  const result = await protocol.getData({ object: 'order_item', id: 'oi_1' });
226
226
 
227
227
  expect(result).toEqual({
228
228
  object: 'order_item',
229
229
  id: 'oi_1',
230
- record: { _id: 'oi_1', name: 'Item 1' },
230
+ record: { id: 'oi_1', name: 'Item 1' },
231
231
  });
232
232
  });
233
233
 
package/src/protocol.ts CHANGED
@@ -246,7 +246,7 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
246
246
  // Form View Generation
247
247
  // Simple single-section layout for now
248
248
  const formFields = fieldKeys
249
- .filter(k => k !== 'id' && k !== 'created_at' && k !== 'modified_at' && !fields[k].hidden)
249
+ .filter(k => k !== 'id' && k !== 'created_at' && k !== 'updated_at' && !fields[k].hidden)
250
250
  .map(f => ({
251
251
  field: f,
252
252
  label: fields[f]?.label,
@@ -361,7 +361,7 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
361
361
 
362
362
  async getData(request: { object: string, id: string, expand?: string | string[], select?: string | string[] }) {
363
363
  const queryOptions: any = {
364
- filter: { _id: request.id }
364
+ filter: { id: request.id }
365
365
  };
366
366
 
367
367
  // Support select for single-record retrieval
@@ -393,14 +393,14 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
393
393
  const result = await this.engine.insert(request.object, request.data);
394
394
  return {
395
395
  object: request.object,
396
- id: result._id || result.id,
396
+ id: result.id,
397
397
  record: result
398
398
  };
399
399
  }
400
400
 
401
401
  async updateData(request: { object: string, id: string, data: any }) {
402
402
  // Adapt: update(obj, id, data) -> update(obj, data, options)
403
- const result = await this.engine.update(request.object, request.data, { filter: { _id: request.id } });
403
+ const result = await this.engine.update(request.object, request.data, { filter: { id: request.id } });
404
404
  return {
405
405
  object: request.object,
406
406
  id: request.id,
@@ -410,7 +410,7 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
410
410
 
411
411
  async deleteData(request: { object: string, id: string }) {
412
412
  // Adapt: delete(obj, id) -> delete(obj, options)
413
- await this.engine.delete(request.object, { filter: { _id: request.id } });
413
+ await this.engine.delete(request.object, { filter: { id: request.id } });
414
414
  return {
415
415
  object: request.object,
416
416
  id: request.id,
@@ -478,13 +478,13 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
478
478
  switch (operation) {
479
479
  case 'create': {
480
480
  const created = await this.engine.insert(object, record.data || record);
481
- results.push({ id: created._id || created.id, success: true, record: created });
481
+ results.push({ id: created.id, success: true, record: created });
482
482
  succeeded++;
483
483
  break;
484
484
  }
485
485
  case 'update': {
486
486
  if (!record.id) throw new Error('Record id is required for update');
487
- const updated = await this.engine.update(object, record.data || {}, { filter: { _id: record.id } });
487
+ const updated = await this.engine.update(object, record.data || {}, { filter: { id: record.id } });
488
488
  results.push({ id: record.id, success: true, record: updated });
489
489
  succeeded++;
490
490
  break;
@@ -493,28 +493,28 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
493
493
  // Try update first, then create if not found
494
494
  if (record.id) {
495
495
  try {
496
- const existing = await this.engine.findOne(object, { filter: { _id: record.id } });
496
+ const existing = await this.engine.findOne(object, { filter: { id: record.id } });
497
497
  if (existing) {
498
- const updated = await this.engine.update(object, record.data || {}, { filter: { _id: record.id } });
498
+ const updated = await this.engine.update(object, record.data || {}, { filter: { id: record.id } });
499
499
  results.push({ id: record.id, success: true, record: updated });
500
500
  } else {
501
- const created = await this.engine.insert(object, { _id: record.id, ...(record.data || {}) });
502
- results.push({ id: created._id || created.id, success: true, record: created });
501
+ const created = await this.engine.insert(object, { id: record.id, ...(record.data || {}) });
502
+ results.push({ id: created.id, success: true, record: created });
503
503
  }
504
504
  } catch {
505
- const created = await this.engine.insert(object, { _id: record.id, ...(record.data || {}) });
506
- results.push({ id: created._id || created.id, success: true, record: created });
505
+ const created = await this.engine.insert(object, { id: record.id, ...(record.data || {}) });
506
+ results.push({ id: created.id, success: true, record: created });
507
507
  }
508
508
  } else {
509
509
  const created = await this.engine.insert(object, record.data || record);
510
- results.push({ id: created._id || created.id, success: true, record: created });
510
+ results.push({ id: created.id, success: true, record: created });
511
511
  }
512
512
  succeeded++;
513
513
  break;
514
514
  }
515
515
  case 'delete': {
516
516
  if (!record.id) throw new Error('Record id is required for delete');
517
- await this.engine.delete(object, { filter: { _id: record.id } });
517
+ await this.engine.delete(object, { filter: { id: record.id } });
518
518
  results.push({ id: record.id, success: true });
519
519
  succeeded++;
520
520
  break;
@@ -563,7 +563,7 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
563
563
 
564
564
  for (const record of records) {
565
565
  try {
566
- const updated = await this.engine.update(object, record.data, { filter: { _id: record.id } });
566
+ const updated = await this.engine.update(object, record.data, { filter: { id: record.id } });
567
567
  results.push({ id: record.id, success: true, record: updated });
568
568
  succeeded++;
569
569
  } catch (err: any) {
@@ -765,7 +765,7 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
765
765
  async deleteManyData(request: DeleteManyDataRequest): Promise<any> {
766
766
  // This expects deleting by IDs.
767
767
  return this.engine.delete(request.object, {
768
- filter: { _id: { $in: request.ids } },
768
+ filter: { id: { $in: request.ids } },
769
769
  ...request.options
770
770
  });
771
771
  }