@objectstack/client 0.8.1 → 0.9.0
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/CHANGELOG.md +8 -0
- package/dist/index.d.ts +40 -51
- package/dist/index.js +51 -110
- package/package.json +7 -5
- package/src/client.test.ts +91 -0
- package/src/index.ts +67 -138
- package/tsconfig.json +2 -1
package/CHANGELOG.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { QueryAST, SortNode, AggregationNode } from '@objectstack/spec/data';
|
|
2
|
-
import { BatchUpdateRequest, BatchUpdateResponse, BatchOptions, MetadataCacheRequest, MetadataCacheResponse, StandardErrorCode, ErrorCategory,
|
|
2
|
+
import { BatchUpdateRequest, BatchUpdateResponse, BatchOptions, MetadataCacheRequest, MetadataCacheResponse, StandardErrorCode, ErrorCategory, GetDiscoveryResponse, GetMetaTypesResponse, GetMetaItemsResponse } from '@objectstack/spec/api';
|
|
3
3
|
import { Logger } from '@objectstack/core';
|
|
4
4
|
export interface ClientConfig {
|
|
5
5
|
baseUrl: string;
|
|
@@ -17,16 +17,11 @@ export interface ClientConfig {
|
|
|
17
17
|
*/
|
|
18
18
|
debug?: boolean;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
auth: string;
|
|
26
|
-
ui: string;
|
|
27
|
-
};
|
|
28
|
-
capabilities?: Record<string, boolean>;
|
|
29
|
-
}
|
|
20
|
+
/**
|
|
21
|
+
* Discovery Result
|
|
22
|
+
* Re-export from @objectstack/spec/api for convenience
|
|
23
|
+
*/
|
|
24
|
+
export type DiscoveryResult = GetDiscoveryResponse;
|
|
30
25
|
export interface QueryOptions {
|
|
31
26
|
select?: string[];
|
|
32
27
|
filters?: Record<string, any>;
|
|
@@ -52,18 +47,44 @@ export declare class ObjectStackClient {
|
|
|
52
47
|
private baseUrl;
|
|
53
48
|
private token?;
|
|
54
49
|
private fetchImpl;
|
|
55
|
-
private
|
|
50
|
+
private discoveryInfo?;
|
|
56
51
|
private logger;
|
|
57
52
|
constructor(config: ClientConfig);
|
|
58
53
|
/**
|
|
59
|
-
* Initialize the client by discovering server capabilities
|
|
54
|
+
* Initialize the client by discovering server capabilities.
|
|
60
55
|
*/
|
|
61
|
-
connect(): Promise<
|
|
56
|
+
connect(): Promise<{
|
|
57
|
+
version: string;
|
|
58
|
+
apiName: string;
|
|
59
|
+
capabilities?: string[] | undefined;
|
|
60
|
+
endpoints?: Record<string, string> | undefined;
|
|
61
|
+
}>;
|
|
62
62
|
/**
|
|
63
63
|
* Metadata Operations
|
|
64
64
|
*/
|
|
65
65
|
meta: {
|
|
66
|
+
/**
|
|
67
|
+
* Get all available metadata types
|
|
68
|
+
* Returns types like 'object', 'plugin', 'view', etc.
|
|
69
|
+
*/
|
|
70
|
+
getTypes: () => Promise<GetMetaTypesResponse>;
|
|
71
|
+
/**
|
|
72
|
+
* Get all items of a specific metadata type
|
|
73
|
+
* @param type - Metadata type name (e.g., 'object', 'plugin')
|
|
74
|
+
*/
|
|
75
|
+
getItems: (type: string) => Promise<GetMetaItemsResponse>;
|
|
76
|
+
/**
|
|
77
|
+
* Get a specific object definition by name
|
|
78
|
+
* @deprecated Use `getItem('object', name)` instead for consistency with spec protocol
|
|
79
|
+
* @param name - Object name (snake_case identifier)
|
|
80
|
+
*/
|
|
66
81
|
getObject: (name: string) => Promise<any>;
|
|
82
|
+
/**
|
|
83
|
+
* Get a specific metadata item by type and name
|
|
84
|
+
* @param type - Metadata type (e.g., 'object', 'plugin')
|
|
85
|
+
* @param name - Item name (snake_case identifier)
|
|
86
|
+
*/
|
|
87
|
+
getItem: (type: string, name: string) => Promise<any>;
|
|
67
88
|
/**
|
|
68
89
|
* Get object metadata with cache support
|
|
69
90
|
* Supports ETag-based conditional requests for efficient caching
|
|
@@ -106,48 +127,16 @@ export declare class ObjectStackClient {
|
|
|
106
127
|
*/
|
|
107
128
|
deleteMany: (object: string, ids: string[], options?: BatchOptions) => Promise<BatchUpdateResponse>;
|
|
108
129
|
};
|
|
109
|
-
/**
|
|
110
|
-
* View Storage Operations
|
|
111
|
-
* Save, load, and manage UI view configurations
|
|
112
|
-
*/
|
|
113
|
-
views: {
|
|
114
|
-
/**
|
|
115
|
-
* Create a new saved view
|
|
116
|
-
*/
|
|
117
|
-
create: (request: CreateViewRequest) => Promise<ViewResponse>;
|
|
118
|
-
/**
|
|
119
|
-
* Get a saved view by ID
|
|
120
|
-
*/
|
|
121
|
-
get: (id: string) => Promise<ViewResponse>;
|
|
122
|
-
/**
|
|
123
|
-
* List saved views with optional filters
|
|
124
|
-
*/
|
|
125
|
-
list: (request?: ListViewsRequest) => Promise<ListViewsResponse>;
|
|
126
|
-
/**
|
|
127
|
-
* Update an existing view
|
|
128
|
-
*/
|
|
129
|
-
update: (request: UpdateViewRequest) => Promise<ViewResponse>;
|
|
130
|
-
/**
|
|
131
|
-
* Delete a saved view
|
|
132
|
-
*/
|
|
133
|
-
delete: (id: string) => Promise<{
|
|
134
|
-
success: boolean;
|
|
135
|
-
}>;
|
|
136
|
-
/**
|
|
137
|
-
* Share a view with users/teams
|
|
138
|
-
*/
|
|
139
|
-
share: (id: string, userIds: string[]) => Promise<ViewResponse>;
|
|
140
|
-
/**
|
|
141
|
-
* Set a view as default for an object
|
|
142
|
-
*/
|
|
143
|
-
setDefault: (id: string, object: string) => Promise<ViewResponse>;
|
|
144
|
-
};
|
|
145
130
|
/**
|
|
146
131
|
* Private Helpers
|
|
147
132
|
*/
|
|
148
133
|
private isFilterAST;
|
|
149
134
|
private fetch;
|
|
135
|
+
/**
|
|
136
|
+
* Get the conventional route path for a given API endpoint type
|
|
137
|
+
* ObjectStack uses standard conventions: /api/v1/data, /api/v1/meta, /api/v1/ui
|
|
138
|
+
*/
|
|
150
139
|
private getRoute;
|
|
151
140
|
}
|
|
152
141
|
export { QueryBuilder, FilterBuilder, createQuery, createFilter } from './query-builder';
|
|
153
|
-
export type { BatchUpdateRequest, BatchUpdateResponse, UpdateManyRequest, DeleteManyRequest, BatchOptions, BatchRecord, BatchOperationResult, MetadataCacheRequest, MetadataCacheResponse, StandardErrorCode, ErrorCategory,
|
|
142
|
+
export type { BatchUpdateRequest, BatchUpdateResponse, UpdateManyRequest, DeleteManyRequest, BatchOptions, BatchRecord, BatchOperationResult, MetadataCacheRequest, MetadataCacheResponse, StandardErrorCode, ErrorCategory, GetDiscoveryResponse, GetMetaTypesResponse, GetMetaItemsResponse } from '@objectstack/spec/api';
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,44 @@ export class ObjectStackClient {
|
|
|
5
5
|
* Metadata Operations
|
|
6
6
|
*/
|
|
7
7
|
this.meta = {
|
|
8
|
+
/**
|
|
9
|
+
* Get all available metadata types
|
|
10
|
+
* Returns types like 'object', 'plugin', 'view', etc.
|
|
11
|
+
*/
|
|
12
|
+
getTypes: async () => {
|
|
13
|
+
const route = this.getRoute('metadata');
|
|
14
|
+
const res = await this.fetch(`${this.baseUrl}${route}`);
|
|
15
|
+
return res.json();
|
|
16
|
+
},
|
|
17
|
+
/**
|
|
18
|
+
* Get all items of a specific metadata type
|
|
19
|
+
* @param type - Metadata type name (e.g., 'object', 'plugin')
|
|
20
|
+
*/
|
|
21
|
+
getItems: async (type) => {
|
|
22
|
+
const route = this.getRoute('metadata');
|
|
23
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${type}`);
|
|
24
|
+
return res.json();
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* Get a specific object definition by name
|
|
28
|
+
* @deprecated Use `getItem('object', name)` instead for consistency with spec protocol
|
|
29
|
+
* @param name - Object name (snake_case identifier)
|
|
30
|
+
*/
|
|
8
31
|
getObject: async (name) => {
|
|
9
32
|
const route = this.getRoute('metadata');
|
|
10
33
|
const res = await this.fetch(`${this.baseUrl}${route}/object/${name}`);
|
|
11
34
|
return res.json();
|
|
12
35
|
},
|
|
36
|
+
/**
|
|
37
|
+
* Get a specific metadata item by type and name
|
|
38
|
+
* @param type - Metadata type (e.g., 'object', 'plugin')
|
|
39
|
+
* @param name - Item name (snake_case identifier)
|
|
40
|
+
*/
|
|
41
|
+
getItem: async (type, name) => {
|
|
42
|
+
const route = this.getRoute('metadata');
|
|
43
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`);
|
|
44
|
+
return res.json();
|
|
45
|
+
},
|
|
13
46
|
/**
|
|
14
47
|
* Get object metadata with cache support
|
|
15
48
|
* Supports ETag-based conditional requests for efficient caching
|
|
@@ -201,101 +234,6 @@ export class ObjectStackClient {
|
|
|
201
234
|
return res.json();
|
|
202
235
|
}
|
|
203
236
|
};
|
|
204
|
-
/**
|
|
205
|
-
* View Storage Operations
|
|
206
|
-
* Save, load, and manage UI view configurations
|
|
207
|
-
*/
|
|
208
|
-
this.views = {
|
|
209
|
-
/**
|
|
210
|
-
* Create a new saved view
|
|
211
|
-
*/
|
|
212
|
-
create: async (request) => {
|
|
213
|
-
const route = this.getRoute('ui');
|
|
214
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views`, {
|
|
215
|
-
method: 'POST',
|
|
216
|
-
body: JSON.stringify(request)
|
|
217
|
-
});
|
|
218
|
-
return res.json();
|
|
219
|
-
},
|
|
220
|
-
/**
|
|
221
|
-
* Get a saved view by ID
|
|
222
|
-
*/
|
|
223
|
-
get: async (id) => {
|
|
224
|
-
const route = this.getRoute('ui');
|
|
225
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}`);
|
|
226
|
-
return res.json();
|
|
227
|
-
},
|
|
228
|
-
/**
|
|
229
|
-
* List saved views with optional filters
|
|
230
|
-
*/
|
|
231
|
-
list: async (request) => {
|
|
232
|
-
const route = this.getRoute('ui');
|
|
233
|
-
const queryParams = new URLSearchParams();
|
|
234
|
-
if (request?.object)
|
|
235
|
-
queryParams.set('object', request.object);
|
|
236
|
-
if (request?.type)
|
|
237
|
-
queryParams.set('type', request.type);
|
|
238
|
-
if (request?.visibility)
|
|
239
|
-
queryParams.set('visibility', request.visibility);
|
|
240
|
-
if (request?.createdBy)
|
|
241
|
-
queryParams.set('createdBy', request.createdBy);
|
|
242
|
-
if (request?.isDefault !== undefined)
|
|
243
|
-
queryParams.set('isDefault', String(request.isDefault));
|
|
244
|
-
if (request?.limit)
|
|
245
|
-
queryParams.set('limit', String(request.limit));
|
|
246
|
-
if (request?.offset)
|
|
247
|
-
queryParams.set('offset', String(request.offset));
|
|
248
|
-
const url = queryParams.toString()
|
|
249
|
-
? `${this.baseUrl}${route}/views?${queryParams.toString()}`
|
|
250
|
-
: `${this.baseUrl}${route}/views`;
|
|
251
|
-
const res = await this.fetch(url);
|
|
252
|
-
return res.json();
|
|
253
|
-
},
|
|
254
|
-
/**
|
|
255
|
-
* Update an existing view
|
|
256
|
-
*/
|
|
257
|
-
update: async (request) => {
|
|
258
|
-
const route = this.getRoute('ui');
|
|
259
|
-
const { id, ...updateData } = request;
|
|
260
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}`, {
|
|
261
|
-
method: 'PATCH',
|
|
262
|
-
body: JSON.stringify(updateData)
|
|
263
|
-
});
|
|
264
|
-
return res.json();
|
|
265
|
-
},
|
|
266
|
-
/**
|
|
267
|
-
* Delete a saved view
|
|
268
|
-
*/
|
|
269
|
-
delete: async (id) => {
|
|
270
|
-
const route = this.getRoute('ui');
|
|
271
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}`, {
|
|
272
|
-
method: 'DELETE'
|
|
273
|
-
});
|
|
274
|
-
return res.json();
|
|
275
|
-
},
|
|
276
|
-
/**
|
|
277
|
-
* Share a view with users/teams
|
|
278
|
-
*/
|
|
279
|
-
share: async (id, userIds) => {
|
|
280
|
-
const route = this.getRoute('ui');
|
|
281
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}/share`, {
|
|
282
|
-
method: 'POST',
|
|
283
|
-
body: JSON.stringify({ sharedWith: userIds })
|
|
284
|
-
});
|
|
285
|
-
return res.json();
|
|
286
|
-
},
|
|
287
|
-
/**
|
|
288
|
-
* Set a view as default for an object
|
|
289
|
-
*/
|
|
290
|
-
setDefault: async (id, object) => {
|
|
291
|
-
const route = this.getRoute('ui');
|
|
292
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}/set-default`, {
|
|
293
|
-
method: 'POST',
|
|
294
|
-
body: JSON.stringify({ object })
|
|
295
|
-
});
|
|
296
|
-
return res.json();
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
237
|
this.baseUrl = config.baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
300
238
|
this.token = config.token;
|
|
301
239
|
this.fetchImpl = config.fetch || globalThis.fetch.bind(globalThis);
|
|
@@ -307,18 +245,18 @@ export class ObjectStackClient {
|
|
|
307
245
|
this.logger.debug('ObjectStack client created', { baseUrl: this.baseUrl });
|
|
308
246
|
}
|
|
309
247
|
/**
|
|
310
|
-
* Initialize the client by discovering server capabilities
|
|
248
|
+
* Initialize the client by discovering server capabilities.
|
|
311
249
|
*/
|
|
312
250
|
async connect() {
|
|
313
251
|
this.logger.debug('Connecting to ObjectStack server', { baseUrl: this.baseUrl });
|
|
314
252
|
try {
|
|
315
|
-
// Connect to the discovery endpoint
|
|
316
|
-
// During boot, we might not know routes, so we check convention /api/v1 first
|
|
253
|
+
// Connect to the discovery endpoint at /api/v1
|
|
317
254
|
const res = await this.fetch(`${this.baseUrl}/api/v1`);
|
|
318
255
|
const data = await res.json();
|
|
319
|
-
this.
|
|
256
|
+
this.discoveryInfo = data;
|
|
320
257
|
this.logger.info('Connected to ObjectStack server', {
|
|
321
|
-
|
|
258
|
+
version: data.version,
|
|
259
|
+
apiName: data.apiName,
|
|
322
260
|
capabilities: data.capabilities
|
|
323
261
|
});
|
|
324
262
|
return data;
|
|
@@ -385,16 +323,19 @@ export class ObjectStackClient {
|
|
|
385
323
|
}
|
|
386
324
|
return res;
|
|
387
325
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Get the conventional route path for a given API endpoint type
|
|
328
|
+
* ObjectStack uses standard conventions: /api/v1/data, /api/v1/meta, /api/v1/ui
|
|
329
|
+
*/
|
|
330
|
+
getRoute(type) {
|
|
331
|
+
// Use conventional ObjectStack API paths
|
|
332
|
+
const routeMap = {
|
|
333
|
+
data: '/api/v1/data',
|
|
334
|
+
metadata: '/api/v1/meta',
|
|
335
|
+
ui: '/api/v1/ui',
|
|
336
|
+
auth: '/api/v1/auth'
|
|
337
|
+
};
|
|
338
|
+
return routeMap[type] || `/api/v1/${type}`;
|
|
398
339
|
}
|
|
399
340
|
}
|
|
400
341
|
// Re-export type-safe query builder
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Official Client SDK for ObjectStack Protocol",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@objectstack/spec": "0.
|
|
9
|
-
"@objectstack/core": "0.
|
|
8
|
+
"@objectstack/spec": "0.9.0",
|
|
9
|
+
"@objectstack/core": "0.9.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"typescript": "^5.0.0"
|
|
12
|
+
"typescript": "^5.0.0",
|
|
13
|
+
"vitest": "^4.0.18"
|
|
13
14
|
},
|
|
14
15
|
"scripts": {
|
|
15
|
-
"build": "tsc"
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"test": "vitest run"
|
|
16
18
|
}
|
|
17
19
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { ObjectStackClient } from './index';
|
|
3
|
+
|
|
4
|
+
describe('ObjectStackClient', () => {
|
|
5
|
+
it('should initialize with correct configuration', () => {
|
|
6
|
+
const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000' });
|
|
7
|
+
expect(client).toBeDefined();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should normalize base URL', () => {
|
|
11
|
+
const client: any = new ObjectStackClient({ baseUrl: 'http://localhost:3000/' });
|
|
12
|
+
expect(client.baseUrl).toBe('http://localhost:3000');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should make discovery request on connect', async () => {
|
|
16
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
17
|
+
ok: true,
|
|
18
|
+
json: async () => ({
|
|
19
|
+
version: 'v1',
|
|
20
|
+
apiName: 'ObjectStack',
|
|
21
|
+
capabilities: ['metadata', 'data', 'ui'],
|
|
22
|
+
endpoints: {}
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const client = new ObjectStackClient({
|
|
27
|
+
baseUrl: 'http://localhost:3000',
|
|
28
|
+
fetch: fetchMock
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await client.connect();
|
|
32
|
+
expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1', expect.any(Object));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should get metadata types', async () => {
|
|
36
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
37
|
+
ok: true,
|
|
38
|
+
json: async () => ({
|
|
39
|
+
types: ['object', 'plugin', 'view']
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const client = new ObjectStackClient({
|
|
44
|
+
baseUrl: 'http://localhost:3000',
|
|
45
|
+
fetch: fetchMock
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const result = await client.meta.getTypes();
|
|
49
|
+
expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta', expect.any(Object));
|
|
50
|
+
expect(result.types).toEqual(['object', 'plugin', 'view']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should get metadata items by type', async () => {
|
|
54
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
55
|
+
ok: true,
|
|
56
|
+
json: async () => ({
|
|
57
|
+
type: 'object',
|
|
58
|
+
items: [{ name: 'customer' }, { name: 'order' }]
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const client = new ObjectStackClient({
|
|
63
|
+
baseUrl: 'http://localhost:3000',
|
|
64
|
+
fetch: fetchMock
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const result = await client.meta.getItems('object');
|
|
68
|
+
expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta/object', expect.any(Object));
|
|
69
|
+
expect(result.type).toBe('object');
|
|
70
|
+
expect(result.items).toHaveLength(2);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should get metadata item by type and name', async () => {
|
|
74
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
75
|
+
ok: true,
|
|
76
|
+
json: async () => ({
|
|
77
|
+
name: 'customer',
|
|
78
|
+
fields: []
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const client = new ObjectStackClient({
|
|
83
|
+
baseUrl: 'http://localhost:3000',
|
|
84
|
+
fetch: fetchMock
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const result = await client.meta.getItem('object', 'customer');
|
|
88
|
+
expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta/object/customer', expect.any(Object));
|
|
89
|
+
expect(result.name).toBe('customer');
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -9,12 +9,9 @@ import {
|
|
|
9
9
|
MetadataCacheResponse,
|
|
10
10
|
StandardErrorCode,
|
|
11
11
|
ErrorCategory,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
SavedView,
|
|
16
|
-
ListViewsResponse,
|
|
17
|
-
ViewResponse
|
|
12
|
+
GetDiscoveryResponse,
|
|
13
|
+
GetMetaTypesResponse,
|
|
14
|
+
GetMetaItemsResponse
|
|
18
15
|
} from '@objectstack/spec/api';
|
|
19
16
|
import { Logger, createLogger } from '@objectstack/core';
|
|
20
17
|
|
|
@@ -35,16 +32,11 @@ export interface ClientConfig {
|
|
|
35
32
|
debug?: boolean;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
auth: string;
|
|
44
|
-
ui: string;
|
|
45
|
-
};
|
|
46
|
-
capabilities?: Record<string, boolean>;
|
|
47
|
-
}
|
|
35
|
+
/**
|
|
36
|
+
* Discovery Result
|
|
37
|
+
* Re-export from @objectstack/spec/api for convenience
|
|
38
|
+
*/
|
|
39
|
+
export type DiscoveryResult = GetDiscoveryResponse;
|
|
48
40
|
|
|
49
41
|
export interface QueryOptions {
|
|
50
42
|
select?: string[]; // Simplified Selection
|
|
@@ -75,7 +67,7 @@ export class ObjectStackClient {
|
|
|
75
67
|
private baseUrl: string;
|
|
76
68
|
private token?: string;
|
|
77
69
|
private fetchImpl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
78
|
-
private
|
|
70
|
+
private discoveryInfo?: DiscoveryResult;
|
|
79
71
|
private logger: Logger;
|
|
80
72
|
|
|
81
73
|
constructor(config: ClientConfig) {
|
|
@@ -93,21 +85,21 @@ export class ObjectStackClient {
|
|
|
93
85
|
}
|
|
94
86
|
|
|
95
87
|
/**
|
|
96
|
-
* Initialize the client by discovering server capabilities
|
|
88
|
+
* Initialize the client by discovering server capabilities.
|
|
97
89
|
*/
|
|
98
90
|
async connect() {
|
|
99
91
|
this.logger.debug('Connecting to ObjectStack server', { baseUrl: this.baseUrl });
|
|
100
92
|
|
|
101
93
|
try {
|
|
102
|
-
// Connect to the discovery endpoint
|
|
103
|
-
// During boot, we might not know routes, so we check convention /api/v1 first
|
|
94
|
+
// Connect to the discovery endpoint at /api/v1
|
|
104
95
|
const res = await this.fetch(`${this.baseUrl}/api/v1`);
|
|
105
96
|
|
|
106
97
|
const data = await res.json();
|
|
107
|
-
this.
|
|
98
|
+
this.discoveryInfo = data;
|
|
108
99
|
|
|
109
100
|
this.logger.info('Connected to ObjectStack server', {
|
|
110
|
-
|
|
101
|
+
version: data.version,
|
|
102
|
+
apiName: data.apiName,
|
|
111
103
|
capabilities: data.capabilities
|
|
112
104
|
});
|
|
113
105
|
|
|
@@ -122,11 +114,47 @@ export class ObjectStackClient {
|
|
|
122
114
|
* Metadata Operations
|
|
123
115
|
*/
|
|
124
116
|
meta = {
|
|
117
|
+
/**
|
|
118
|
+
* Get all available metadata types
|
|
119
|
+
* Returns types like 'object', 'plugin', 'view', etc.
|
|
120
|
+
*/
|
|
121
|
+
getTypes: async (): Promise<GetMetaTypesResponse> => {
|
|
122
|
+
const route = this.getRoute('metadata');
|
|
123
|
+
const res = await this.fetch(`${this.baseUrl}${route}`);
|
|
124
|
+
return res.json();
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get all items of a specific metadata type
|
|
129
|
+
* @param type - Metadata type name (e.g., 'object', 'plugin')
|
|
130
|
+
*/
|
|
131
|
+
getItems: async (type: string): Promise<GetMetaItemsResponse> => {
|
|
132
|
+
const route = this.getRoute('metadata');
|
|
133
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${type}`);
|
|
134
|
+
return res.json();
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get a specific object definition by name
|
|
139
|
+
* @deprecated Use `getItem('object', name)` instead for consistency with spec protocol
|
|
140
|
+
* @param name - Object name (snake_case identifier)
|
|
141
|
+
*/
|
|
125
142
|
getObject: async (name: string) => {
|
|
126
143
|
const route = this.getRoute('metadata');
|
|
127
144
|
const res = await this.fetch(`${this.baseUrl}${route}/object/${name}`);
|
|
128
145
|
return res.json();
|
|
129
146
|
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get a specific metadata item by type and name
|
|
150
|
+
* @param type - Metadata type (e.g., 'object', 'plugin')
|
|
151
|
+
* @param name - Item name (snake_case identifier)
|
|
152
|
+
*/
|
|
153
|
+
getItem: async (type: string, name: string) => {
|
|
154
|
+
const route = this.getRoute('metadata');
|
|
155
|
+
const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`);
|
|
156
|
+
return res.json();
|
|
157
|
+
},
|
|
130
158
|
|
|
131
159
|
/**
|
|
132
160
|
* Get object metadata with cache support
|
|
@@ -342,103 +370,7 @@ export class ObjectStackClient {
|
|
|
342
370
|
}
|
|
343
371
|
};
|
|
344
372
|
|
|
345
|
-
/**
|
|
346
|
-
* View Storage Operations
|
|
347
|
-
* Save, load, and manage UI view configurations
|
|
348
|
-
*/
|
|
349
|
-
views = {
|
|
350
|
-
/**
|
|
351
|
-
* Create a new saved view
|
|
352
|
-
*/
|
|
353
|
-
create: async (request: CreateViewRequest): Promise<ViewResponse> => {
|
|
354
|
-
const route = this.getRoute('ui');
|
|
355
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views`, {
|
|
356
|
-
method: 'POST',
|
|
357
|
-
body: JSON.stringify(request)
|
|
358
|
-
});
|
|
359
|
-
return res.json();
|
|
360
|
-
},
|
|
361
373
|
|
|
362
|
-
/**
|
|
363
|
-
* Get a saved view by ID
|
|
364
|
-
*/
|
|
365
|
-
get: async (id: string): Promise<ViewResponse> => {
|
|
366
|
-
const route = this.getRoute('ui');
|
|
367
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}`);
|
|
368
|
-
return res.json();
|
|
369
|
-
},
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* List saved views with optional filters
|
|
373
|
-
*/
|
|
374
|
-
list: async (request?: ListViewsRequest): Promise<ListViewsResponse> => {
|
|
375
|
-
const route = this.getRoute('ui');
|
|
376
|
-
const queryParams = new URLSearchParams();
|
|
377
|
-
|
|
378
|
-
if (request?.object) queryParams.set('object', request.object);
|
|
379
|
-
if (request?.type) queryParams.set('type', request.type);
|
|
380
|
-
if (request?.visibility) queryParams.set('visibility', request.visibility);
|
|
381
|
-
if (request?.createdBy) queryParams.set('createdBy', request.createdBy);
|
|
382
|
-
if (request?.isDefault !== undefined) queryParams.set('isDefault', String(request.isDefault));
|
|
383
|
-
if (request?.limit) queryParams.set('limit', String(request.limit));
|
|
384
|
-
if (request?.offset) queryParams.set('offset', String(request.offset));
|
|
385
|
-
|
|
386
|
-
const url = queryParams.toString()
|
|
387
|
-
? `${this.baseUrl}${route}/views?${queryParams.toString()}`
|
|
388
|
-
: `${this.baseUrl}${route}/views`;
|
|
389
|
-
|
|
390
|
-
const res = await this.fetch(url);
|
|
391
|
-
return res.json();
|
|
392
|
-
},
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Update an existing view
|
|
396
|
-
*/
|
|
397
|
-
update: async (request: UpdateViewRequest): Promise<ViewResponse> => {
|
|
398
|
-
const route = this.getRoute('ui');
|
|
399
|
-
const { id, ...updateData } = request;
|
|
400
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}`, {
|
|
401
|
-
method: 'PATCH',
|
|
402
|
-
body: JSON.stringify(updateData)
|
|
403
|
-
});
|
|
404
|
-
return res.json();
|
|
405
|
-
},
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Delete a saved view
|
|
409
|
-
*/
|
|
410
|
-
delete: async (id: string): Promise<{ success: boolean }> => {
|
|
411
|
-
const route = this.getRoute('ui');
|
|
412
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}`, {
|
|
413
|
-
method: 'DELETE'
|
|
414
|
-
});
|
|
415
|
-
return res.json();
|
|
416
|
-
},
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Share a view with users/teams
|
|
420
|
-
*/
|
|
421
|
-
share: async (id: string, userIds: string[]): Promise<ViewResponse> => {
|
|
422
|
-
const route = this.getRoute('ui');
|
|
423
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}/share`, {
|
|
424
|
-
method: 'POST',
|
|
425
|
-
body: JSON.stringify({ sharedWith: userIds })
|
|
426
|
-
});
|
|
427
|
-
return res.json();
|
|
428
|
-
},
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Set a view as default for an object
|
|
432
|
-
*/
|
|
433
|
-
setDefault: async (id: string, object: string): Promise<ViewResponse> => {
|
|
434
|
-
const route = this.getRoute('ui');
|
|
435
|
-
const res = await this.fetch(`${this.baseUrl}${route}/views/${id}/set-default`, {
|
|
436
|
-
method: 'POST',
|
|
437
|
-
body: JSON.stringify({ object })
|
|
438
|
-
});
|
|
439
|
-
return res.json();
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
374
|
|
|
443
375
|
/**
|
|
444
376
|
* Private Helpers
|
|
@@ -509,16 +441,20 @@ export class ObjectStackClient {
|
|
|
509
441
|
return res;
|
|
510
442
|
}
|
|
511
443
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Get the conventional route path for a given API endpoint type
|
|
446
|
+
* ObjectStack uses standard conventions: /api/v1/data, /api/v1/meta, /api/v1/ui
|
|
447
|
+
*/
|
|
448
|
+
private getRoute(type: 'data' | 'metadata' | 'ui' | 'auth'): string {
|
|
449
|
+
// Use conventional ObjectStack API paths
|
|
450
|
+
const routeMap: Record<string, string> = {
|
|
451
|
+
data: '/api/v1/data',
|
|
452
|
+
metadata: '/api/v1/meta',
|
|
453
|
+
ui: '/api/v1/ui',
|
|
454
|
+
auth: '/api/v1/auth'
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
return routeMap[type] || `/api/v1/${type}`;
|
|
522
458
|
}
|
|
523
459
|
}
|
|
524
460
|
|
|
@@ -538,14 +474,7 @@ export type {
|
|
|
538
474
|
MetadataCacheResponse,
|
|
539
475
|
StandardErrorCode,
|
|
540
476
|
ErrorCategory,
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
SavedView,
|
|
545
|
-
ViewResponse,
|
|
546
|
-
ListViewsResponse,
|
|
547
|
-
ViewType,
|
|
548
|
-
ViewVisibility,
|
|
549
|
-
ViewColumn,
|
|
550
|
-
ViewLayout
|
|
477
|
+
GetDiscoveryResponse,
|
|
478
|
+
GetMetaTypesResponse,
|
|
479
|
+
GetMetaItemsResponse
|
|
551
480
|
} from '@objectstack/spec/api';
|