@objectstack/client 0.9.2 → 1.0.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # @objectstack/client
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - Major version release for ObjectStack Protocol v1.0.
8
+ - Stabilized Protocol Definitions
9
+ - Enhanced Runtime Plugin Support
10
+ - Fixed Type Compliance across Monorepo
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+ - @objectstack/spec@1.0.0
16
+ - @objectstack/core@1.0.0
17
+
3
18
  ## 0.9.2
4
19
 
5
20
  ### Patch Changes
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
@@ -92,6 +108,47 @@ export declare class ObjectStackClient {
92
108
  getCached: (name: string, cacheOptions?: MetadataCacheRequest) => Promise<MetadataCacheResponse>;
93
109
  getView: (object: string, type?: "list" | "form") => Promise<any>;
94
110
  };
111
+ /**
112
+ * Analytics Services
113
+ */
114
+ analytics: {
115
+ query: (payload: any) => Promise<any>;
116
+ meta: (cube: string) => Promise<any>;
117
+ explain: (payload: any) => Promise<any>;
118
+ };
119
+ /**
120
+ * Hub Management Services
121
+ */
122
+ hub: {
123
+ spaces: {
124
+ list: () => Promise<any>;
125
+ create: (payload: any) => Promise<any>;
126
+ };
127
+ plugins: {
128
+ install: (pkg: string, version?: string) => Promise<any>;
129
+ };
130
+ };
131
+ /**
132
+ * Authentication Services
133
+ */
134
+ auth: {
135
+ login: (request: LoginRequest) => Promise<SessionResponse>;
136
+ logout: () => Promise<void>;
137
+ me: () => Promise<SessionResponse>;
138
+ };
139
+ /**
140
+ * Storage Services
141
+ */
142
+ storage: {
143
+ upload: (file: any, scope?: string) => Promise<FileUploadResponse>;
144
+ getDownloadUrl: (fileId: string) => Promise<string>;
145
+ };
146
+ /**
147
+ * Automation Services
148
+ */
149
+ automation: {
150
+ trigger: (triggerName: string, payload: any) => Promise<any>;
151
+ };
95
152
  /**
96
153
  * Data Operations
97
154
  */
@@ -134,7 +191,7 @@ export declare class ObjectStackClient {
134
191
  private fetch;
135
192
  /**
136
193
  * 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
194
+ * ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
138
195
  */
139
196
  private getRoute;
140
197
  }
package/dist/index.js CHANGED
@@ -88,6 +88,148 @@ export class ObjectStackClient {
88
88
  return res.json();
89
89
  }
90
90
  };
