@uploadista/client-core 0.0.6 → 0.0.7

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.
@@ -5,12 +5,15 @@ import type {
5
5
  } from "../services/http-client";
6
6
  import type { DirectAuthManager } from "./direct-auth";
7
7
  import type { NoAuthManager } from "./no-auth";
8
- import type { SaasAuthManager } from "./saas-auth";
8
+ import type { UploadistaCloudAuthManager } from "./uploadista-cloud-auth";
9
9
 
10
10
  /**
11
11
  * Union type of all auth managers
12
12
  */
13
- export type AuthManager = DirectAuthManager | SaasAuthManager | NoAuthManager;
13
+ export type AuthManager =
14
+ | DirectAuthManager
15
+ | UploadistaCloudAuthManager
16
+ | NoAuthManager;
14
17
 
15
18
  /**
16
19
  * Auth-aware HTTP client wrapper.
@@ -49,7 +52,7 @@ export class AuthHttpClient implements HttpClient {
49
52
  // include credentials for cors if needed
50
53
  credentials:
51
54
  this.authManager.getType() === "no-auth" ||
52
- this.authManager.getType() === "saas"
55
+ this.authManager.getType() === "uploadista-cloud"
53
56
  ? "omit"
54
57
  : (options.credentials ?? "include"),
55
58
  });
@@ -72,14 +75,14 @@ export class AuthHttpClient implements HttpClient {
72
75
  headers: Record<string, string>,
73
76
  url: string,
74
77
  ): Promise<Record<string, string>> {
75
- // Check if this is a DirectAuthManager or SaasAuthManager
78
+ // Check if this is a DirectAuthManager or UploadistaCloudAuthManager
76
79
  if ("attachCredentials" in this.authManager) {
77
80
  // DirectAuthManager or NoAuthManager
78
81
  return await this.authManager.attachCredentials(headers);
79
82
  }
80
83
 
81
84
  if ("attachToken" in this.authManager) {
82
- // SaasAuthManager - extract job ID from URL if present
85
+ // UploadistaCloudAuthManager - extract job ID from URL if present
83
86
  const jobId = this.extractJobIdFromUrl(url);
84
87
  return await this.authManager.attachToken(headers, jobId);
85
88
  }
@@ -97,7 +100,7 @@ export class AuthHttpClient implements HttpClient {
97
100
  // - /api/upload/{uploadId}
98
101
  // - /api/flow/{flowId}/{storageId}
99
102
  // - /api/jobs/{jobId}/status
100
- // - /api/jobs/{jobId}/continue/{nodeId}
103
+ // - /api/jobs/{jobId}/resume/{nodeId}
101
104
 
102
105
  const uploadMatch = url.match(/\/api\/upload\/([^/?]+)/);
103
106
  if (uploadMatch) {
@@ -114,7 +117,7 @@ export class AuthHttpClient implements HttpClient {
114
117
  return jobMatch[1];
115
118
  }
116
119
 
117
- // No job ID found - SaaS mode will use global token
120
+ // No job ID found - UploadistaCloud mode will use global token
118
121
  return undefined;
119
122
  }
120
123
 
package/src/auth/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./auth-http-client";
2
2
  export * from "./direct-auth";
3
3
  export * from "./no-auth";
4
- export * from "./saas-auth";
5
4
  export * from "./types";
5
+ export * from "./uploadista-cloud-auth";
package/src/auth/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export class BaseAuthManager {
2
- constructor(private type: "direct" | "saas" | "no-auth") {}
2
+ constructor(private type: "direct" | "uploadista-cloud" | "no-auth") {}
3
3
 
4
4
  getType() {
5
5
  return this.type;
@@ -56,7 +56,7 @@ export type DirectAuthConfig = {
56
56
  };
57
57
 
58
58
  /**
59
- * SaaS auth mode configuration.
59
+ * UploadistaCloud auth mode configuration.
60
60
  * Client requests JWT tokens from a user-controlled auth server,
61
61
  * which validates credentials and issues tokens using a secure API key.
62
62
  *
@@ -69,26 +69,21 @@ export type DirectAuthConfig = {
69
69
  * @example
70
70
  * ```typescript
71
71
  * {
72
- * mode: 'saas',
72
+ * mode: 'uploadista-cloud',
73
73
  * authServerUrl: 'https://auth.myapp.com/token',
74
- * getCredentials: async () => ({
75
- * username: await getUsername(),
76
- * password: await getPassword()
77
- * })
74
+ * clientId: 'my-client-id'
78
75
  * }
79
76
  * ```
80
77
  */
