@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 +22 -0
- package/README.md +6 -0
- package/dist/index.d.ts +68 -4
- package/dist/index.js +170 -4
- package/package.json +4 -3
- package/src/client.test.ts +3 -3
- package/src/index.ts +193 -6
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?:
|
|
60
|
-
|
|
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/
|
|
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/
|
|
484
|
+
* ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
|
|
329
485
|
*/
|
|
330
486
|
getRoute(type) {
|
|
331
|
-
// Use
|
|
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/
|
|
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.
|
|
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
|
-
"@objectstack/core": "0.
|
|
9
|
+
"@objectstack/spec": "1.0.1",
|
|
10
|
+
"@objectstack/core": "1.0.1"
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {
|
|
12
13
|
"typescript": "^5.0.0",
|
package/src/client.test.ts
CHANGED
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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/
|
|
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}`;
|