@objectstack/client 0.9.2 → 1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @objectstack/client
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @objectstack/spec@1.0.1
8
+ - @objectstack/core@1.0.1
9
+
10
+ ## 1.0.0
11
+
12
+ ### Major Changes
13
+
14
+ - Major version release for ObjectStack Protocol v1.0.
15
+ - Stabilized Protocol Definitions
16
+ - Enhanced Runtime Plugin Support
17
+ - Fixed Type Compliance across Monorepo
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+ - @objectstack/spec@1.0.0
23
+ - @objectstack/core@1.0.0
24
+
3
25
  ## 0.9.2
4
26
 
5
27
  ### Patch Changes
package/README.md CHANGED
@@ -47,6 +47,12 @@ async function main() {
47
47
  // 3. Metadata Access
48
48
  const todoSchema = await client.meta.getObject('todo_task');
49
49
  console.log('Fields:', todoSchema.fields);
50
+
51
+ // Save Metadata (New Feature)
52
+ await client.meta.saveItem('object', 'my_custom_object', {
53
+ label: 'My Object',
54
+ fields: { name: { type: 'text' } }
55
+ });
50
56
 
51
57
  // 4. Advanced Query
52
58
  const tasks = await client.data.find<{ subject: string; priority: number }>('todo_task', {
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, GetDiscoveryResponse, GetMetaTypesResponse, GetMetaItemsResponse } from '@objectstack/spec/api';
2
+ import { BatchUpdateRequest, BatchUpdateResponse, BatchOptions, MetadataCacheRequest, MetadataCacheResponse, StandardErrorCode, ErrorCategory, GetDiscoveryResponse, GetMetaTypesResponse, GetMetaItemsResponse, LoginRequest, SessionResponse, FileUploadResponse } from '@objectstack/spec/api';
3
3
  import { Logger } from '@objectstack/core';
4
4
  export interface ClientConfig {
5
5
  baseUrl: string;
@@ -56,8 +56,24 @@ export declare class ObjectStackClient {
56
56
  connect(): Promise<{
57
57
  version: string;
58
58
  apiName: string;
59
- capabilities?: string[] | undefined;
60
- endpoints?: Record<string, string> | undefined;
59
+ capabilities?: {
60
+ search: boolean;
61
+ files: boolean;
62
+ graphql: boolean;
63
+ analytics: boolean;
64
+ hub: boolean;
65
+ websockets: boolean;
66
+ } | undefined;
67
+ endpoints?: {
68
+ data: string;
69
+ metadata: string;
70
+ auth: string;
71
+ graphql?: string | undefined;
72
+ storage?: string | undefined;
73
+ automation?: string | undefined;
74
+ analytics?: string | undefined;
75
+ hub?: string | undefined;
76
+ } | undefined;
61
77
  }>;
62
78
  /**
63
79
  * Metadata Operations
@@ -85,6 +101,13 @@ export declare class ObjectStackClient {
85
101
  * @param name - Item name (snake_case identifier)
86
102
  */
87
103
  getItem: (type: string, name: string) => Promise<any>;
104
+ /**
105
+ * Save a metadata item
106
+ * @param type - Metadata type (e.g., 'object', 'plugin')
107
+ * @param name - Item name
108
+ * @param item - The metadata content to save
109
+ */
110
+ saveItem: (type: string, name: string, item: any) => Promise<any>;
88
111
  /**
89
112
  * Get object metadata with cache support
90
113
  * Supports ETag-based conditional requests for efficient caching
@@ -92,6 +115,47 @@ export declare class ObjectStackClient {
92
115
  getCached: (name: string, cacheOptions?: MetadataCacheRequest) => Promise<MetadataCacheResponse>;
93
116
  getView: (object: string, type?: "list" | "form") => Promise<any>;
94
117
  };
118
+ /**
119
+ * Analytics Services
120
+ */
121
+ analytics: {
122
+ query: (payload: any) => Promise<any>;
123
+ meta: (cube: string) => Promise<any>;
124
+ explain: (payload: any) => Promise<any>;
125
+ };
126
+ /**
127
+ * Hub Management Services
128
+ */
129
+ hub: {
130
+ spaces: {
131
+ list: () => Promise<any>;
132
+ create: (payload: any) => Promise<any>;
133
+ };
134
+ plugins: {
135
+ install: (pkg: string, version?: string) => Promise<any>;
136
+ };
137
+ };
138
+ /**
139
+ * Authentication Services
140
+ */
141
+ auth: {
142
+ login: (request: LoginRequest) => Promise<SessionResponse>;
143
+ logout: () => Promise<void>;
144
+ me: () => Promise<SessionResponse>;
145
+ };
146
+ /**
147
+ * Storage Services
148
+ */
149
+ storage: {
150
+ upload: (file: any, scope?: string) => Promise<FileUploadResponse>;
151
+ getDownloadUrl: (fileId: string) => Promise<string>;
152
+ };
153
+ /**
154
+ * Automation Services
155
+ */
156
+ automation: {
157
+ trigger: (triggerName: string, payload: any) => Promise<any>;
158
+ };
95
159
  /**
96
160
  * Data Operations
97
161
  */
@@ -134,7 +198,7 @@ export declare class ObjectStackClient {
134
198
  private fetch;
135
199
  /**
136
200
  * 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
201
+ * ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
138
202
  */
139
203
  private getRoute;
140
204
  }
package/dist/index.js CHANGED
@@ -43,6 +43,20 @@ export class ObjectStackClient {
43
43
  const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`);
44
44
  return res.json();
45
45
  },
46
+ /**
47
+ * Save a metadata item
48
+ * @param type - Metadata type (e.g., 'object', 'plugin')
49
+ * @param name - Item name
50
+ * @param item - The metadata content to save
51
+ */
52
+ saveItem: async (type, name, item) => {
53
+ const route = this.getRoute('metadata');
54
+ const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`, {
55
+ method: 'PUT',
56
+ body: JSON.stringify(item)
57
+ });
58
+ return res.json();
59
+ },
46
60
  /**
47
61
  * Get object metadata with cache support
48
62
  * Supports ETag-based conditional requests for efficient caching
@@ -88,6 +102,148 @@ export class ObjectStackClient {
88
102
  return res.json();
89
103
  }
90
104
  };
105
+ /**
106
+ * Analytics Services
107
+ */
108
+ this.analytics = {
109
+ query: async (payload) => {
110
+ const route = this.getRoute('analytics');
111
+ const res = await this.fetch(`${this.baseUrl}${route}/query`, {
112
+ method: 'POST',
113
+ body: JSON.stringify(payload)
114
+ });
115
+ return res.json();
116
+ },
117
+ meta: async (cube) => {
118
+ const route = this.getRoute('analytics');
119
+ const res = await this.fetch(`${this.baseUrl}${route}/meta/${cube}`);
120
+ return res.json();
121
+ },
122
+ explain: async (payload) => {
123
+ const route = this.getRoute('analytics');
124
+ const res = await this.fetch(`${this.baseUrl}${route}/explain`, {
125
+ method: 'POST',
126
+ body: JSON.stringify(payload)
127
+ });
128
+ return res.json();
129
+ }
130
+ };
131
+ /**
132
+ * Hub Management Services
133
+ */
134
+ this.hub = {
135
+ spaces: {
136
+ list: async () => {
137
+ const route = this.getRoute('hub');
138
+ const res = await this.fetch(`${this.baseUrl}${route}/spaces`);
139
+ return res.json();
140
+ },
141
+ create: async (payload) => {
142
+ const route = this.getRoute('hub');
143
+ const res = await this.fetch(`${this.baseUrl}${route}/spaces`, {
144
+ method: 'POST',
145
+ body: JSON.stringify(payload)
146
+ });
147
+ return res.json();
148
+ }
149
+ },
150
+ plugins: {
151
+ install: async (pkg, version) => {
152
+ const route = this.getRoute('hub');
153
+ const res = await this.fetch(`${this.baseUrl}${route}/plugins/install`, {
154
+ method: 'POST',
155
+ body: JSON.stringify({ pkg, version })
156
+ });
157
+ return res.json();
158
+ }
159
+ }
160
+ };
161
+ /**
162
+ * Authentication Services
163
+ */
164
+ this.auth = {
165
+ login: async (request) => {
166
+ const route = this.getRoute('auth');
167
+ const res = await this.fetch(`${this.baseUrl}${route}/login`, {
168
+ method: 'POST',
169
+ body: JSON.stringify(request)
170
+ });
171
+ const data = await res.json();
172
+ // Auto-set token if present in response
173
+ if (data.data?.token) {
174
+ this.token = data.data.token;
175
+ }
176
+ return data;
177
+ },
178
+ logout: async () => {
179
+ const route = this.getRoute('auth');
180
+ await this.fetch(`${this.baseUrl}${route}/logout`, { method: 'POST' });
181
+ this.token = undefined;
182
+ },
183
+ me: async () => {
184
+ const route = this.getRoute('auth');
185
+ const res = await this.fetch(`${this.baseUrl}${route}/me`);
186
+ return res.json();
187
+ }
188
+ };
189
+ /**
190
+ * Storage Services
191
+ */
192
+ this.storage = {
193
+ upload: async (file, scope = 'user') => {
194
+ // 1. Get Presigned URL
195
+ const presignedReq = {
196
+ filename: file.name,
197
+ mimeType: file.type,
198
+ size: file.size,
199
+ scope
200
+ };
201
+ const route = this.getRoute('storage');
202
+ const presignedRes = await this.fetch(`${this.baseUrl}${route}/upload/presigned`, {
203
+ method: 'POST',
204
+ body: JSON.stringify(presignedReq)
205
+ });
206
+ const { data: presigned } = await presignedRes.json();
207
+ // 2. Upload to Cloud directly (Bypass API Middleware to avoid Auth headers if using S3)
208
+ // Use fetchImpl directly
209
+ const uploadRes = await this.fetchImpl(presigned.uploadUrl, {
210
+ method: presigned.method,
211
+ headers: presigned.headers,
212
+ body: file
213
+ });
214
+ if (!uploadRes.ok) {
215
+ throw new Error(`Storage Upload Failed: ${uploadRes.statusText}`);
216
+ }
217
+ // 3. Complete Upload
218
+ const completeReq = {
219
+ fileId: presigned.fileId
220
+ };
221
+ const completeRes = await this.fetch(`${this.baseUrl}${route}/upload/complete`, {
222
+ method: 'POST',
223
+ body: JSON.stringify(completeReq)
224
+ });
225
+ return completeRes.json();
226
+ },
227
+ getDownloadUrl: async (fileId) => {
228
+ const route = this.getRoute('storage');
229
+ const res = await this.fetch(`${this.baseUrl}${route}/files/${fileId}/url`);
230
+ const data = await res.json();
231
+ return data.url;
232
+ }
233
+ };
234
+ /**
235
+ * Automation Services
236
+ */
237
+ this.automation = {
238
+ trigger: async (triggerName, payload) => {
239
+ const route = this.getRoute('automation');
240
+ const res = await this.fetch(`${this.baseUrl}${route}/trigger/${triggerName}`, {
241
+ method: 'POST',
242
+ body: JSON.stringify(payload)
243
+ });
244
+ return res.json();
245
+ }
246
+ };
91
247
  /**
92
248
  * Data Operations
93
249
  */
@@ -325,15 +481,25 @@ export class ObjectStackClient {
325
481
  }
326
482
  /**
327
483
  * 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
484
+ * ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
329
485
  */
330
486
  getRoute(type) {
331
- // Use conventional ObjectStack API paths
487
+ // 1. Use discovered routes if available
488
+ // Note: Spec uses 'endpoints', mapped dynamically
489
+ if (this.discoveryInfo?.endpoints && this.discoveryInfo.endpoints[type]) {
490
+ return this.discoveryInfo.endpoints[type];
491
+ }
492
+ // 2. Fallback to conventions
493
+ // Note: HttpDispatcher expects /metadata, not /meta
332
494
  const routeMap = {
333
495
  data: '/api/v1/data',
334
- metadata: '/api/v1/meta',
496
+ metadata: '/api/v1/metadata',
335
497
  ui: '/api/v1/ui',
336
- auth: '/api/v1/auth'
498
+ auth: '/api/v1/auth',
499
+ analytics: '/api/v1/analytics',
500
+ hub: '/api/v1/hub',
501
+ storage: '/api/v1/storage',
502
+ automation: '/api/v1/automation'
337
503
  };
338
504
  return routeMap[type] || `/api/v1/${type}`;
339
505
  }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@objectstack/client",
3
- "version": "0.9.2",
3
+ "version": "1.0.1",
4
+ "license": "Apache-2.0",
4
5
  "description": "Official Client SDK for ObjectStack Protocol",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "dependencies": {
8
- "@objectstack/spec": "0.9.2",
9
- "@objectstack/core": "0.9.2"
9
+ "@objectstack/spec": "1.0.1",
10
+ "@objectstack/core": "1.0.1"
10
11
  },
11
12
  "devDependencies": {
12
13
  "typescript": "^5.0.0",
@@ -46,7 +46,7 @@ describe('ObjectStackClient', () => {
46
46
  });
47
47
 
48
48
  const result = await client.meta.getTypes();
49
- expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta', expect.any(Object));
49
+ expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/metadata', expect.any(Object));
50
50
  expect(result.types).toEqual(['object', 'plugin', 'view']);
51
51
  });
52
52
 
@@ -65,7 +65,7 @@ describe('ObjectStackClient', () => {
65
65
  });
66
66
 
67
67
  const result = await client.meta.getItems('object');
68
- expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta/object', expect.any(Object));
68
+ expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/metadata/object', expect.any(Object));
69
69
  expect(result.type).toBe('object');
70
70
  expect(result.items).toHaveLength(2);
71
71
  });
