@objectstack/objectql 4.0.2 → 4.0.3

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/src/protocol.ts CHANGED
@@ -2,15 +2,16 @@
2
2
 
3
3
  import { ObjectStackProtocol } from '@objectstack/spec/api';
4
4
  import { IDataEngine } from '@objectstack/core';
5
- import type {
6
- BatchUpdateRequest,
7
- BatchUpdateResponse,
5
+ import type {
6
+ BatchUpdateRequest,
7
+ BatchUpdateResponse,
8
8
  UpdateManyDataRequest,
9
9
  DeleteManyDataRequest
10
10
  } from '@objectstack/spec/api';
11
11
  import type { MetadataCacheRequest, MetadataCacheResponse, ServiceInfo, ApiRoutes, WellKnownCapabilities } from '@objectstack/spec/api';
12
12
  import type { IFeedService } from '@objectstack/spec/contracts';
13
13
  import { parseFilterAST, isFilterAST } from '@objectstack/spec/data';
14
+ import { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL } from '@objectstack/spec/shared';
14
15
 
15
16
  // We import SchemaRegistry directly since this class lives in the same package
16
17
  import { SchemaRegistry } from './registry.js';
@@ -180,18 +181,31 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
180
181
  }
181
182
 
182
183
  async getMetaTypes() {
183
- return {
184
- types: SchemaRegistry.getRegisteredTypes()
185
- };
184
+ const schemaTypes = SchemaRegistry.getRegisteredTypes();
185
+
186
+ // Also include types from MetadataService (runtime-registered: agent, tool, etc.)
187
+ let runtimeTypes: string[] = [];
188
+ try {
189
+ const services = this.getServicesRegistry?.();
190
+ const metadataService = services?.get('metadata');
191
+ if (metadataService && typeof metadataService.getRegisteredTypes === 'function') {
192
+ runtimeTypes = await metadataService.getRegisteredTypes();
193
+ }
194
+ } catch {
195
+ // MetadataService not available
196
+ }
197
+
198
+ const allTypes = Array.from(new Set([...schemaTypes, ...runtimeTypes]));
199
+ return { types: allTypes };
186
200
  }
187
201
 
188
202
  async getMetaItems(request: { type: string; packageId?: string }) {
189
203
  const { packageId } = request;
190
204
  let items = SchemaRegistry.listItems(request.type, packageId);
191
- // Normalize singular/plural: REST uses singular ('app') but registry may store as plural ('apps')
205
+ // Normalize singular/plural using explicit mapping
192
206
  if (items.length === 0) {
193
- const alt = request.type.endsWith('s') ? request.type.slice(0, -1) : request.type + 's';
194
- items = SchemaRegistry.listItems(alt, packageId);
207
+ const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
208
+ if (alt) items = SchemaRegistry.listItems(alt, packageId);
195
209
  }
196
210
 
197
211
  // Fallback to database if registry is empty for this type
@@ -212,8 +226,9 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
212
226
  return data;
213
227
  });
214
228
  } else {
215
- // Try alternate type name in DB
216
- const alt = request.type.endsWith('s') ? request.type.slice(0, -1) : request.type + 's';
229
+ // Try alternate type name in DB using explicit mapping
230
+ const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
231
+ if (alt) {
217
232
  const altRecords = await this.engine.find('sys_metadata', {
218
233
  where: { type: alt, state: 'active' }
219
234
  });
@@ -226,12 +241,41 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
226
241
  return data;
227
242
  });
228
243
  }
244
+ }
229
245
  }
230
246
  } catch {
231
247
  // DB not available, return registry results (empty)
232
248
  }
233
249
  }
234
250
 
