From cd7c7366b952fca0af6de50f6c83e583dc9e718a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 08:48:50 +0000 Subject: [PATCH 1/4] Initial plan From 11e01c17c5c5b8fdedf3da2758762067b2ba7d54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 08:55:28 +0000 Subject: [PATCH 2/4] Align client discovery interface with spec API protocol Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- packages/client/src/client.test.ts | 7 +++- packages/client/src/index.ts | 57 +++++++++++++++--------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/packages/client/src/client.test.ts b/packages/client/src/client.test.ts index 8015864aa..bf7e8e885 100644 --- a/packages/client/src/client.test.ts +++ b/packages/client/src/client.test.ts @@ -15,7 +15,12 @@ describe('ObjectStackClient', () => { it('should make discovery request on connect', async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, - json: async () => ({ routes: { data: '/api/v1/data' } }) + json: async () => ({ + version: 'v1', + apiName: 'ObjectStack', + capabilities: ['metadata', 'data', 'ui'], + endpoints: {} + }) }); const client = new ObjectStackClient({ diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index ae1198993..1e6a46e91 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -8,7 +8,8 @@ import { MetadataCacheRequest, MetadataCacheResponse, StandardErrorCode, - ErrorCategory + ErrorCategory, + GetDiscoveryResponse } from '@objectstack/spec/api'; import { Logger, createLogger } from '@objectstack/core'; @@ -29,16 +30,11 @@ export interface ClientConfig { debug?: boolean; } -export interface DiscoveryResult { - routes: { - discovery: string; - metadata: string; - data: string; - auth: string; - ui: string; - }; - capabilities?: Record; -} +/** + * Discovery Result + * Re-export from @objectstack/spec/api for convenience + */ +export type DiscoveryResult = GetDiscoveryResponse; export interface QueryOptions { select?: string[]; // Simplified Selection @@ -69,7 +65,7 @@ export class ObjectStackClient { private baseUrl: string; private token?: string; private fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise; - private routes?: DiscoveryResult['routes']; + private discoveryInfo?: DiscoveryResult; private logger: Logger; constructor(config: ClientConfig) { @@ -87,21 +83,21 @@ export class ObjectStackClient { } /** - * Initialize the client by discovering server capabilities and routes. + * Initialize the client by discovering server capabilities. */ async connect() { this.logger.debug('Connecting to ObjectStack server', { baseUrl: this.baseUrl }); try { - // Connect to the discovery endpoint - // During boot, we might not know routes, so we check convention /api/v1 first + // Connect to the discovery endpoint at /api/v1 const res = await this.fetch(`${this.baseUrl}/api/v1`); const data = await res.json(); - this.routes = data.routes; + this.discoveryInfo = data; this.logger.info('Connected to ObjectStack server', { - routes: Object.keys(data.routes || {}), + version: data.version, + apiName: data.apiName, capabilities: data.capabilities }); @@ -407,16 +403,20 @@ export class ObjectStackClient { return res; } - private getRoute(key: keyof DiscoveryResult['routes']): string { - if (!this.routes) { - // Fallback for strictness, but we allow bootstrapping - this.logger.warn('Accessing route before connect()', { - route: key, - fallback: `/api/v1/${key}` - }); - return `/api/v1/${key}`; - } - return this.routes[key] || `/api/v1/${key}`; + /** + * Get the conventional route path for a given API endpoint type + * ObjectStack uses standard conventions: /api/v1/data, /api/v1/meta, /api/v1/ui + */ + private getRoute(type: 'data' | 'metadata' | 'ui' | 'auth'): string { + // Use conventional ObjectStack API paths + const routeMap: Record = { + data: '/api/v1/data', + metadata: '/api/v1/meta', + ui: '/api/v1/ui', + auth: '/api/v1/auth' + }; + + return routeMap[type] || `/api/v1/${type}`; } } @@ -435,5 +435,6 @@ export type { MetadataCacheRequest, MetadataCacheResponse, StandardErrorCode, - ErrorCategory + ErrorCategory, + GetDiscoveryResponse } from '@objectstack/spec/api'; From f9cc0a4ad8d4f11cc1b3f0f1c6aa9bd8a9d2b833 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 08:57:23 +0000 Subject: [PATCH 3/4] Add metadata discovery methods to client (getTypes, getItems, getItem) Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- packages/client/src/client.test.ts | 57 ++++++++++++++++++++++++++++++ packages/client/src/index.ts | 43 ++++++++++++++++++++-- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/packages/client/src/client.test.ts b/packages/client/src/client.test.ts index bf7e8e885..8073fec10 100644 --- a/packages/client/src/client.test.ts +++ b/packages/client/src/client.test.ts @@ -31,4 +31,61 @@ describe('ObjectStackClient', () => { await client.connect(); expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1', expect.any(Object)); }); + + it('should get metadata types', async () => { + const fetchMock = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ + types: ['object', 'plugin', 'view'] + }) + }); + + const client = new ObjectStackClient({ + baseUrl: 'http://localhost:3000', + fetch: fetchMock + }); + + const result = await client.meta.getTypes(); + expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta', expect.any(Object)); + expect(result.types).toEqual(['object', 'plugin', 'view']); + }); + + it('should get metadata items by type', async () => { + const fetchMock = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ + type: 'object', + items: [{ name: 'customer' }, { name: 'order' }] + }) + }); + + const client = new ObjectStackClient({ + baseUrl: 'http://localhost:3000', + fetch: fetchMock + }); + + const result = await client.meta.getItems('object'); + expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta/object', expect.any(Object)); + expect(result.type).toBe('object'); + expect(result.items).toHaveLength(2); + }); + + it('should get metadata item by type and name', async () => { + const fetchMock = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ + name: 'customer', + fields: [] + }) + }); + + const client = new ObjectStackClient({ + baseUrl: 'http://localhost:3000', + fetch: fetchMock + }); + + const result = await client.meta.getItem('object', 'customer'); + expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta/object/customer', expect.any(Object)); + expect(result.name).toBe('customer'); + }); }); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 1e6a46e91..bfbb9c402 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -9,7 +9,9 @@ import { MetadataCacheResponse, StandardErrorCode, ErrorCategory, - GetDiscoveryResponse + GetDiscoveryResponse, + GetMetaTypesResponse, + GetMetaItemsResponse } from '@objectstack/spec/api'; import { Logger, createLogger } from '@objectstack/core'; @@ -112,11 +114,46 @@ export class ObjectStackClient { * Metadata Operations */ meta = { + /** + * Get all available metadata types + * Returns types like 'object', 'plugin', 'view', etc. + */ + getTypes: async (): Promise => { + const route = this.getRoute('metadata'); + const res = await this.fetch(`${this.baseUrl}${route}`); + return res.json(); + }, + + /** + * Get all items of a specific metadata type + * @param type - Metadata type name (e.g., 'object', 'plugin') + */ + getItems: async (type: string): Promise => { + const route = this.getRoute('metadata'); + const res = await this.fetch(`${this.baseUrl}${route}/${type}`); + return res.json(); + }, + + /** + * Get a specific object definition by name + * Legacy method - prefer getItem for consistency + */ getObject: async (name: string) => { const route = this.getRoute('metadata'); const res = await this.fetch(`${this.baseUrl}${route}/object/${name}`); return res.json(); }, + + /** + * Get a specific metadata item by type and name + * @param type - Metadata type (e.g., 'object', 'plugin') + * @param name - Item name (snake_case identifier) + */ + getItem: async (type: string, name: string) => { + const route = this.getRoute('metadata'); + const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`); + return res.json(); + }, /** * Get object metadata with cache support @@ -436,5 +473,7 @@ export type { MetadataCacheResponse, StandardErrorCode, ErrorCategory, - GetDiscoveryResponse + GetDiscoveryResponse, + GetMetaTypesResponse, + GetMetaItemsResponse } from '@objectstack/spec/api'; From 7f003f4503c416097951c72ddd635aab9045e8a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 08:59:51 +0000 Subject: [PATCH 4/4] Add @deprecated JSDoc tag to getObject method Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- packages/client/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index bfbb9c402..a657568b5 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -136,7 +136,8 @@ export class ObjectStackClient { /** * Get a specific object definition by name - * Legacy method - prefer getItem for consistency + * @deprecated Use `getItem('object', name)` instead for consistency with spec protocol + * @param name - Object name (snake_case identifier) */ getObject: async (name: string) => { const route = this.getRoute('metadata');