@@ -85,7 +85,7 @@ describe('ObjectStackClient', () => {
85
85
  });
86
86
 
87
87
  const result = await client.meta.getItem('object', 'customer');
88
- expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/meta/object/customer', expect.any(Object));
88
+ expect(fetchMock).toHaveBeenCalledWith('http://localhost:3000/api/v1/metadata/object/customer', expect.any(Object));
89
89
  expect(result.name).toBe('customer');
90
90
  });
91
91
  });
package/src/index.ts CHANGED
@@ -11,7 +11,13 @@ import {
11
11
  ErrorCategory,
12
12
  GetDiscoveryResponse,
13
13
  GetMetaTypesResponse,
14
- GetMetaItemsResponse
14
+ GetMetaItemsResponse,
15
+ LoginRequest,
16
+ SessionResponse,
17
+ GetPresignedUrlRequest,
18
+ PresignedUrlResponse,
19
+ CompleteUploadRequest,
20
+ FileUploadResponse
15
21
  } from '@objectstack/spec/api';
16
22
  import { Logger, createLogger } from '@objectstack/core';
17
23
 
@@ -155,6 +161,21 @@ export class ObjectStackClient {
155
161
  const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`);
156
162
  return res.json();
157
163
  },
164
+
165
+ /**
166
+ * Save a metadata item
167
+ * @param type - Metadata type (e.g., 'object', 'plugin')
168
+ * @param name - Item name
169
+ * @param item - The metadata content to save
170
+ */
171
+ saveItem: async (type: string, name: string, item: any) => {
172
+ const route = this.getRoute('metadata');
173
+ const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`, {
174
+ method: 'PUT',
175
+ body: JSON.stringify(item)
176
+ });
177
+ return res.json();
178
+ },
158
179
 
