@@ -82,6 +82,16 @@ import {
8282 UnsubscribeResponse ,
8383 WellKnownCapabilities ,
8484 ApiRoutes ,
85+ ImportRequest ,
86+ ImportResponse ,
87+ CreateImportJobRequest ,
88+ CreateImportJobResponse ,
89+ ImportJobProgress ,
90+ ImportJobResults ,
91+ ImportJobSummary ,
92+ ListImportJobsRequest ,
93+ ListImportJobsResponse ,
94+ UndoImportJobResponse ,
8595} from '@objectstack/spec/api' ;
8696import type {
8797 ApprovalRequestRow ,
@@ -3046,6 +3056,95 @@ export class ObjectStackClient {
30463056 return this . unwrapResponse < T [ ] > ( res ) ;
30473057 } ,
30483058
3059+ /**
3060+ * Bulk-import rows (CSV text or JSON row objects) into an object.
3061+ *
3062+ * The server coerces each cell to its storage value using the object's field
3063+ * metadata (booleans, numbers, dates→ISO, select label→code, lookup name→id),
3064+ * so callers send raw spreadsheet values plus an optional column `mapping`.
3065+ * `writeMode` selects insert / update / upsert (the latter two need
3066+ * `matchFields`); `dryRun` validates + previews without persisting. The
3067+ * response carries per-row outcomes for an import report.
3068+ */
3069+ import : async ( object : string , request : ImportRequest ) : Promise < ImportResponse > => {
3070+ const route = this . getRoute ( 'data' ) ;
3071+ const res = await this . fetch ( `${ this . baseUrl } ${ route } /${ object } /import` , {
3072+ method : 'POST' ,
3073+ body : JSON . stringify ( request ) ,
3074+ } ) ;
3075+ return this . unwrapResponse < ImportResponse > ( res ) ;
3076+ } ,
3077+
3078+ /**
3079+ * Import-job namespace — the asynchronous counterpart to {@link import} for
3080+ * large files (up to 50,000 rows). `createImportJob` posts the whole payload
3081+ * once and returns immediately with a `jobId`; a server worker processes the
3082+ * batch in the background. Poll {@link getImportJobProgress} for live
3083+ * counters, {@link getImportJobResults} for the capped per-row report, and
3084+ * {@link listImportJobs} for history. {@link cancelImportJob} stops a
3085+ * pending/running job cooperatively.
3086+ *
3087+ * These routes require a server new enough to expose them — older servers
3088+ * return 404, which surfaces here as a rejected promise. Callers that want
3089+ * graceful degradation should feature-detect (e.g. try the job, fall back
3090+ * to the synchronous {@link import} on 404).
3091+ */
3092+ createImportJob : async ( object : string , request : CreateImportJobRequest ) : Promise < CreateImportJobResponse > => {
3093+ const route = this . getRoute ( 'data' ) ;
3094+ const res = await this . fetch ( `${ this . baseUrl } ${ route } /${ object } /import/jobs` , {
3095+ method : 'POST' ,
3096+ body : JSON . stringify ( request ) ,
3097+ } ) ;
3098+ return this . unwrapResponse < CreateImportJobResponse > ( res ) ;
3099+ } ,
3100+
3101+ getImportJobProgress : async ( jobId : string ) : Promise < ImportJobProgress > => {
3102+ const route = this . getRoute ( 'data' ) ;
3103+ const res = await this . fetch ( `${ this . baseUrl } ${ route } /import/jobs/${ encodeURIComponent ( jobId ) } ` ) ;
3104+ return this . unwrapResponse < ImportJobProgress > ( res ) ;
3105+ } ,
3106+
3107+ getImportJobResults : async ( jobId : string ) : Promise < ImportJobResults > => {
3108+ const route = this . getRoute ( 'data' ) ;
3109+ const res = await this . fetch ( `${ this . baseUrl } ${ route } /import/jobs/${ encodeURIComponent ( jobId ) } /results` ) ;
3110+ return this . unwrapResponse < ImportJobResults > ( res ) ;
3111+ } ,
3112+
3113+ listImportJobs : async ( query : Partial < ListImportJobsRequest > = { } ) : Promise < ImportJobSummary [ ] > => {
3114+ const route = this . getRoute ( 'data' ) ;
3115+ const qs = new URLSearchParams ( ) ;
3116+ if ( query . object ) qs . set ( 'object' , query . object ) ;
3117+ if ( query . status ) qs . set ( 'status' , query . status ) ;
3118+ if ( query . limit != null ) qs . set ( 'limit' , String ( query . limit ) ) ;
3119+ if ( query . offset != null ) qs . set ( 'offset' , String ( query . offset ) ) ;
3120+ const suffix = qs . toString ( ) ? `?${ qs . toString ( ) } ` : '' ;
3121+ const res = await this . fetch ( `${ this . baseUrl } ${ route } /import/jobs${ suffix } ` ) ;
3122+ const body = await this . unwrapResponse < ListImportJobsResponse > ( res ) ;
3123+ return body . jobs ;
3124+ } ,
3125+
3126+ cancelImportJob : async ( jobId : string ) : Promise < { success : boolean } > => {
3127+ const route = this . getRoute ( 'data' ) ;
3128+ const res = await this . fetch ( `${ this . baseUrl } ${ route } /import/jobs/${ encodeURIComponent ( jobId ) } /cancel` , {
3129+ method : 'POST' ,
3130+ } ) ;
3131+ return this . unwrapResponse < { success : boolean } > ( res ) ;
3132+ } ,
3133+
3134+ /**
3135+ * Logically roll back a finished import: delete the records it created and
3136+ * restore the fields it updated to their pre-import values. Only jobs that
3137+ * captured an undo log (small, non-dry-run, not yet reverted) are undoable —
3138+ * others return 422. See {@link ImportJobProgress.undoable}.
3139+ */
3140+ undoImportJob : async ( jobId : string ) : Promise < UndoImportJobResponse > => {
3141+ const route = this . getRoute ( 'data' ) ;
3142+ const res = await this . fetch ( `${ this . baseUrl } ${ route } /import/jobs/${ encodeURIComponent ( jobId ) } /undo` , {
3143+ method : 'POST' ,
3144+ } ) ;
3145+ return this . unwrapResponse < UndoImportJobResponse > ( res ) ;
3146+ } ,
3147+
30493148 update : async < T = any > (
30503149 object : string ,
30513150 id : string ,
@@ -3456,6 +3555,64 @@ export class ScopedProjectClient {
34563555 } ) ;
34573556 return this . parent . _unwrap < T [ ] > ( res ) ;
34583557 } ,
3558+ /**
3559+ * Bulk-import rows (CSV text or JSON row objects) into an object. The server
3560+ * coerces each cell to its storage value from field metadata (booleans,
3561+ * numbers, dates→ISO, select label→code, lookup name→id); callers send raw
3562+ * values plus an optional column `mapping`. `writeMode` selects
3563+ * insert/update/upsert (update/upsert need `matchFields`); `dryRun`
3564+ * validates + previews without persisting.
3565+ */
3566+ import : async ( object : string , request : ImportRequest ) : Promise < ImportResponse > => {
3567+ const res = await this . parent . _fetch ( this . url ( `/data/${ object } /import` ) , {
3568+ method : 'POST' ,
3569+ body : JSON . stringify ( request ) ,
3570+ } ) ;
3571+ return this . parent . _unwrap < ImportResponse > ( res ) ;
3572+ } ,
3573+ /**
3574+ * Asynchronous import jobs (scoped) — see the top-level `data.createImportJob`
3575+ * for semantics. Large payloads are posted once; a server worker processes
3576+ * them in the background while callers poll progress / results / history.
3577+ */
3578+ createImportJob : async ( object : string , request : CreateImportJobRequest ) : Promise < CreateImportJobResponse > => {
3579+ const res = await this . parent . _fetch ( this . url ( `/data/${ object } /import/jobs` ) , {
3580+ method : 'POST' ,
3581+ body : JSON . stringify ( request ) ,
3582+ } ) ;
3583+ return this . parent . _unwrap < CreateImportJobResponse > ( res ) ;
3584+ } ,
3585+ getImportJobProgress : async ( jobId : string ) : Promise < ImportJobProgress > => {
3586+ const res = await this . parent . _fetch ( this . url ( `/data/import/jobs/${ encodeURIComponent ( jobId ) } ` ) ) ;
3587+ return this . parent . _unwrap < ImportJobProgress > ( res ) ;
3588+ } ,
3589+ getImportJobResults : async ( jobId : string ) : Promise < ImportJobResults > => {
3590+ const res = await this . parent . _fetch ( this . url ( `/data/import/jobs/${ encodeURIComponent ( jobId ) } /results` ) ) ;
3591+ return this . parent . _unwrap < ImportJobResults > ( res ) ;
3592+ } ,
3593+ listImportJobs : async ( query : Partial < ListImportJobsRequest > = { } ) : Promise < ImportJobSummary [ ] > => {
3594+ const qs = new URLSearchParams ( ) ;
3595+ if ( query . object ) qs . set ( 'object' , query . object ) ;
3596+ if ( query . status ) qs . set ( 'status' , query . status ) ;
3597+ if ( query . limit != null ) qs . set ( 'limit' , String ( query . limit ) ) ;
3598+ if ( query . offset != null ) qs . set ( 'offset' , String ( query . offset ) ) ;
3599+ const suffix = qs . toString ( ) ? `?${ qs . toString ( ) } ` : '' ;
3600+ const res = await this . parent . _fetch ( this . url ( `/data/import/jobs${ suffix } ` ) ) ;
3601+ const body = await this . parent . _unwrap < ListImportJobsResponse > ( res ) ;
3602+ return body . jobs ;
3603+ } ,
3604+ cancelImportJob : async ( jobId : string ) : Promise < { success : boolean } > => {
3605+ const res = await this . parent . _fetch ( this . url ( `/data/import/jobs/${ encodeURIComponent ( jobId ) } /cancel` ) , {
3606+ method : 'POST' ,
3607+ } ) ;
3608+ return this . parent . _unwrap < { success : boolean } > ( res ) ;
3609+ } ,
3610+ undoImportJob : async ( jobId : string ) : Promise < UndoImportJobResponse > => {
3611+ const res = await this . parent . _fetch ( this . url ( `/data/import/jobs/${ encodeURIComponent ( jobId ) } /undo` ) , {
3612+ method : 'POST' ,
3613+ } ) ;
3614+ return this . parent . _unwrap < UndoImportJobResponse > ( res ) ;
3615+ } ,
34593616 update : async < T = any > ( object : string , id : string , data : Partial < T > ) : Promise < UpdateDataResult < T > > => {
34603617 const res = await this . parent . _fetch ( this . url ( `/data/${ object } /${ id } ` ) , {
34613618 method : 'PATCH' ,
@@ -3638,6 +3795,14 @@ export type {
36383795 AuthProviderInfo ,
36393796 EmailPasswordConfigPublic ,
36403797 AuthFeaturesConfig ,
3798+ CreateImportJobRequest ,
3799+ CreateImportJobResponse ,
3800+ ImportJobProgress ,
3801+ ImportJobResults ,
3802+ ImportJobSummary ,
3803+ ListImportJobsRequest ,
3804+ ListImportJobsResponse ,
3805+ UndoImportJobResponse ,
36413806} from '@objectstack/spec/api' ;
36423807
36433808// Approval runtime types (ADR-0019) — surfaced so SDK consumers can type the
0 commit comments