@flashbacktech/flashbackclient 0.0.93 → 0.0.95

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.
@@ -1,6 +1,7 @@
1
1
  import { CreateUnitRequest, CreateUnitResponse, CreateRepoRequest, CreateRepoResponse, StorageUnit, CreateRepoKeyRequest, CreateRepoKeyResponse, GetUnitsResponse, GetReposResponse, GetRepoKeysResponse, UpdateUnitRequest, UpdateUnitResponse, ActionResponse, UpdateRepoRequest, UpdateRepoResponse, UpdateRepoKeyRequest, UpdateRepoKeyResponse, ValidateUnitRequest, ValidateUnitResponse, ValidateRepoUnitsRequest, ValidateRepoUnitsResponse, StorageUnitStatusResponse, GetUnitNodeStatsResponse, GetUnitNodeStatsRequest } from './types/storage';
2
2
  import { IApiClient, ProviderType } from './interfaces';
3
3
  import { ActivateResponse, DeactivateResponse, LoginBody, LoginResponse, LogoutResponse, OAuth2ResponseDTO, RefreshTokenErrorResponse, RefreshTokenResponse, RegisterBody, RegisterResponse } from './types/auth';
4
+ import { StatsQueryParams, StatsResponse } from './types/stats';
4
5
  interface ErrorResponse {
5
6
  message?: string;
6
7
  [key: string]: any;
@@ -59,5 +60,8 @@ export declare class ApiClient implements IApiClient {
59
60
  userLogout: (refreshToken: string) => Promise<LogoutResponse>;
60
61
  userActivate: () => Promise<ActivateResponse>;
61
62
  userDeactivate: () => Promise<DeactivateResponse>;
63
+ private validateDateRange;
64
+ getDailyStats: (params: StatsQueryParams) => Promise<StatsResponse>;
65
+ getMinuteStats: (params: StatsQueryParams) => Promise<StatsResponse>;
62
66
  }
63
67
  export {};
@@ -218,9 +218,47 @@ class ApiClient {
218
218
  this.userDeactivate = async () => {
219
219
  return this.makeRequest('user/deactivate', 'POST', null);
220
220
  };
221
+ this.getDailyStats = async (params) => {
222
+ this.validateDateRange(params.startDate, params.endDate);
223
+ const queryParams = new URLSearchParams();
224
+ if (params.startDate) {
225
+ queryParams.append('startDate', params.startDate.toISOString());
226
+ }
227
+ if (params.endDate) {
228
+ queryParams.append('endDate', params.endDate.toISOString());
229
+ }
230
+ if (params.repoId && params.repoId.length > 0)
231
+ queryParams.append('repoId', params.repoId.join(','));
232
+ if (params.unitId && params.unitId.length > 0)
233
+ queryParams.append('unitId', params.unitId.join(','));
234
+ return this.makeRequest(`stats/daily?${queryParams.toString()}`, 'GET', null);
235
+ };
236
+ this.getMinuteStats = async (params) => {
237
+ this.validateDateRange(params.startDate, params.endDate);
238
+ const queryParams = new URLSearchParams();
239
+ if (params.startDate) {
240
+ queryParams.append('startDate', params.startDate.toISOString());
241
+ }
242
+ if (params.endDate) {
243
+ queryParams.append('endDate', params.endDate.toISOString());
244
+ }
245
+ if (params.repoId && params.repoId.length > 0)
246
+ queryParams.append('repoId', params.repoId.join(','));
247
+ if (params.unitId && params.unitId.length > 0)
248
+ queryParams.append('unitId', params.unitId.join(','));
249
+ return this.makeRequest(`stats/minute?${queryParams.toString()}`, 'GET', null);
250
+ };
221
251
  this.baseURL = baseURL;
222
252
  this.headers = {};
223
253
  this.debug = false;
224
254
  }
255
+ ////// Stats API
256
+ validateDateRange(startDate, endDate) {
257
+ if (startDate && endDate) {
258
+ if (startDate > endDate) {
259
+ throw new Error('startDate cannot be greater than endDate');
260
+ }
261
+ }
262
+ }
225
263
  }
226
264
  exports.ApiClient = ApiClient;
@@ -1,5 +1,6 @@
1
1
  import { StorageUnit, CreateUnitRequest, CreateUnitResponse, CreateRepoRequest, CreateRepoResponse, CreateRepoKeyRequest, CreateRepoKeyResponse, GetUnitsResponse, GetReposResponse, GetRepoKeysResponse, UpdateUnitRequest, UpdateUnitResponse, ActionResponse, UpdateRepoResponse, UpdateRepoRequest, UpdateRepoKeyRequest, UpdateRepoKeyResponse, ValidateUnitRequest, ValidateUnitResponse, ValidateRepoUnitsRequest, ValidateRepoUnitsResponse, GetUnitNodeStatsRequest, GetUnitNodeStatsResponse } from "./types/storage";