159
180
  /**
160
181
  * Get object metadata with cache support
@@ -208,6 +229,161 @@ export class ObjectStackClient {
208
229
  }
209
230
  };
210
231
 
232
+ /**
233
+ * Analytics Services
234
+ */
235
+ analytics = {
236
+ query: async (payload: any) => {
237
+ const route = this.getRoute('analytics');
238
+ const res = await this.fetch(`${this.baseUrl}${route}/query`, {
239
+ method: 'POST',
240
+ body: JSON.stringify(payload)
241
+ });
242
+ return res.json();
243
+ },
244
+ meta: async (cube: string) => {
245
+ const route = this.getRoute('analytics');
246
+ const res = await this.fetch(`${this.baseUrl}${route}/meta/${cube}`);
247
+ return res.json();
248
+ },
249
+ explain: async (payload: any) => {
250
+ const route = this.getRoute('analytics');
251
+ const res = await this.fetch(`${this.baseUrl}${route}/explain`, {
252
+ method: 'POST',
253
+ body: JSON.stringify(payload)
254
+ });
255
+ return res.json();
256
+ }
257
+ };
258
+
259
+ /**
260
+ * Hub Management Services
261
+ */
262
+ hub = {
263
+ spaces: {
264
+ list: async () => {
265
+ const route = this.getRoute('hub');
266
+ const res = await this.fetch(`${this.baseUrl}${route}/spaces`);
267
+ return res.json();
268
+ },
269
+ create: async (payload: any) => {
270
+ const route = this.getRoute('hub');
271
+ const res = await this.fetch(`${this.baseUrl}${route}/spaces`, {
272
+ method: 'POST',
273
+ body: JSON.stringify(payload)
274
+ });
275
+ return res.json();
276
+ }
277
+ },
278
+ plugins: {
279
+ install: async (pkg: string, version?: string) => {
280
+ const route = this.getRoute('hub');
281
+ const res = await this.fetch(`${this.baseUrl}${route}/plugins/install`, {
282
+ method: 'POST',
283
+ body: JSON.stringify({ pkg, version })
284
+ });
285
+ return res.json();
286
+ }
287
+ }
288
+ };
289
+
290
+ /**
291
+ * Authentication Services
292
+ */
293
+ auth = {
294
+ login: async (request: LoginRequest): Promise<SessionResponse> => {
295
+ const route = this.getRoute('auth');
296
+ const res = await this.fetch(`${this.baseUrl}${route}/login`, {
297
+ method: 'POST',
298
+ body: JSON.stringify(request)
299
+ });
300
+ const data = await res.json();
301
+ // Auto-set token if present in response
302
+ if (data.data?.token) {
303
+ this.token = data.data.token;
304
+ }
305
+ return data;
306
+ },
307
+
308
+ logout: async () => {
309
+ const route = this.getRoute('auth');
310
+ await this.fetch(`${this.baseUrl}${route}/logout`, { method: 'POST' });
311
+ this.token = undefined;
312
+ },
313
+
314
+ me: async (): Promise<SessionResponse> => {
315
+ const route = this.getRoute('auth');
316
+ const res = await this.fetch(`${this.baseUrl}${route}/me`);
317
+ return res.json();
318
+ }
319
+ };
320
+
321
+ /**
322
+ * Storage Services
323
+ */
324
+ storage = {
325
+ upload: async (file: any, scope: string = 'user'): Promise<FileUploadResponse> => {
326
+ // 1. Get Presigned URL
327
+ const presignedReq: GetPresignedUrlRequest = {
328
+ filename: file.name,
329
+ mimeType: file.type,
330
+ size: file.size,
331
+ scope
332
+ };
333
+
334
+ const route = this.getRoute('storage');
335
+ const presignedRes = await this.fetch(`${this.baseUrl}${route}/upload/presigned`, {
336
+ method: 'POST',
337
+ body: JSON.stringify(presignedReq)
338
+ });
339
+ const { data: presigned } = await presignedRes.json() as { data: PresignedUrlResponse['data'] };
340
+
341
+ // 2. Upload to Cloud directly (Bypass API Middleware to avoid Auth headers if using S3)
342
+ // Use fetchImpl directly
343
+ const uploadRes = await this.fetchImpl(presigned.uploadUrl, {
344
+ method: presigned.method,
345
+ headers: presigned.headers,
346
+ body: file
347
+ });
348
+
349
+ if (!uploadRes.ok) {
350
+ throw new Error(`Storage Upload Failed: ${uploadRes.statusText}`);
351
+ }
352
+
353
+ // 3. Complete Upload
354
+ const completeReq: CompleteUploadRequest = {
355
+ fileId: presigned.fileId
356
+ };
357
+ const completeRes = await this.fetch(`${this.baseUrl}${route}/upload/complete`, {
358
+ method: 'POST',
359
+ body: JSON.stringify(completeReq)
360
+ });
361
+
362
+ return completeRes.json();
363
+ },
364
+
365
+ getDownloadUrl: async (fileId: string): Promise<string> => {
366
+ const route = this.getRoute('storage');
367
+ const res = await this.fetch(`${this.baseUrl}${route}/files/${fileId}/url`);
368
+ const data = await res.json();
369
+ return data.url;
370
+ }
371
+ };
372
+
373
+ /**
374
+ * Automation Services
375
+ */
376
+ automation = {
377
+ trigger: async (triggerName: string, payload: any) => {
378
+ const route = this.getRoute('automation');
379
+ const res = await this.fetch(`${this.baseUrl}${route}/trigger/${triggerName}`, {
380
+ method: 'POST',
381
+ body: JSON.stringify(payload)
382
+ });
383
+ return res.json();
384
+ }
385
+ };
386
+
211
387
  /**
212
388
  * Data Operations
213
389
  */