91
+ /**
92
+ * Analytics Services
93
+ */
94
+ this.analytics = {
95
+ query: async (payload) => {
96
+ const route = this.getRoute('analytics');
97
+ const res = await this.fetch(`${this.baseUrl}${route}/query`, {
98
+ method: 'POST',
99
+ body: JSON.stringify(payload)
100
+ });
101
+ return res.json();
102
+ },
103
+ meta: async (cube) => {
104
+ const route = this.getRoute('analytics');
105
+ const res = await this.fetch(`${this.baseUrl}${route}/meta/${cube}`);
106
+ return res.json();
107
+ },
108
+ explain: async (payload) => {
109
+ const route = this.getRoute('analytics');
110
+ const res = await this.fetch(`${this.baseUrl}${route}/explain`, {
111
+ method: 'POST',
112
+ body: JSON.stringify(payload)
113
+ });
114
+ return res.json();
115
+ }
116
+ };
117
+ /**
118
+ * Hub Management Services
119
+ */
120
+ this.hub = {
121
+ spaces: {
122
+ list: async () => {
123
+ const route = this.getRoute('hub');
124
+ const res = await this.fetch(`${this.baseUrl}${route}/spaces`);
125
+ return res.json();
126
+ },
127
+ create: async (payload) => {
128
+ const route = this.getRoute('hub');
129
+ const res = await this.fetch(`${this.baseUrl}${route}/spaces`, {
130
+ method: 'POST',
131
+ body: JSON.stringify(payload)
132
+ });
133
+ return res.json();
134
+ }
135
+ },
136
+ plugins: {
137
+ install: async (pkg, version) => {
138
+ const route = this.getRoute('hub');
139
+ const res = await this.fetch(`${this.baseUrl}${route}/plugins/install`, {
140
+ method: 'POST',
141
+ body: JSON.stringify({ pkg, version })
142
+ });
143
+ return res.json();
144
+ }
145
+ }
146
+ };
147
+ /**
148
+ * Authentication Services
149
+ */
150
+ this.auth = {
151
+ login: async (request) => {
152
+ const route = this.getRoute('auth');
153
+ const res = await this.fetch(`${this.baseUrl}${route}/login`, {
154
+ method: 'POST',
155
+ body: JSON.stringify(request)
156
+ });
157
+ const data = await res.json();
158
+ // Auto-set token if present in response
159
+ if (data.data?.token) {
160
+ this.token = data.data.token;
161
+ }
162
+ return data;
163
+ },
164
+ logout: async () => {
165
+ const route = this.getRoute('auth');
166
+ await this.fetch(`${this.baseUrl}${route}/logout`, { method: 'POST' });
167
+ this.token = undefined;
168
+ },
169
+ me: async () => {
170
+ const route = this.getRoute('auth');
171
+ const res = await this.fetch(`${this.baseUrl}${route}/me`);
172
+ return res.json();
173
+ }
174
+ };
175
+ /**
176
+ * Storage Services
177
+ */
178
+ this.storage = {
179
+ upload: async (file, scope = 'user') => {
180
+ // 1. Get Presigned URL
181
+ const presignedReq = {
182
+ filename: file.name,
183
+ mimeType: file.type,
184
+ size: file.size,
185
+ scope
186
+ };
187
+ const route = this.getRoute('storage');
188
+ const presignedRes = await this.fetch(`${this.baseUrl}${route}/upload/presigned`, {
189
+ method: 'POST',
190
+ body: JSON.stringify(presignedReq)
191
+ });
192
+ const { data: presigned } = await presignedRes.json();
193
+ // 2. Upload to Cloud directly (Bypass API Middleware to avoid Auth headers if using S3)
194
+ // Use fetchImpl directly
195
+ const uploadRes = await this.fetchImpl(presigned.uploadUrl, {
196
+ method: presigned.method,
197
+ headers: presigned.headers,
198
+ body: file
199
+ });
200
+ if (!uploadRes.ok) {
201
+ throw new Error(`Storage Upload Failed: ${uploadRes.statusText}`);
202
+ }
203
+ // 3. Complete Upload
204
+ const completeReq = {
205
+ fileId: presigned.fileId
206
+ };
207
+ const completeRes = await this.fetch(`${this.baseUrl}${route}/upload/complete`, {
208
+ method: 'POST',
209
+ body: JSON.stringify(completeReq)
210
+ });
211
+ return completeRes.json();
212
+ },
213
+ getDownloadUrl: async (fileId) => {
214
+ const route = this.getRoute('storage');
215
+ const res = await this.fetch(`${this.baseUrl}${route}/files/${fileId}/url`);
216
+ const data = await res.json();
217
+ return data.url;
218
+ }
219
+ };
220
+ /**
221
+ * Automation Services
222
+ */
223
+ this.automation = {
224
+ trigger: async (triggerName, payload) => {
225
+ const route = this.getRoute('automation');
226
+ const res = await this.fetch(`${this.baseUrl}${route}/trigger/${triggerName}`, {
227
+ method: 'POST',
228
+ body: JSON.stringify(payload)
229
+ });
230
+ return res.json();
231
+ }
232
+ };
91
233
  /**
92
234
  * Data Operations
93
235
  */
@@ -325,15 +467,25 @@ export class ObjectStackClient {
325
467
  }
326
468
  /**
327
469
  * 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
470
+ * ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
329
471
  */