251
+ // Merge with MetadataService (runtime-registered items: agents, tools, etc.)
252
+ try {
253
+ const services = this.getServicesRegistry?.();
254
+ const metadataService = services?.get('metadata');
255
+ if (metadataService && typeof metadataService.list === 'function') {
256
+ const runtimeItems = await metadataService.list(request.type);
257
+ if (runtimeItems && runtimeItems.length > 0) {
258
+ // Merge, avoiding duplicates by name
259
+ const itemMap = new Map<string, any>();
260
+ for (const item of items) {
261
+ const entry = item as any;
262
+ if (entry && typeof entry === 'object' && 'name' in entry) {
263
+ itemMap.set(entry.name, entry);
264
+ }
265
+ }
266
+ for (const item of runtimeItems) {
267
+ const entry = item as any;
268
+ if (entry && typeof entry === 'object' && 'name' in entry) {
269
+ itemMap.set(entry.name, entry);
270
+ }
271
+ }
272
+ items = Array.from(itemMap.values());
273
+ }
274
+ }
275
+ } catch {
276
+ // MetadataService not available or doesn't support this type
277
+ }
278
+
235
279
  return {
236
280
  type: request.type,
237
281
  items
@@ -240,10 +284,10 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
240
284
 
241
285
  async getMetaItem(request: { type: string, name: string, packageId?: string }) {
242
286
  let item = SchemaRegistry.getItem(request.type, request.name);
243
- // Normalize singular/plural
287
+ // Normalize singular/plural using explicit mapping
244
288
  if (item === undefined) {
245
- const alt = request.type.endsWith('s') ? request.type.slice(0, -1) : request.type + 's';
246
- item = SchemaRegistry.getItem(alt, request.name);
289
+ const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
290
+ if (alt) item = SchemaRegistry.getItem(alt, request.name);
247
291
  }
248
292
 
249
293
  // Fallback to database if not in registry
@@ -259,8 +303,9 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
259
303
  // Hydrate back into registry for next time
260
304
  SchemaRegistry.registerItem(request.type, item, 'name' as any);
261
305
  } else {
262
- // Try alternate type name
263
- const alt = request.type.endsWith('s') ? request.type.slice(0, -1) : request.type + 's';
306
+ // Try alternate type name using explicit mapping
307
+ const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
308
+ if (alt) {
264
309
  const altRecord = await this.engine.findOne('sys_metadata', {
265
310
  where: { type: alt, name: request.name, state: 'active' }
266
311
  });
@@ -271,12 +316,26 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
271
316
  // Hydrate back into registry for next time
272
317
  SchemaRegistry.registerItem(request.type, item, 'name' as any);
273
318
  }
319
+ }
274
320
  }
275
321
  } catch {
276
322
  // DB not available, return undefined
277
323
  }
278
324
  }
279
325
 