@@ -443,15 +619,26 @@ export class ObjectStackClient {
443
619
 
444
620
  /**
445
621
  * 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
622
+ * ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
447
623
  */
448
- private getRoute(type: 'data' | 'metadata' | 'ui' | 'auth'): string {
449
- // Use conventional ObjectStack API paths
624
+ private getRoute(type: 'data' | 'metadata' | 'ui' | 'auth' | 'analytics' | 'hub' | 'storage' | 'automation'): string {
625
+ // 1. Use discovered routes if available
626
+ // Note: Spec uses 'endpoints', mapped dynamically
627
+ if (this.discoveryInfo?.endpoints && (this.discoveryInfo.endpoints as any)[type]) {
628
+ return (this.discoveryInfo.endpoints as any)[type];
629
+ }
630
+
631
+ // 2. Fallback to conventions
632
+ // Note: HttpDispatcher expects /metadata, not /meta
450
633
  const routeMap: Record<string, string> = {
451
634
  data: '/api/v1/data',
452
- metadata: '/api/v1/meta',
635
+ metadata: '/api/v1/metadata',
453
636
  ui: '/api/v1/ui',
454
- auth: '/api/v1/auth'
637
+ auth: '/api/v1/auth',
638
+ analytics: '/api/v1/analytics',
639
+ hub: '/api/v1/hub',
640
+ storage: '/api/v1/storage',
641
+ automation: '/api/v1/automation'
455
642
  };
456
643
 
457
644
  return routeMap[type] || `/api/v1/${type}`;