330
472
  getRoute(type) {
331
- // Use conventional ObjectStack API paths
473
+ // 1. Use discovered routes if available
474
+ // Note: Spec uses 'endpoints', mapped dynamically
475
+ if (this.discoveryInfo?.endpoints && this.discoveryInfo.endpoints[type]) {
476
+ return this.discoveryInfo.endpoints[type];
477
+ }
478
+ // 2. Fallback to conventions
479
+ // Note: HttpDispatcher expects /metadata, not /meta
332
480
  const routeMap = {
333
481
  data: '/api/v1/data',
334
- metadata: '/api/v1/meta',
482
+ metadata: '/api/v1/metadata',
335
483
  ui: '/api/v1/ui',
336
- auth: '/api/v1/auth'
484
+ auth: '/api/v1/auth',
485
+ analytics: '/api/v1/analytics',
486
+ hub: '/api/v1/hub',
487
+ storage: '/api/v1/storage',
488
+ automation: '/api/v1/automation'
337
489
  };
338
490
  return routeMap[type] || `/api/v1/${type}`;
339
491
  }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@objectstack/client",
3
- "version": "0.9.2",
3
+ "version": "1.0.0",
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.0",
10
+ "@objectstack/core": "1.0.0"
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
 
@@ -208,6 +214,161 @@ export class ObjectStackClient {
208
214
  }
209
215
  };
210
216
 
217
+ /**
218
+ * Analytics Services
219
+ */
220
+ analytics = {
221
+ query: async (payload: any) => {
222
+ const route = this.getRoute('analytics');
223
+ const res = await this.fetch(`${this.baseUrl}${route}/query`, {
224
+ method: 'POST',
225
+ body: JSON.stringify(payload)
226
+ });
227
+ return res.json();
228
+ },
229
+ meta: async (cube: string) => {
230
+ const route = this.getRoute('analytics');
231
+ const res = await this.fetch(`${this.baseUrl}${route}/meta/${cube}`);
232
+ return res.json();
233
+ },
234
+ explain: async (payload: any) => {
235
+ const route = this.getRoute('analytics');
236
+ const res = await this.fetch(`${this.baseUrl}${route}/explain`, {
237
+ method: 'POST',
238
+ body: JSON.stringify(payload)
239
+ });
240
+ return res.json();
241
+ }
242
+ };
243
+
244
+ /**
245
+ * Hub Management Services
246
+ */
247
+ hub = {
248
+ spaces: {
249
+ list: async () => {
250
+ const route = this.getRoute('hub');
251
+ const res = await this.fetch(`${this.baseUrl}${route}/spaces`);
252
+ return res.json();
253
+ },
254
+ create: async (payload: any) => {
255
+ const route = this.getRoute('hub');
256
+ const res = await this.fetch(`${this.baseUrl}${route}/spaces`, {
257
+ method: 'POST',
258
+ body: JSON.stringify(payload)
259
+ });
260
+ return res.json();
261
+ }
262
+ },
263
+ plugins: {
264
+ install: async (pkg: string, version?: string) => {
265
+ const route = this.getRoute('hub');
266
+ const res = await this.fetch(`${this.baseUrl}${route}/plugins/install`, {
267
+ method: 'POST',
268
+ body: JSON.stringify({ pkg, version })
269
+ });
270
+ return res.json();
271
+ }
272
+ }
273
+ };
274
+
275
+ /**
276
+ * Authentication Services
277
+ */
278
+ auth = {
279
+ login: async (request: LoginRequest): Promise<SessionResponse> => {
280
+ const route = this.getRoute('auth');
281
+ const res = await this.fetch(`${this.baseUrl}${route}/login`, {
282
+ method: 'POST',
283
+ body: JSON.stringify(request)
284
+ });
285
+ const data = await res.json();
286
+ // Auto-set token if present in response
287
+ if (data.data?.token) {
288
+ this.token = data.data.token;
289
+ }
290
+ return data;
291
+ },
292
+
293
+ logout: async () => {
294
+ const route = this.getRoute('auth');
295
+ await this.fetch(`${this.baseUrl}${route}/logout`, { method: 'POST' });
296
+ this.token = undefined;
297
+ },
298
+
299
+ me: async (): Promise<SessionResponse> => {
300
+ const route = this.getRoute('auth');
301
+ const res = await this.fetch(`${this.baseUrl}${route}/me`);
302
+ return res.json();
303
+ }
304
+ };
305
+
306
+ /**
307
+ * Storage Services
308
+ */
309
+ storage = {
310
+ upload: async (file: any, scope: string = 'user'): Promise<FileUploadResponse> => {
311
+ // 1. Get Presigned URL
312
+ const presignedReq: GetPresignedUrlRequest = {
313
+ filename: file.name,
314
+ mimeType: file.type,
315
+ size: file.size,
316
+ scope
317
+ };
318
+
319
+ const route = this.getRoute('storage');
320
+ const presignedRes = await this.fetch(`${this.baseUrl}${route}/upload/presigned`, {
321
+ method: 'POST',
322
+ body: JSON.stringify(presignedReq)
323
+ });
324
+ const { data: presigned } = await presignedRes.json() as { data: PresignedUrlResponse['data'] };
325
+
326
+ // 2. Upload to Cloud directly (Bypass API Middleware to avoid Auth headers if using S3)
327
+ // Use fetchImpl directly
328
+ const uploadRes = await this.fetchImpl(presigned.uploadUrl, {
329
+ method: presigned.method,
330
+ headers: presigned.headers,
331
+ body: file
332
+ });
333
+
334
+ if (!uploadRes.ok) {
335
+ throw new Error(`Storage Upload Failed: ${uploadRes.statusText}`);
336
+ }
337
+
338
+ // 3. Complete Upload
339
+ const completeReq: CompleteUploadRequest = {
340
+ fileId: presigned.fileId
341
+ };
342
+ const completeRes = await this.fetch(`${this.baseUrl}${route}/upload/complete`, {
343
+ method: 'POST',
344
+ body: JSON.stringify(completeReq)
345
+ });
346
+
347
+ return completeRes.json();
348
+ },
349
+
350
+ getDownloadUrl: async (fileId: string): Promise<string> => {
351
+ const route = this.getRoute('storage');
352
+ const res = await this.fetch(`${this.baseUrl}${route}/files/${fileId}/url`);
353
+ const data = await res.json();
354
+ return data.url;
355
+ }
356
+ };
357
+
358
+ /**
359
+ * Automation Services
360
+ */
361
+ automation = {
362
+ trigger: async (triggerName: string, payload: any) => {
363
+ const route = this.getRoute('automation');
364
+ const res = await this.fetch(`${this.baseUrl}${route}/trigger/${triggerName}`, {
365
+ method: 'POST',
366
+ body: JSON.stringify(payload)
367
+ });
368
+ return res.json();
369
+ }
370
+ };
371
+
211
372
  /**
212
373
  * Data Operations
213
374
  */