2
2
  import { RegisterBody, LoginBody, RegisterResponse, LoginResponse, LogoutResponse, ActivateResponse, DeactivateResponse, RefreshTokenResponse, RefreshTokenErrorResponse } from "./types/auth";
3
+ import { StatsQueryParams, StatsResponse } from "./types/stats";
3
4
  export declare enum ProviderType {
4
5
  GOOGLE = "GOOGLE",
5
6
  GITHUB = "GITHUB",
@@ -31,4 +32,6 @@ export interface IApiClient {
31
32
  userLogout(refreshToken: string): Promise<LogoutResponse>;
32
33
  userActivate(): Promise<ActivateResponse>;
33
34
  userDeactivate(): Promise<DeactivateResponse>;
35
+ getDailyStats(params: StatsQueryParams): Promise<StatsResponse>;
36
+ getMinuteStats(params: StatsQueryParams): Promise<StatsResponse>;
34
37
  }
@@ -0,0 +1,20 @@
1
+ export interface StatsQueryParams {
2
+ startDate?: Date;
3
+ endDate?: Date;
4
+ repoId?: string[];
5
+ unitId?: string[];
6
+ }
7
+ export interface StatsResponse {
8
+ success: boolean;
9
+ data: StatsData[];
10
+ message?: string;
11
+ }
12
+ export interface StatsData {
13
+ timestamp: number;
14
+ repoId: string;
15
+ unitId: string;
16
+ upl_bytes: bigint;
17
+ dwl_bytes: bigint;
18
+ size_change: bigint;
19
+ latency_ms: number;
20
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -18,6 +18,8 @@ export interface SignedUrlOptions {
18
18
  export declare class FlashbackGCSStorage extends Storage {
19
19
  protected credentials: ServiceCredentials;
20
20
  private debug;
21
+ apiEndpoint: string;
22
+ private currentUploadContentType;
21
23
  constructor(opts: FlashbackStorageOptions);
22
24
  cleanup(): void;
23
25
  bucket(name: string): import("@google-cloud/storage").Bucket;
@@ -7,14 +7,18 @@ exports.FlashbackGCSStorage = void 0;
7
7
  const storage_1 = require("@google-cloud/storage");
8
8
  const oauth2_1 = require("./oauth2");
9
9
  const crypto_1 = __importDefault(require("crypto"));
10
+ const node_fetch_1 = __importDefault(require("node-fetch"));
10
11
  const originalRequest = require('gaxios').instance.request;
12
+ const originalFetch = node_fetch_1.default;
11
13
  class FlashbackGCSStorage extends storage_1.Storage {
12
14
  constructor(opts) {
13
15
  const { credentials, apiEndpoint = 'https://gcs-us-east-1-gcp.flashback.tech', tokenScopes = ['READ', 'WRITE'], ...rest } = opts;
14
- const authClient = new oauth2_1.FlashbackAuthClient(apiEndpoint + '/token', tokenScopes, credentials);
16
+ // Ensure the endpoint doesn't have a trailing slash
17
+ const cleanEndpoint = apiEndpoint.replace(/\/$/, '');
18
+ const authClient = new oauth2_1.FlashbackAuthClient(cleanEndpoint + '/token', tokenScopes, credentials);
15
19
  super({
16
20
  ...rest,
17
- apiEndpoint,
21
+ apiEndpoint: cleanEndpoint,
18
22
  authClient,
19
23
  useAuthWithCustomEndpoint: true,
20
24
  retryOptions: {
@@ -24,6 +28,7 @@ class FlashbackGCSStorage extends storage_1.Storage {
24
28
  });
25
29
  this.debug = false;
26
30
  this.credentials = credentials;
31
+ this.apiEndpoint = cleanEndpoint;
27
32
  // Intercept Gaxios instance creation
28
33
  require('gaxios').instance.request = async function (opts) {
29
34
  // Add auth headers to all requests
@@ -32,15 +37,45 @@ class FlashbackGCSStorage extends storage_1.Storage {
32
37
  ...(opts.headers || {}),
33
38
  ...headers,
34
39
  };
40
+ // Ensure the base URL is used and properly handle query parameters
41
+ if (!opts.url.startsWith('http')) {
42
+ // Remove any trailing question marks
43
+ const cleanUrl = opts.url.replace(/\?+$/, '');
44
+ opts.url = `${cleanEndpoint}${cleanUrl}`;
45
+ }
35
46
  return originalRequest.call(this, opts);
36
47
  };
48
+ // Intercept node-fetch
49
+ global.fetch = async (url, options = {}) => {
50
+ const headers = await authClient.getRequestHeaders();
51
+ const finalUrl = url.startsWith('http') ? url : `${cleanEndpoint}${url}`;
52
+ return originalFetch(finalUrl, {
53
+ ...options,
54
+ headers: {
55
+ ...(options.headers || {}),
56
+ ...headers,
57
+ },
58
+ });
59
+ };
37
60
  }
38
61
  cleanup() {
39
62
  require('gaxios').instance.request = originalRequest;
63
+ global.fetch = originalFetch;
40
64
  }
41
65
  // Override the bucket method to ensure we pass the auth client
42
66
  bucket(name) {
43
- return super.bucket(name);
67
+ const bucket = super.bucket(name);
68
+ const originalUpload = bucket.upload.bind(bucket);
69
+ bucket.upload = async (filePath, options) => {
70
+ this.currentUploadContentType = options?.contentType;
71
+ try {
72
+ return await originalUpload(filePath, options);
73
+ }
74
+ finally {
75
+ this.currentUploadContentType = undefined;
76
+ }
77
+ };
78
+ return bucket;
44
79
  }
45
80
  setDebug(debug) {
46
81
  this.debug = debug;
@@ -71,12 +106,13 @@ class FlashbackGCSStorage extends storage_1.Storage {
71
106
  if (contentType) {
72
107
  extensionHeaders['content-type'] = contentType;
73
108
  }
74
- const signedHeaders = Object.keys(extensionHeaders)
109
+ // Sort headers once and use the same order for both signedHeaders and canonicalHeaders
110
+ const sortedHeaderKeys = Object.keys(extensionHeaders)
75
111
  .map(header => header.toLowerCase())
76
- .sort()
77
- .join(';');
78
- const canonicalHeaders = Object.entries(extensionHeaders)
79
- .map(([key, value]) => `${key.toLowerCase()}:${value}`)
112
+ .sort();
113
+ const signedHeaders = sortedHeaderKeys.join(';');
114
+ const canonicalHeaders = sortedHeaderKeys
115
+ .map(key => `${key}:${extensionHeaders[key.toLowerCase()]}`)
80
116
  .join('\n') + '\n';
81
117
  const datestamp = accessibleAt.toISOString().split('T')[0];
82
118
  const credentialScope = `${datestamp}/auto/storage/goog4_request`;
@@ -120,7 +156,8 @@ class FlashbackGCSStorage extends storage_1.Storage {
120
156
  credentialScope,
121
157
  crypto_1.default.createHash('sha256').update(canonicalRequest).digest('hex'),
122
158
  ].join('\n');
123
- this.doLog('String to Sign:', stringToSign);
159
+ const canonicalRequestHex = Buffer.from(canonicalRequest).toString('hex');
160
+ this.doLog('String to Sign:', stringToSign, canonicalRequestHex);
124
161
  const sign = crypto_1.default.createSign('RSA-SHA256');
125
162
  sign.update(stringToSign);
126
163
  const signature = sign.sign(this.credentials.private_key, 'hex');
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generate a 64 bytes base64 block ID string.
3
+ *
4
+ * @export
5
+ * @param {number} blockIndex
6
+ * @returns {string}
7
+ */
8
+ export declare function generateBlockID(blockIDPrefix: string, blockIndex: number): string;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateBlockID = generateBlockID;
4
+ /**
5
+ * Generate a 64 bytes base64 block ID string.
6
+ *
7
+ * @export
8
+ * @param {number} blockIndex
9
+ * @returns {string}
10
+ */
11
+ function generateBlockID(blockIDPrefix, blockIndex) {
12
+ // To generate a 64 bytes base64 string, source string should be 48
13
+ const maxSourceStringLength = 48;
14
+ // A blob can have a maximum of 100,000 uncommitted blocks at any given time
15
+ const maxBlockIndexLength = 6;
16
+ const maxAllowedBlockIDPrefixLength = maxSourceStringLength - maxBlockIndexLength;
17
+ if (blockIDPrefix.length > maxAllowedBlockIDPrefixLength) {
18
+ blockIDPrefix = blockIDPrefix.slice(0, maxAllowedBlockIDPrefixLength);
19
+ }
20
+ const res = blockIDPrefix +
21
+ blockIndex.toString().padStart(maxSourceStringLength - blockIDPrefix.length, "0");
22
+ return Buffer.from(res).toString("base64");
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flashbacktech/flashbackclient",
3
- "version": "0.0.93",
3
+ "version": "0.0.95",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -24,9 +24,9 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@aws-sdk/client-sts": "^3.777.0",
27
+ "@google-cloud/storage": "^7.15.0",
27
28
  "@stellar/stellar-sdk": "^13.0.0",
28
29
  "formdata-node": "^6.0.3",
29
- "@google-cloud/storage": "^7.15.0",
30
30
  "google-auth-library": "^9.15.1"
31
31
  },
32
32
  "bin": {
@@ -55,6 +55,7 @@
55
55
  "devDependencies": {
56
56
  "@aws-sdk/client-s3": "^3.777.0",
57
57
  "@aws-sdk/s3-request-presigner": "^3.782.0",
58
+ "@azure/storage-blob": "^12.27.0",
58
59
  "@eslint/js": "^8.56.0",
59
60
  "@types/jest": "^29.5.14",
60
61
  "@types/node": "^22.10.1",