326
+ // Fallback to MetadataService for runtime-registered items (agents, tools, etc.)
327
+ if (item === undefined) {
328
+ try {
329
+ const services = this.getServicesRegistry?.();
330
+ const metadataService = services?.get('metadata');
331
+ if (metadataService && typeof metadataService.get === 'function') {
332
+ item = await metadataService.get(request.type, request.name);
333
+ }
334
+ } catch {
335
+ // MetadataService not available
336
+ }
337
+ }
338
+
280
339
  return {
281
340
  type: request.type,
282
341
  name: request.name,
@@ -563,7 +622,27 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
563
622
 
564
623
  async getMetaItemCached(request: { type: string, name: string, cacheRequest?: MetadataCacheRequest }): Promise<MetadataCacheResponse> {
565
624
  try {
566
- const item = SchemaRegistry.getItem(request.type, request.name);
625
+ let item = SchemaRegistry.getItem(request.type, request.name);
626
+
627
+ // Normalize singular/plural using explicit mapping
628
+ if (!item) {
629
+ const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
630
+ if (alt) item = SchemaRegistry.getItem(alt, request.name);
631
+ }
632
+
633
+ // Fallback to MetadataService (e.g. agents, tools registered in MetadataManager)
634
+ if (!item) {
635
+ try {
636
+ const services = this.getServicesRegistry?.();
637
+ const metadataService = services?.get('metadata');
638
+ if (metadataService && typeof metadataService.get === 'function') {
639
+ item = await metadataService.get(request.type, request.name);
640
+ }
641
+ } catch {
642
+ // MetadataService not available
643
+ }
644
+ }
645
+
567
646
  if (!item) {
568
647
  throw new Error(`Metadata item ${request.type}/${request.name} not found`);
569
648
  }
@@ -984,10 +1063,12 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
984
1063
  const data = typeof record.metadata === 'string'
985
1064
  ? JSON.parse(record.metadata)
986
1065
  : record.metadata;
987
- if (record.type === 'object') {
1066
+ // Normalize DB type to singular (DB may store legacy plural forms)
1067
+ const normalizedType = PLURAL_TO_SINGULAR[record.type] ?? record.type;
1068
+ if (normalizedType === 'object') {
988
1069
  SchemaRegistry.registerObject(data as any, record.packageId || 'sys_metadata');
989
1070
  } else {
990
- SchemaRegistry.registerItem(record.type, data, 'name' as any);
1071
+ SchemaRegistry.registerItem(normalizedType, data, 'name' as any);
991
1072
  }
992
1073
  loaded++;
993
1074
  } catch (e) {
@@ -353,17 +353,17 @@ describe('SchemaRegistry', () => {
353
353
  describe('Generic Metadata', () => {
354
354
  it('should register and retrieve generic items', () => {
355
355
  const item = { name: 'test_action', type: 'custom' };
356
- SchemaRegistry.registerItem('actions', item, 'name', 'com.pkg');
357
-
358
- const retrieved = SchemaRegistry.getItem('actions', 'test_action');
356
+ SchemaRegistry.registerItem('action', item, 'name', 'com.pkg');
357
+
358
+ const retrieved = SchemaRegistry.getItem('action', 'test_action');
359
359
  expect(retrieved).toEqual(item);
360
360
  });
361
361
 
362
362
  it('should list items by type with package filter', () => {
363
- SchemaRegistry.registerItem('actions', { name: 'a1' }, 'name', 'com.pkg1');
364
- SchemaRegistry.registerItem('actions', { name: 'a2' }, 'name', 'com.pkg2');
365
-
366
- const filtered = SchemaRegistry.listItems('actions', 'com.pkg1');
363
+ SchemaRegistry.registerItem('action', { name: 'a1' }, 'name', 'com.pkg1');
364
+ SchemaRegistry.registerItem('action', { name: 'a2' }, 'name', 'com.pkg2');
365
+
366
+ const filtered = SchemaRegistry.listItems('action', 'com.pkg1');
367
367
  expect(filtered).toHaveLength(1);
368
368
  });
369
369
  });
@@ -396,12 +396,12 @@ describe('SchemaRegistry', () => {
396
396
  describe('Reset', () => {
397
397
  it('should clear all state', () => {
398
398
  SchemaRegistry.registerObject({ name: 'obj', fields: {} } as any, 'com.pkg', 'pkg', 'own');
399
- SchemaRegistry.registerItem('actions', { name: 'act' }, 'name');
400
-
399
+ SchemaRegistry.registerItem('action', { name: 'act' }, 'name');
400
+
401
401
  SchemaRegistry.reset();
402
-
402
+
403
403
  expect(SchemaRegistry.getAllObjects()).toHaveLength(0);
404
- expect(SchemaRegistry.listItems('actions')).toHaveLength(0);
404
+ expect(SchemaRegistry.listItems('action')).toHaveLength(0);
405
405
  });
406
406
  });
407
407
 
package/src/registry.ts CHANGED
@@ -485,7 +485,7 @@ export class SchemaRegistry {
485
485
  if (type === 'object') {
486
486
  return ObjectSchema.parse(item);
487
487
  }
488
- if (type === 'apps') {
488
+ if (type === 'app') {
489
489
  return AppSchema.parse(item);
490
490
  }
491
491
  if (type === 'package') {
@@ -664,15 +664,15 @@ export class SchemaRegistry {
664
664
  // ==========================================
665
665
 
666
666
  static registerApp(app: any, packageId?: string) {
667
- this.registerItem('apps', app, 'name', packageId);
667
+ this.registerItem('app', app, 'name', packageId);
668
668
  }
669
669
 
670
670
  static getApp(name: string): any {
671
- return this.getItem('apps', name);
671
+ return this.getItem('app', name);
672
672
  }
673
673
 
674
674
  static getAllApps(): any[] {
675
- return this.listItems('apps');
675
+ return this.listItems('app');
676
676
  }
677
677
 
678
678
  // ==========================================