@@ -443,15 +604,26 @@ export class ObjectStackClient {
443
604
 
444
605
  /**
445
606
  * 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
607
+ * ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
447
608
  */
448
- private getRoute(type: 'data' | 'metadata' | 'ui' | 'auth'): string {
449
- // Use conventional ObjectStack API paths
609
+ private getRoute(type: 'data' | 'metadata' | 'ui' | 'auth' | 'analytics' | 'hub' | 'storage' | 'automation'): string {
610
+ // 1. Use discovered routes if available
611
+ // Note: Spec uses 'endpoints', mapped dynamically
612
+ if (this.discoveryInfo?.endpoints && (this.discoveryInfo.endpoints as any)[type]) {
613
+ return (this.discoveryInfo.endpoints as any)[type];
614
+ }
615
+
616
+ // 2. Fallback to conventions
617
+ // Note: HttpDispatcher expects /metadata, not /meta
450
618
  const routeMap: Record<string, string> = {
451
619
  data: '/api/v1/data',
452
- metadata: '/api/v1/meta',
620
+ metadata: '/api/v1/metadata',
453
621
  ui: '/api/v1/ui',
454
- auth: '/api/v1/auth'
622
+ auth: '/api/v1/auth',
623
+ analytics: '/api/v1/analytics',
624
+ hub: '/api/v1/hub',
625
+ storage: '/api/v1/storage',
626
+ automation: '/api/v1/automation'
455
627
  };
456
628
 
457
629
  return routeMap[type] || `/api/v1/${type}`;