@@ -11,7 +11,13 @@ import {
1111 ErrorCategory ,
1212 GetDiscoveryResponse ,
1313 GetMetaTypesResponse ,
14- GetMetaItemsResponse
14+ GetMetaItemsResponse ,
15+ LoginRequest ,
16+ SessionResponse ,
17+ GetPresignedUrlRequest ,
18+ PresignedUrlResponse ,
19+ CompleteUploadRequest ,
20+ FileUploadResponse
1521} from '@objectstack/spec/api' ;
1622import { Logger , createLogger } from '@objectstack/core' ;
1723
@@ -266,6 +272,103 @@ export class ObjectStackClient {
266272 }
267273 } ;
268274
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+
269372 /**
270373 * Data Operations
271374 */
@@ -503,10 +606,11 @@ export class ObjectStackClient {
503606 * Get the conventional route path for a given API endpoint type
504607 * ObjectStack uses standard conventions: /api/v1/data, /api/v1/metadata, /api/v1/ui
505608 */
506- private getRoute ( type : 'data' | 'metadata' | 'ui' | 'auth' | 'analytics' | 'hub' | 'storage' ) : string {
609+ private getRoute ( type : 'data' | 'metadata' | 'ui' | 'auth' | 'analytics' | 'hub' | 'storage' | 'automation' ) : string {
507610 // 1. Use discovered routes if available
508- if ( this . discoveryInfo ?. routes && ( this . discoveryInfo . routes as any ) [ type ] ) {
509- return ( this . discoveryInfo . routes as any ) [ type ] ;
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 ] ;
510614 }
511615
512616 // 2. Fallback to conventions
@@ -518,7 +622,8 @@ export class ObjectStackClient {
518622 auth : '/api/v1/auth' ,
519623 analytics : '/api/v1/analytics' ,
520624 hub : '/api/v1/hub' ,
521- storage : '/api/v1/storage'
625+ storage : '/api/v1/storage' ,
626+ automation : '/api/v1/automation'
522627 } ;
523628
524629 return routeMap [ type ] || `/api/v1/${ type } ` ;
0 commit comments