@lokalise/file-storage-service-sdk
Advanced tools
Comparing version 6.1.0 to 6.2.0
import type { ErrorReporter } from '@lokalise/node-core'; | ||
import type { TempFilePathResolver } from './fileStorageClient'; | ||
export interface FileStorageClientConfig { | ||
@@ -6,2 +7,4 @@ baseUrl: string; | ||
errorReporter?: ErrorReporter; | ||
tempFilePathResolver?: TempFilePathResolver; | ||
createDefaultContentDirectory?: boolean; | ||
} | ||
@@ -8,0 +11,0 @@ export interface RetryConfig { |
/// <reference types="node" /> | ||
import type { Either } from '@lokalise/node-core/dist/src/errors/either'; | ||
import type { Either } from '@lokalise/node-core'; | ||
import type { FileStorageClientConfig } from './configModels'; | ||
import type { DownloadError, FileData, FileMetadata, FileUploadData, HttpRequestContext } from './model'; | ||
import type { ContentHeaders, ErrorResponse, UseCaseEnum } from './schema/storageSchemas'; | ||
export type TempFilePathResolver = (fileData: FileData) => Promise<string>; | ||
export declare const defaultTempFilePathResolver: TempFilePathResolver; | ||
export declare class FileStorageClient { | ||
@@ -10,2 +12,3 @@ private readonly retryConfig; | ||
private readonly fileStorageHttpClient; | ||
private readonly tempFilePathResolver; | ||
constructor(config: FileStorageClientConfig); | ||
@@ -12,0 +15,0 @@ uploadFile(ownerId: string, useCase: UseCaseEnum, fileData: FileData, contentHeaders: ContentHeaders, requestContext: HttpRequestContext): Promise<Either<ErrorResponse, string>>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.FileStorageClient = void 0; | ||
exports.FileStorageClient = exports.defaultTempFilePathResolver = void 0; | ||
const node_fs_1 = require("node:fs"); | ||
const node_path_1 = require("node:path"); | ||
const node_core_1 = require("@lokalise/node-core"); | ||
const tmp_1 = require("tmp"); | ||
const configModels_1 = require("./configModels"); | ||
const errorHandling_1 = require("./errorHandling"); | ||
const restUtils_1 = require("./utils/restUtils"); | ||
const DEFAULT_UPLOAD_FILES_SUBDIR = 'fss-uploads'; | ||
const defaultTempFilePathResolver = () => { | ||
return new Promise((resolve, reject) => { | ||
(0, tmp_1.tmpName)({ | ||
dir: DEFAULT_UPLOAD_FILES_SUBDIR, | ||
}, (err, filepath) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
return resolve(filepath); | ||
}); | ||
}); | ||
}; | ||
exports.defaultTempFilePathResolver = defaultTempFilePathResolver; | ||
function isReadableStream(obj) { | ||
return obj != null && typeof obj === 'object' && typeof obj.pipe === 'function'; | ||
} | ||
class FileStorageClient { | ||
@@ -12,2 +32,3 @@ retryConfig; | ||
fileStorageHttpClient; | ||
tempFilePathResolver; | ||
constructor(config) { | ||
@@ -17,2 +38,12 @@ this.fileStorageHttpClient = (0, node_core_1.buildClient)(config.baseUrl); | ||
this.errorReporter = config.errorReporter; | ||
this.tempFilePathResolver = config.tempFilePathResolver ?? exports.defaultTempFilePathResolver; | ||
if (config.createDefaultContentDirectory !== false) { | ||
const filePath = (0, tmp_1.tmpNameSync)({ | ||
dir: DEFAULT_UPLOAD_FILES_SUBDIR, | ||
}); | ||
const dirPath = (0, node_path_1.dirname)(filePath); | ||
if (!(0, node_fs_1.existsSync)(dirPath)) { | ||
(0, node_fs_1.mkdirSync)(dirPath, { recursive: true }); | ||
} | ||
} | ||
} | ||
@@ -23,2 +54,15 @@ async uploadFile(ownerId, useCase, fileData, contentHeaders, requestContext) { | ||
return { error: uploadUrlResponse.error }; | ||
let contentToSend = fileData.file; | ||
let contentLength = undefined; | ||
let contentProvider; | ||
// This is necessary because S3 requires 'Content-Length' header for streams | ||
if (isReadableStream(fileData.file)) { | ||
const tempFilePath = await this.tempFilePathResolver(fileData); | ||
contentProvider = await node_core_1.FsReadableProvider.persistReadableToFs({ | ||
sourceReadable: fileData.file, | ||
targetFile: tempFilePath, | ||
}); | ||
contentLength = await contentProvider.getContentLength(); | ||
contentToSend = await contentProvider.createStream(); | ||
} | ||
const headers = (0, node_core_1.copyWithoutUndefined)({ | ||
@@ -28,2 +72,3 @@ 'Content-Type': contentHeaders.contentType, | ||
'Content-Disposition': contentHeaders.contentDisposition, | ||
'Content-Length': contentLength, | ||
}); | ||
@@ -34,3 +79,3 @@ const uploadUrlObject = new URL(uploadUrlResponse.result.uploadUrl); | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call | ||
(0, node_core_1.buildClient)(uploadBaseUrl), uploadUrlObject.pathname, fileData.file, { | ||
(0, node_core_1.buildClient)(uploadBaseUrl), uploadUrlObject.pathname, contentToSend, { | ||
headers, | ||
@@ -41,2 +86,5 @@ safeParseJson: true, | ||
}); | ||
if (contentProvider) { | ||
await contentProvider.destroy(); | ||
} | ||
if (response.error) { | ||
@@ -43,0 +91,0 @@ this.maybeReportWithErrorReporter(response.error); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const node_stream_1 = require("node:stream"); | ||
const mockttp_1 = require("mockttp"); | ||
@@ -80,2 +81,34 @@ const testFs_1 = require("../../test/fixtures/testFs"); | ||
}); | ||
it('Attempts to upload a file as a Readable correctly', async () => { | ||
expect.assertions(3); | ||
const uploadHost = 'http://localhost:8086'; | ||
const uploadPath = '/upload-files-here'; | ||
const uploadUrl = `${uploadHost}${uploadPath}`; | ||
const fileId = 777; | ||
const postReply = { | ||
data: { | ||
uploadUrl, | ||
fileId, | ||
}, | ||
}; | ||
await mockServer.forPost(testStorageClient_1.uploadInterceptData.path).thenReply(200, JSON.stringify(postReply), { | ||
'content-type': 'application/json', | ||
}); | ||
await mockStorageServer.forPut(uploadPath).thenCallback((req) => { | ||
expect(req.headers).toEqual({ | ||
connection: 'keep-alive', | ||
'content-length': '22', | ||
'content-type': 'application/json', | ||
host: expect.any(String), | ||
}); | ||
return { statusCode: 200 }; | ||
}); | ||
await expect(fileStorageClient.uploadFile(testFs_1.FS_TEST_DATA.PROJECT_ID, 'import', { | ||
...testFs_1.FS_TEST_DATA.FILE_DESCRIPTION, | ||
file: node_stream_1.Readable.from(node_stream_1.Readable.from(testFs_1.FS_TEST_DATA.FILE_DESCRIPTION.file.toString())), | ||
}, { | ||
contentType: 'application/json', | ||
}, testFs_1.FS_TEST_DATA.REQUEST_CONTEXT)).resolves.toEqual({ result: fileId }); | ||
expect(reportFunction).not.toHaveBeenCalled(); | ||
}); | ||
it('Attempts to upload a file correctly with query params', async () => { | ||
@@ -82,0 +115,0 @@ const uploadHost = 'http://localhost:8086'; |
@@ -67,4 +67,4 @@ import z from 'zod'; | ||
}, "strip", z.ZodTypeAny, { | ||
filename: string; | ||
status: "uploading" | "uploaded" | "deleting" | "deleted"; | ||
filename: string; | ||
mimeType: string; | ||
@@ -76,4 +76,4 @@ metadata: {} & { | ||
}, { | ||
filename: string; | ||
status: "uploading" | "uploaded" | "deleting" | "deleted"; | ||
filename: string; | ||
mimeType: string; | ||
@@ -87,4 +87,4 @@ metadata: {} & { | ||
data: { | ||
filename: string; | ||
status: "uploading" | "uploaded" | "deleting" | "deleted"; | ||
filename: string; | ||
mimeType: string; | ||
@@ -98,4 +98,4 @@ metadata: {} & { | ||
data: { | ||
filename: string; | ||
status: "uploading" | "uploaded" | "deleting" | "deleted"; | ||
filename: string; | ||
mimeType: string; | ||
@@ -102,0 +102,0 @@ metadata: {} & { |
{ | ||
"name": "@lokalise/file-storage-service-sdk", | ||
"version": "6.1.0", | ||
"version": "6.2.0", | ||
"description": "SDK for file-storage-service", | ||
@@ -43,31 +43,30 @@ "author": { | ||
"test:ci": "npm run lint && npm run test:coverage", | ||
"lint": "eslint .", | ||
"lint:fix": "eslint . --fix", | ||
"format": "prettier --write .", | ||
"lint": "eslint . --cache --max-warnings=0 && prettier --check --log-level warn lib test \"**/*.{json,md}\" && tsc --noEmit", | ||
"lint:fix": "eslint . --fix && prettier --write lib test \"**/*.{json,md}\"", | ||
"version": "auto-changelog -p && git add CHANGELOG.md" | ||
}, | ||
"dependencies": { | ||
"@lokalise/node-core": "^9.0.0", | ||
"@lokalise/zod-extras": "^1.3.1", | ||
"undici": "^6.0.1", | ||
"zod": "^3.19.1" | ||
"@lokalise/node-core": "^9.22.0", | ||
"@lokalise/zod-extras": "^2.1.0", | ||
"tmp": "^0.2.3", | ||
"undici": "^6.18.2", | ||
"zod": "^3.23.8" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^29.4.0", | ||
"@types/node": "^18.13.0", | ||
"@typescript-eslint/eslint-plugin": "^6.6.0", | ||
"@typescript-eslint/parser": "^6.6.0", | ||
"@types/node": "^20.13.0", | ||
"@types/tmp": "^0.2.6", | ||
"@typescript-eslint/eslint-plugin": "^7.11.0", | ||
"@typescript-eslint/parser": "^7.11.0", | ||
"auto-changelog": "^2.4.0", | ||
"eslint": "^8.34.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-plugin-jest": "^27.2.1", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"jest": "^29.4.3", | ||
"mockttp": "^3.6.3", | ||
"prettier": "^2.8.4", | ||
"eslint": "^8.57.0", | ||
"eslint-plugin-import": "^2.29.1", | ||
"eslint-plugin-jest": "^28.5.0", | ||
"jest": "^29.7.0", | ||
"mockttp": "^3.11.0", | ||
"prettier": "^3.3.0", | ||
"shx": "^0.3.4", | ||
"ts-jest": "^29.0.5", | ||
"ts-node": "^10.9.1", | ||
"typescript": "5.3.3" | ||
"ts-jest": "^29.1.4", | ||
"ts-node": "^10.9.2", | ||
"typescript": "5.4.5" | ||
}, | ||
@@ -74,0 +73,0 @@ "engines": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
92150
16
1127
5
+ Addedtmp@^0.2.3
+ Added@lokalise/zod-extras@2.1.0(transitive)
+ Addedtmp@0.2.3(transitive)
- Removed@lokalise/zod-extras@1.3.2(transitive)
Updated@lokalise/node-core@^9.22.0
Updated@lokalise/zod-extras@^2.1.0
Updatedundici@^6.18.2
Updatedzod@^3.23.8