81
- export type SaasAuthConfig = {
82
- mode: "saas";
78
+ export type UploadistaCloudAuthConfig = {
79
+ mode: "uploadista-cloud";
83
80
  /**
84
81
  * URL of the user's auth server that issues JWT tokens.
85
82
  * Should be a GET endpoint that accepts client id and returns { token, expiresIn }.
86
83
  */
87
84
  authServerUrl: string;
88
85
  /**
89
- * Function that returns user credentials to send to the auth server.
90
- * The auth server will validate these credentials before issuing a token.
91
- * Credentials format is client id
86
+ * Client ID to use for authentication. It will be used to compare the API Key with the client id on the auth server.
92
87
  */
93
88
  clientId: string;
94
89
  };
@@ -97,9 +92,9 @@ export type SaasAuthConfig = {
97
92
  * Authentication configuration for the uploadista client.
98
93
  * Supports two modes:
99
94
  * - Direct: Bring your own auth (any protocol)
100
- * - SaaS: Standard JWT token exchange with auth server
95
+ * - UploadistaCloud: Standard JWT token exchange with auth server
101
96
  *
102
97
  * Use a discriminated union to ensure type safety - TypeScript will
103
98
  * enforce that the correct fields are present for each mode.
104
99
  */
105
- export type AuthConfig = DirectAuthConfig | SaasAuthConfig;
100
+ export type AuthConfig = DirectAuthConfig | UploadistaCloudAuthConfig;
@@ -1,5 +1,5 @@
1
1
  import type { HttpClient } from "../services/http-client";
2
- import { BaseAuthManager, type SaasAuthConfig } from "./types";
2
+ import { BaseAuthManager, type UploadistaCloudAuthConfig } from "./types";
3
3
 
4
4
  /**
5
5
  * Token response from the auth server
@@ -20,7 +20,7 @@ type CachedToken = {
20
20
  };
21
21
 
22
22
  /**
23
- * SaaS auth manager - handles JWT token exchange with an auth server.
23
+ * UploadistaCloud auth manager - handles JWT token exchange with an auth server.
24
24
  *
25
25
  * Token exchange flow:
26
26
  * 1. Client calls getCredentials() to get user credentials
@@ -31,7 +31,7 @@ type CachedToken = {
31
31
  *
32
32
  * Security: API keys are kept server-side in the auth server, never exposed to clients.
33
33
  */
34
- export class SaasAuthManager extends BaseAuthManager {
34
+ export class UploadistaCloudAuthManager extends BaseAuthManager {
35
35
  /** Token cache: maps job ID to cached token */
36
36
  private tokenCache = new Map<string, CachedToken>();
37
37
 
@@ -39,10 +39,10 @@ export class SaasAuthManager extends BaseAuthManager {
39
39
  private globalToken: CachedToken | null = null;
40
40
 
41
41
  constructor(
42
- private config: SaasAuthConfig,
42
+ private config: UploadistaCloudAuthConfig,
43
43
  private httpClient: HttpClient,
44
44
  ) {
45
- super("saas");
45
+ super("uploadista-cloud");
46
46
  }
47
47
 
48
48
  /**
@@ -1,6 +1,11 @@
1
+ import type { FlowJob } from "@uploadista/core/flow";
1
2
  import type { DataStoreCapabilities } from "@uploadista/core/types";
2
3
  import type { AuthConfig, AuthManager } from "../auth";
3
- import { DirectAuthManager, NoAuthManager, SaasAuthManager } from "../auth";
4
+ import {
5
+ DirectAuthManager,
6
+ NoAuthManager,
7
+ UploadistaCloudAuthManager,
8
+ } from "../auth";
4
9
  import type { Logger } from "../logger";
5
10
  import { createLogger } from "../logger";
6
11
  import { defaultClientCapabilities } from "../mock-data-store";
@@ -175,7 +180,7 @@ export type UploadistaClientOptions<UploadInput> = {
175
180
  * Optional authentication configuration.
176
181
  * Supports two modes:
177
182
  * - Direct: Bring your own auth (headers, cookies, custom tokens)
178
- * - SaaS: Standard JWT token exchange with auth server
183
+ * - UploadistaCloud: Standard JWT token exchange with auth server
179
184
  *
180
185
  * If omitted, client operates in no-auth mode (backward compatible).
181
186
  *
@@ -189,10 +194,10 @@ export type UploadistaClientOptions<UploadInput> = {
189
194
  * }
190
195
  * ```
191
196
  *
192
- * @example SaaS mode with auth server
197
+ * @example UploadistaCloud mode with auth server
193
198
  * ```typescript
194
199
  * auth: {
195
- * mode: 'saas',
200
+ * mode: 'uploadista-cloud',
196
201
  * authServerUrl: 'https://auth.myapp.com/token',
197
202
  * getCredentials: () => ({ username: 'user', password: 'pass' })
198
203
  * }
@@ -230,7 +235,7 @@ export const defaultConnectionPoolingConfig: ConnectionPoolConfig = {
230
235
  * - Smart chunking based on network conditions
231
236
  * - Flow-based file processing pipelines
232
237
  * - WebSocket support for real-time progress
233
- * - Authentication (direct, SaaS, or no-auth modes)
238
+ * - Authentication (direct, uploadista-cloud, or no-auth modes)
234
239
  *
235
240
  * The client automatically:
236
241
  * - Fetches server capabilities and adapts upload strategy
@@ -369,13 +374,13 @@ export function createUploadistaClient<UploadInput>({
369
374
  const authManager: AuthManager = auth
370
375
  ? auth.mode === "direct"
371
376
  ? new DirectAuthManager(auth, platformService, logger)
372
- : new SaasAuthManager(auth, httpClient)
377
+ : new UploadistaCloudAuthManager(auth, httpClient)
373
378
  : new NoAuthManager();
374
379
 
375
380
  // Log auth mode for debugging (without exposing credentials)
376
381
  if (auth) {
377
382
  logger.log(
378
- `Authentication enabled in ${auth.mode} mode${auth.mode === "saas" ? ` (server: ${auth.authServerUrl})` : ""}`,
383
+ `Authentication enabled in ${auth.mode} mode${auth.mode === "uploadista-cloud" ? ` (server: ${auth.authServerUrl})` : ""}`,
379
384
  );
380
385
  }
381
386
 
@@ -666,7 +671,11 @@ export function createUploadistaClient<UploadInput>({
666
671
  UploadistaUploadOptions,
667
672
  "uploadLengthDeferred" | "uploadSize" | "metadata"
668
673
  > = {},
669
- ): Promise<{ abort: () => void; jobId: string }> => {
674
+ ): Promise<{
675
+ abort: () => Promise<void>;
676
+ pause: () => Promise<FlowJob>;
677
+ jobId: string;
678
+ }> => {
670
679
  const source = await fileReader.openFile(file, chunkSize);
671
680
 
672
681
  const initializedSmartChunker = await initializeSmartChunker();
@@ -697,7 +706,10 @@ export function createUploadistaClient<UploadInput>({
697
706
 
698
707
  if (!result) {
699
708
  return {
700
- abort: () => {},
709
+ abort: async () => {},
710
+ pause: async () => {
711
+ throw new Error("Flow upload not initialized");
712
+ },
701
713
  jobId: "",
702
714
  };
703
715
  }
@@ -735,7 +747,17 @@ export function createUploadistaClient<UploadInput>({
735
747
  });
736
748
 
737
749
  return {
738
- abort: () => {
750
+ abort: async () => {
751
+ // First, tell the server to cancel the flow
752
+ try {
753
+ await uploadistaApi.cancelFlow(jobId);
754
+ logger.log(`Flow cancelled on server: ${jobId}`);
755
+ } catch (err) {
756
+ // Log but don't throw - client cleanup should still happen
757
+ logger.log(`Failed to cancel flow on server: ${err}`);
758
+ }
759
+
760
+ // Then do client-side cleanup
739
761
  abortController.abort();
740
762
  if (timeoutId) {
741
763
  platformService.clearTimeout(timeoutId);
@@ -744,6 +766,7 @@ export function createUploadistaClient<UploadInput>({
744
766
  wsManager.closeWebSocket(jobId);
745
767
  wsManager.closeUploadWebSocket(uploadFile.id);
746
768
  },
769
+ pause: () => uploadistaApi.pauseFlow(jobId),
747
770
  jobId,
748
771
  };
749
772
  };
@@ -777,7 +800,7 @@ export function createUploadistaClient<UploadInput>({
777
800
  return { status, job };
778
801
  },
779
802
 
780
- continueFlow: async ({
803
+ resumeFlow: async ({
781
804
  jobId,
782
805
  nodeId,
783
806
  newData,
@@ -788,11 +811,19 @@ export function createUploadistaClient<UploadInput>({
788
811
  newData: unknown;
789
812
  contentType?: "application/json" | "application/octet-stream";
790
813
  }) => {
791
- return uploadistaApi.continueFlow(jobId, nodeId, newData, {
814
+ return uploadistaApi.resumeFlow(jobId, nodeId, newData, {
792
815
  contentType,
793
816
  });
794
817
  },
795
818
 
819
+ pauseFlow: async (jobId: string) => {
820
+ return uploadistaApi.pauseFlow(jobId);
821
+ },
822
+
823
+ cancelFlow: async (jobId: string) => {
824
+ return uploadistaApi.cancelFlow(jobId);
825
+ },
826
+
796
827
  // Job operations (unified for both uploads and flows)
797
828
  getJobStatus: async (jobId: string) => {
798
829
  return uploadistaApi.getJobStatus(jobId);
@@ -874,7 +905,7 @@ export function createUploadistaClient<UploadInput>({
874
905
  *
875
906
  * The client provides methods for:
876
907
  * - **Upload operations**: upload(), uploadWithFlow()
877
- * - **Flow operations**: getFlow(), runFlow(), continueFlow()
908
+ * - **Flow operations**: getFlow(), runFlow(), resumeFlow()
878
909
  * - **Job management**: getJobStatus()
879
910
  * - **WebSocket management**: openUploadWebSocket(), openFlowWebSocket(), closeWebSocket()
880
911
  * - **Metrics and diagnostics**: getNetworkMetrics(), getChunkingInsights(), exportMetrics()
@@ -215,7 +215,7 @@ export type UploadistaApi = {
215
215
  * @returns Updated job metadata
216
216
  * @throws {UploadistaError} If job not found or continuation fails
217
217
  */
218
- continueFlow: (
218
+ resumeFlow: (
219
219
  jobId: string,
220
220
  nodeId: string,
221
221
  newData: unknown,
@@ -224,6 +224,31 @@ export type UploadistaApi = {
224
224
  },
225
225
  ) => Promise<FlowJob>;
226
226
 
227
+ /**
228
+ * Pauses a running flow execution.
229
+ *
230
+ * The flow will stop at the next node boundary (not mid-node execution).
231
+ * Can be resumed later using resumeFlow.
232
+ *
233
+ * @param jobId - Job identifier for the running flow
234
+ * @returns Updated job metadata with "paused" status
235
+ * @throws {UploadistaError} If job not found or cannot be paused
236
+ */
237
+ pauseFlow: (jobId: string) => Promise<FlowJob>;
238
+
239
+ /**
240
+ * Cancels a running or paused flow execution.
241
+ *
242
+ * The flow will stop at the next node boundary (not mid-node execution).
243
+ * Intermediate files are automatically cleaned up. This operation is terminal
244
+ * and cannot be undone.
245
+ *
246
+ * @param jobId - Job identifier for the flow to cancel
247
+ * @returns Updated job metadata with "cancelled" status
248
+ * @throws {UploadistaError} If job not found or cannot be cancelled
249
+ */
250
+ cancelFlow: (jobId: string) => Promise<FlowJob>;
251
+
227
252
  /**
228
253
  * Retrieves current job status and outputs.
229
254
  *
@@ -390,7 +415,7 @@ export function createUploadistaApi(
390
415
 
391
416
  /**
392
417
  * Helper function to extract auth token for WebSocket connection.
393
- * Supports both DirectAuthManager (extracts from headers) and SaasAuthManager (gets cached token).
418
+ * Supports both DirectAuthManager (extracts from headers) and UploadistaCloudAuthManager (gets cached token).
394
419
  */
395
420
  const getAuthTokenForWebSocket = async (
396
421
  manager: AuthManager,
@@ -398,17 +423,19 @@ export function createUploadistaApi(
398
423
  ): Promise<string | null> => {
399
424
  logger?.log(`Getting auth token for WebSocket (jobId: ${jobId})`);
400
425
 
401
- // Check if this is a SaasAuthManager (has attachToken method)
426
+ // Check if this is a UploadistaCloudAuthManager (has attachToken method)
402
427
  if ("attachToken" in manager) {
403
- logger?.log("Detected SaasAuthManager, calling attachToken");
428
+ logger?.log("Detected UploadistaCloudAuthManager, calling attachToken");
404
429
  const headers = await manager.attachToken({}, jobId);
405
430
  const authHeader = headers.Authorization;
406
431
  if (authHeader?.startsWith("Bearer ")) {
407
- logger?.log("Successfully extracted Bearer token from SaasAuthManager");
432
+ logger?.log(
433
+ "Successfully extracted Bearer token from UploadistaCloudAuthManager",
434
+ );
408
435
  return authHeader.substring(7); // Remove "Bearer " prefix
409
436
  }
410
437
  logger?.log(
411
- `No valid Authorization header from SaasAuthManager: ${authHeader}`,
438
+ `No valid Authorization header from UploadistaCloudAuthManager: ${authHeader}`,
412
439
  );
413
440
  }
414
441
 
@@ -629,7 +656,7 @@ export function createUploadistaApi(
629
656
  return { status: res.status, job: data };
630
657
  },
631
658
 
632
- continueFlow: async (
659
+ resumeFlow: async (
633
660
  jobId: string,
634
661
  nodeId: string,
635
662
  newData: unknown,
@@ -637,8 +664,6 @@ export function createUploadistaApi(
637
664
  contentType?: "application/json" | "application/octet-stream";
638
665
  },
639
666
  ) => {
640
- logger?.log(`continueFlow: ${jobId} at node: ${nodeId}`);
641
-
642
667
  const contentType = options?.contentType || "application/json";
643
668
 
644
669
  let body: RequestBody;
@@ -651,7 +676,7 @@ export function createUploadistaApi(
651
676
  }
652
677
 
653
678
  const res = await httpClient.request(
654
- `${jobsEndpoint}/${jobId}/continue/${nodeId}`,
679
+ `${jobsEndpoint}/${jobId}/resume/${nodeId}`,
655
680
  {
656
681
  method: "PATCH",
657
682
  headers: {
@@ -665,12 +690,71 @@ export function createUploadistaApi(
665
690
  const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
666
691
  const errorName = mapServerErrorCodeToClientName(
667
692
  errorData.code,
668
- "FLOW_CONTINUE_FAILED",
693
+ "FLOW_RESUMED_FAILED",
694
+ );
695
+ const errorMessage =
696
+ errorData.error ||
697
+ errorData.message ||
698
+ `Failed to resume flow for job ${jobId}`;
699
+
700
+ throw new UploadistaError({
701
+ name: errorName,
702
+ message: errorData.code
703
+ ? `${errorMessage} (${errorData.code})`
704
+ : errorMessage,
705
+ status: res.status,
706
+ });
707
+ }
708
+
709
+ const data = (await res.json()) as FlowJob;
710
+ return data;
711
+ },
712
+
713
+ pauseFlow: async (jobId: string) => {
714
+ const res = await httpClient.request(`${jobsEndpoint}/${jobId}/pause`, {
715
+ method: "POST",
716
+ });
717
+
718
+ if (!res.ok) {
719
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
720
+ const errorName = mapServerErrorCodeToClientName(
721
+ errorData.code,
722
+ "FLOW_PAUSE_FAILED",
723
+ );
724
+ const errorMessage =
725
+ errorData.error ||
726
+ errorData.message ||
727
+ `Failed to pause flow for job ${jobId}`;
728
+
729
+ throw new UploadistaError({
730
+ name: errorName,
731
+ message: errorData.code
732
+ ? `${errorMessage} (${errorData.code})`
733
+ : errorMessage,
734
+ status: res.status,
735
+ });
736
+ }
737
+
738
+ const data = (await res.json()) as FlowJob;
739
+ logger?.log(`Flow paused: ${jobId}, status: ${data.status}`);
740
+ return data;
741
+ },
742
+
743
+ cancelFlow: async (jobId: string) => {
744
+ const res = await httpClient.request(`${jobsEndpoint}/${jobId}/cancel`, {
745
+ method: "POST",
746
+ });
747
+
748
+ if (!res.ok) {
749
+ const errorData = (await res.json().catch(() => ({}))) as ErrorResponse;
750
+ const errorName = mapServerErrorCodeToClientName(
751
+ errorData.code,
752
+ "FLOW_CANCEL_FAILED",
669
753
  );
670
754
  const errorMessage =
671
755
  errorData.error ||
672
756
  errorData.message ||
673
- `Failed to continue flow for job ${jobId}`;
757
+ `Failed to cancel flow for job ${jobId}`;
674
758
 
675
759
  throw new UploadistaError({
676
760
  name: errorName,
@@ -682,6 +766,7 @@ export function createUploadistaApi(
682
766
  }
683
767
 
684
768
  const data = (await res.json()) as FlowJob;
769
+ logger?.log(`Flow cancelled: ${jobId}, status: ${data.status}`);
685
770
  return data;
686
771
  },
687
772
 
package/src/error.ts CHANGED
@@ -36,7 +36,9 @@ export type UploadistaErrorName =
36
36
  | "FLOW_NOT_FOUND"
37
37
  | "FLOW_INIT_FAILED"
38
38
  | "FLOW_RUN_FAILED"
39
- | "FLOW_CONTINUE_FAILED"
39
+ | "FLOW_RESUMED_FAILED"
40
+ | "FLOW_PAUSE_FAILED"
41
+ | "FLOW_CANCEL_FAILED"
40
42
  | "FLOW_UNEXPECTED_STATE"
41
43
  | "FLOW_INCOMPATIBLE"
42
44
  | "FLOW_NO_UPLOAD_ID"
@@ -144,7 +144,7 @@ export async function startFlowUpload({
144
144
  }
145
145
 
146
146
  /**
147
- * Upload chunks directly to the upload API (not through continueFlow)
147
+ * Upload chunks directly to the upload API (not through resumeFlow)
148
148
  * This is more efficient and reuses the existing upload infrastructure
149
149
  */
150
150
  export async function performFlowUpload({
@@ -265,7 +265,7 @@ export async function performFlowUpload({
265
265
  logger.log(`Finalizing flow upload for job ${jobId}`);
266
266
 
267
267
  try {
268
- await uploadistaApi.continueFlow(
268
+ await uploadistaApi.resumeFlow(
269
269
  jobId,
270
270
  inputNodeId,
271
271
  {