@kmlckj/licos-platform-sdk 0.1.0 → 0.2.0

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.
package/README.md CHANGED
@@ -1,3 +1,32 @@
1
1
  # @kmlckj/licos-platform-sdk
2
2
 
3
- Node.js SDK for LICOS platform storage upload, download, and preview APIs.
3
+ LICOS 平台对象存储 SDK,兼容前端和后端调用。
4
+
5
+ ## 后端模式
6
+
7
+ - `baseUrl` / `token` / `userId` / `projectId` 不传时,会回退读取 `LICOS_*` 环境变量
8
+ - `uploadUser` / `uploadProject` 支持直接传本地文件路径
9
+ - `download(url, outputPath)` 会把文件写到本地路径
10
+
11
+ ```js
12
+ import { uploadProject, download } from '@kmlckj/licos-platform-sdk';
13
+
14
+ const uploaded = await uploadProject('/tmp/report.pdf');
15
+ await download(uploaded.download_url, '/tmp/report-downloaded.pdf');
16
+ ```
17
+
18
+ ## 前端模式
19
+
20
+ - 默认走同源相对地址;也可以显式传 `baseUrl`
21
+ - 上传支持 `File` / `Blob` / `ArrayBuffer` / TypedArray / `{ data, fileName, contentType }`
22
+ - `download(url)` 返回 `Blob`
23
+
24
+ ```js
25
+ import { uploadUser, download } from '@kmlckj/licos-platform-sdk';
26
+
27
+ const uploaded = await uploadUser(fileInput.files[0], {
28
+ token: userJwt,
29
+ });
30
+
31
+ const blob = await download(uploaded.download_url);
32
+ ```
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@kmlckj/licos-platform-sdk",
3
- "version": "0.1.0",
4
- "description": "LICOS platform SDK for storage upload, download, and preview",
3
+ "version": "0.2.0",
4
+ "description": "LICOS platform SDK for storage upload, download, and preview in browser and Node runtimes",
5
5
  "author": "kmlckj",
6
6
  "type": "module",
7
7
  "main": "./src/index.js",
8
+ "browser": "./src/browser.js",
8
9
  "types": "./src/index.d.ts",
9
10
  "exports": {
10
11
  ".": {
11
12
  "types": "./src/index.d.ts",
13
+ "browser": "./src/browser.js",
12
14
  "import": "./src/index.js",
13
15
  "default": "./src/index.js"
14
16
  }
package/src/browser.js ADDED
@@ -0,0 +1,101 @@
1
+ import {
2
+ ApiError,
3
+ ConfigurationError,
4
+ PlatformSdkError,
5
+ downloadBlob,
6
+ encodeObjectKey,
7
+ encodeStorageSegment,
8
+ normalizeBaseUrl,
9
+ resolveProjectId,
10
+ resolveUserId,
11
+ uploadFile,
12
+ } from './shared.js';
13
+
14
+ export class StorageClient {
15
+ constructor(options = {}) {
16
+ this.baseUrl = options.baseUrl;
17
+ this.token = options.token;
18
+ this.userId = options.userId;
19
+ this.projectId = options.projectId;
20
+ this.fetchImpl = options.fetch ?? fetch;
21
+ this.headers = options.headers;
22
+ this.credentials = options.credentials;
23
+ }
24
+
25
+ previewUrl(bucket, objectKey) {
26
+ const baseUrl = normalizeBaseUrl(this.baseUrl);
27
+ return `${baseUrl}/api/v1/public/storage/${encodeStorageSegment(bucket)}/${encodeObjectKey(objectKey)}`;
28
+ }
29
+
30
+ downloadUrl(bucket, objectKey) {
31
+ return `${this.previewUrl(bucket, objectKey)}?download=1`;
32
+ }
33
+
34
+ async uploadUser(file) {
35
+ const baseUrl = normalizeBaseUrl(this.baseUrl);
36
+ return uploadFile(this.fetchImpl, `${baseUrl}/api/v1/storage/upload/user`, file, {
37
+ token: this.token,
38
+ headers: this.headers,
39
+ credentials: this.credentials,
40
+ });
41
+ }
42
+
43
+ async uploadProject(file, options = {}) {
44
+ const baseUrl = normalizeBaseUrl(this.baseUrl);
45
+ const userId = resolveUserId(options.userId ?? this.userId);
46
+ const projectId = resolveProjectId(options.projectId ?? this.projectId);
47
+ return uploadFile(
48
+ this.fetchImpl,
49
+ `${baseUrl}/api/v1/storage/upload/project/${encodeStorageSegment(userId)}/${encodeStorageSegment(projectId)}`,
50
+ file,
51
+ {
52
+ token: this.token,
53
+ headers: this.headers,
54
+ credentials: this.credentials,
55
+ },
56
+ );
57
+ }
58
+
59
+ async download(url, outputPath) {
60
+ if (outputPath !== undefined) {
61
+ throw new ConfigurationError(
62
+ 'Front-end mode does not support downloading directly to a filesystem path. Omit outputPath to receive a Blob.',
63
+ );
64
+ }
65
+ return downloadBlob(this.fetchImpl, url, {
66
+ headers: this.headers,
67
+ credentials: this.credentials,
68
+ });
69
+ }
70
+ }
71
+
72
+ function defaultClient(options = {}) {
73
+ return new StorageClient(options);
74
+ }
75
+
76
+ export function previewUrl(bucket, objectKey, options = {}) {
77
+ return defaultClient(options).previewUrl(bucket, objectKey);
78
+ }
79
+
80
+ export function downloadUrl(bucket, objectKey, options = {}) {
81
+ return defaultClient(options).downloadUrl(bucket, objectKey);
82
+ }
83
+
84
+ export async function uploadUser(file, options = {}) {
85
+ return defaultClient(options).uploadUser(file);
86
+ }
87
+
88
+ export async function uploadProject(file, options = {}) {
89
+ return defaultClient(options).uploadProject(file, options);
90
+ }
91
+
92
+ export async function download(url, outputPathOrOptions, maybeOptions = {}) {
93
+ if (typeof outputPathOrOptions === 'string') {
94
+ throw new ConfigurationError(
95
+ 'Front-end mode does not support downloading directly to a filesystem path. Omit outputPath to receive a Blob.',
96
+ );
97
+ }
98
+ return defaultClient(outputPathOrOptions ?? maybeOptions).download(url);
99
+ }
100
+
101
+ export { PlatformSdkError, ConfigurationError, ApiError };
package/src/index.d.ts CHANGED
@@ -8,12 +8,35 @@ export interface UploadResult {
8
8
  download_url: string;
9
9
  }
10
10
 
11
+ export interface BlobLike {
12
+ arrayBuffer(): Promise<ArrayBuffer>;
13
+ readonly size?: number;
14
+ readonly type?: string;
15
+ readonly name?: string;
16
+ }
17
+
18
+ export interface UploadBinaryDescriptor {
19
+ data?: ArrayBuffer | ArrayBufferView | BlobLike;
20
+ file?: ArrayBuffer | ArrayBufferView | BlobLike;
21
+ fileName?: string;
22
+ contentType?: string;
23
+ }
24
+
25
+ export type UploadSource =
26
+ | string
27
+ | ArrayBuffer
28
+ | ArrayBufferView
29
+ | BlobLike
30
+ | UploadBinaryDescriptor;
31
+
11
32
  export interface StorageClientOptions {
12
33
  baseUrl?: string;
13
34
  token?: string;
14
35
  userId?: string;
15
36
  projectId?: string;
16
37
  fetch?: typeof fetch;
38
+ headers?: HeadersInit;
39
+ credentials?: RequestCredentials;
17
40
  }
18
41
 
19
42
  export interface UploadProjectOptions extends StorageClientOptions {
@@ -34,8 +57,9 @@ export class StorageClient {
34
57
  constructor(options?: StorageClientOptions);
35
58
  previewUrl(bucket: string, objectKey: string): string;
36
59
  downloadUrl(bucket: string, objectKey: string): string;
37
- uploadUser(filePath: string): Promise<UploadResult>;
38
- uploadProject(filePath: string, options?: UploadProjectOptions): Promise<UploadResult>;
60
+ uploadUser(file: UploadSource): Promise<UploadResult>;
61
+ uploadProject(file: UploadSource, options?: UploadProjectOptions): Promise<UploadResult>;
62
+ download(url: string): Promise<Blob>;
39
63
  download(url: string, outputPath: string): Promise<string>;
40
64
  }
41
65
 
@@ -50,13 +74,14 @@ export function downloadUrl(
50
74
  options?: StorageClientOptions,
51
75
  ): string;
52
76
  export function uploadUser(
53
- filePath: string,
77
+ file: UploadSource,
54
78
  options?: StorageClientOptions,
55
79
  ): Promise<UploadResult>;
56
80
  export function uploadProject(
57
- filePath: string,
81
+ file: UploadSource,
58
82
  options?: UploadProjectOptions,
59
83
  ): Promise<UploadResult>;
84
+ export function download(url: string, options?: StorageClientOptions): Promise<Blob>;
60
85
  export function download(
61
86
  url: string,
62
87
  outputPath: string,
package/src/index.js CHANGED
@@ -1,89 +1,20 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
 
4
- export class PlatformSdkError extends Error {
5
- constructor(message, options = {}) {
6
- super(message);
7
- this.name = new.target.name;
8
- this.status = options.status;
9
- this.code = options.code;
10
- this.details = options.details;
11
- }
12
- }
13
-
14
- export class ConfigurationError extends PlatformSdkError {}
15
- export class ApiError extends PlatformSdkError {}
16
-
17
- function requireEnv(name) {
18
- const value = process.env[name];
19
- if (value) {
20
- return value;
21
- }
22
- throw new ConfigurationError(`Missing required environment variable: ${name}`);
23
- }
24
-
25
- function normalizeBaseUrl(baseUrl) {
26
- return (baseUrl ?? requireEnv('LICOS_API_BASE_URL')).replace(/\/+$/, '');
27
- }
28
-
29
- function resolveToken(token) {
30
- return token ?? requireEnv('LICOS_USER_TOKEN');
31
- }
32
-
33
- function resolveUserId(userId) {
34
- return userId ?? requireEnv('LICOS_USER_ID');
35
- }
36
-
37
- function resolveProjectId(projectId) {
38
- return projectId ?? requireEnv('LICOS_PROJECT_ID');
39
- }
40
-
41
- function encodeStorageSegment(value) {
42
- return encodeURIComponent(value);
43
- }
44
-
45
- function encodeObjectKey(value) {
46
- return value.split('/').map(encodeStorageSegment).join('/');
47
- }
48
-
49
- function guessContentType(filePath) {
50
- switch (path.extname(filePath).toLowerCase()) {
51
- case '.txt':
52
- return 'text/plain';
53
- case '.json':
54
- return 'application/json';
55
- case '.pdf':
56
- return 'application/pdf';
57
- case '.png':
58
- return 'image/png';
59
- case '.jpg':
60
- case '.jpeg':
61
- return 'image/jpeg';
62
- default:
63
- return 'application/octet-stream';
64
- }
65
- }
66
-
67
- async function readJsonEnvelope(response) {
68
- let payload;
69
- try {
70
- payload = await response.json();
71
- } catch (error) {
72
- throw new ApiError('Platform API returned non-JSON response', {
73
- status: response.status,
74
- details: error instanceof Error ? error.message : String(error),
75
- });
76
- }
77
-
78
- if (!response.ok || payload?.code !== 0) {
79
- throw new ApiError(payload?.message ?? `Platform API failed: HTTP ${response.status}`, {
80
- status: response.status,
81
- code: payload?.code,
82
- details: payload?.data ?? payload,
83
- });
84
- }
85
- return payload.data;
86
- }
4
+ import {
5
+ ApiError,
6
+ ConfigurationError,
7
+ PlatformSdkError,
8
+ downloadBlob,
9
+ encodeObjectKey,
10
+ encodeStorageSegment,
11
+ fileNameFromPath,
12
+ guessContentType,
13
+ normalizeBaseUrl,
14
+ resolveProjectId,
15
+ resolveUserId,
16
+ uploadFile,
17
+ } from './shared.js';
87
18
 
88
19
  export class StorageClient {
89
20
  constructor(options = {}) {
@@ -92,6 +23,8 @@ export class StorageClient {
92
23
  this.userId = options.userId;
93
24
  this.projectId = options.projectId;
94
25
  this.fetchImpl = options.fetch ?? fetch;
26
+ this.headers = options.headers;
27
+ this.credentials = options.credentials;
95
28
  }
96
29
 
97
30
  previewUrl(bucket, objectKey) {
@@ -103,53 +36,56 @@ export class StorageClient {
103
36
  return `${this.previewUrl(bucket, objectKey)}?download=1`;
104
37
  }
105
38
 
106
- async uploadUser(filePath) {
39
+ async uploadUser(file) {
107
40
  const baseUrl = normalizeBaseUrl(this.baseUrl);
108
- return this.#upload(`${baseUrl}/api/v1/storage/upload/user`, filePath);
41
+ return this.#upload(`${baseUrl}/api/v1/storage/upload/user`, file);
109
42
  }
110
43
 
111
- async uploadProject(filePath, options = {}) {
44
+ async uploadProject(file, options = {}) {
112
45
  const baseUrl = normalizeBaseUrl(this.baseUrl);
113
46
  const userId = resolveUserId(options.userId ?? this.userId);
114
47
  const projectId = resolveProjectId(options.projectId ?? this.projectId);
115
48
  return this.#upload(
116
49
  `${baseUrl}/api/v1/storage/upload/project/${encodeStorageSegment(userId)}/${encodeStorageSegment(projectId)}`,
117
- filePath,
50
+ file,
118
51
  );
119
52
  }
120
53
 
121
54
  async download(url, outputPath) {
122
- const response = await this.fetchImpl(url, { method: 'GET' });
123
- if (!response.ok) {
124
- throw new ApiError(`Platform download failed: HTTP ${response.status}`, {
125
- status: response.status,
126
- });
55
+ const blob = await downloadBlob(this.fetchImpl, url, {
56
+ headers: this.headers,
57
+ credentials: this.credentials,
58
+ });
59
+ if (outputPath === undefined) {
60
+ return blob;
127
61
  }
128
- const buffer = Buffer.from(await response.arrayBuffer());
62
+ const buffer = Buffer.from(await blob.arrayBuffer());
129
63
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
130
64
  await fs.writeFile(outputPath, buffer);
131
65
  return outputPath;
132
66
  }
133
67
 
134
- async #upload(url, filePath) {
135
- const token = resolveToken(this.token);
136
- const fileBuffer = await fs.readFile(filePath);
137
- const fileName = path.basename(filePath);
138
- const formData = new FormData();
139
- formData.append(
140
- 'file',
141
- new Blob([fileBuffer], { type: guessContentType(filePath) }),
142
- fileName,
143
- );
144
-
145
- const response = await this.fetchImpl(url, {
146
- method: 'POST',
147
- headers: {
148
- Authorization: `Bearer ${token}`,
68
+ async #upload(url, file) {
69
+ return uploadFile(
70
+ this.fetchImpl,
71
+ url,
72
+ file,
73
+ {
74
+ token: this.token,
75
+ headers: this.headers,
76
+ credentials: this.credentials,
149
77
  },
150
- body: formData,
151
- });
152
- return readJsonEnvelope(response);
78
+ async (filePath) => {
79
+ const fileBuffer = await fs.readFile(filePath);
80
+ const fileName = fileNameFromPath(filePath);
81
+ const contentType = guessContentType(fileName);
82
+ return {
83
+ fileName,
84
+ contentType,
85
+ blob: new Blob([fileBuffer], { type: contentType }),
86
+ };
87
+ },
88
+ );
153
89
  }
154
90
  }
155
91
 
@@ -173,6 +109,11 @@ export async function uploadProject(filePath, options = {}) {
173
109
  return defaultClient(options).uploadProject(filePath, options);
174
110
  }
175
111
 
176
- export async function download(url, outputPath, options = {}) {
177
- return defaultClient(options).download(url, outputPath);
112
+ export async function download(url, outputPathOrOptions, maybeOptions = {}) {
113
+ if (typeof outputPathOrOptions === 'string') {
114
+ return defaultClient(maybeOptions).download(url, outputPathOrOptions);
115
+ }
116
+ return defaultClient(outputPathOrOptions ?? {}).download(url);
178
117
  }
118
+
119
+ export { PlatformSdkError, ConfigurationError, ApiError };
package/src/shared.js ADDED
@@ -0,0 +1,256 @@
1
+ export class PlatformSdkError extends Error {
2
+ constructor(message, options = {}) {
3
+ super(message);
4
+ this.name = new.target.name;
5
+ this.status = options.status;
6
+ this.code = options.code;
7
+ this.details = options.details;
8
+ }
9
+ }
10
+
11
+ export class ConfigurationError extends PlatformSdkError {}
12
+ export class ApiError extends PlatformSdkError {}
13
+
14
+ export function isNodeRuntime() {
15
+ return typeof process !== 'undefined' && !!process?.versions?.node;
16
+ }
17
+
18
+ function readEnv(name) {
19
+ if (typeof process === 'undefined' || !process?.env) {
20
+ return undefined;
21
+ }
22
+ const value = process.env[name];
23
+ return value ? value : undefined;
24
+ }
25
+
26
+ export function requireEnv(name) {
27
+ const value = readEnv(name);
28
+ if (value) {
29
+ return value;
30
+ }
31
+ throw new ConfigurationError(`Missing required environment variable: ${name}`);
32
+ }
33
+
34
+ export function normalizeBaseUrl(baseUrl) {
35
+ if (typeof baseUrl === 'string') {
36
+ return baseUrl.replace(/\/+$/, '');
37
+ }
38
+
39
+ const envBaseUrl = readEnv('LICOS_API_BASE_URL');
40
+ if (envBaseUrl) {
41
+ return envBaseUrl.replace(/\/+$/, '');
42
+ }
43
+
44
+ if (!isNodeRuntime()) {
45
+ return '';
46
+ }
47
+
48
+ throw new ConfigurationError('Missing required environment variable: LICOS_API_BASE_URL');
49
+ }
50
+
51
+ export function resolveUserId(userId) {
52
+ return userId ?? requireEnv('LICOS_USER_ID');
53
+ }
54
+
55
+ export function resolveProjectId(projectId) {
56
+ return projectId ?? requireEnv('LICOS_PROJECT_ID');
57
+ }
58
+
59
+ export function encodeStorageSegment(value) {
60
+ return encodeURIComponent(value);
61
+ }
62
+
63
+ export function encodeObjectKey(value) {
64
+ return value.split('/').map(encodeStorageSegment).join('/');
65
+ }
66
+
67
+ export function fileNameFromPath(filePath) {
68
+ const parts = String(filePath).split(/[\\/]/);
69
+ const fileName = parts[parts.length - 1];
70
+ return fileName || 'upload.bin';
71
+ }
72
+
73
+ export function guessContentType(fileName) {
74
+ const ext = /\.[^./\\]+$/.exec(String(fileName).toLowerCase())?.[0] ?? '';
75
+ switch (ext) {
76
+ case '.txt':
77
+ return 'text/plain';
78
+ case '.json':
79
+ return 'application/json';
80
+ case '.pdf':
81
+ return 'application/pdf';
82
+ case '.png':
83
+ return 'image/png';
84
+ case '.jpg':
85
+ case '.jpeg':
86
+ return 'image/jpeg';
87
+ case '.gif':
88
+ return 'image/gif';
89
+ case '.webp':
90
+ return 'image/webp';
91
+ case '.svg':
92
+ return 'image/svg+xml';
93
+ case '.md':
94
+ return 'text/markdown';
95
+ default:
96
+ return 'application/octet-stream';
97
+ }
98
+ }
99
+
100
+ function isBlobLike(value) {
101
+ return !!value && typeof value === 'object' && typeof value.arrayBuffer === 'function';
102
+ }
103
+
104
+ function isBinaryDescriptor(value) {
105
+ if (!value || typeof value !== 'object' || isBlobLike(value)) {
106
+ return false;
107
+ }
108
+ return 'data' in value || 'file' in value;
109
+ }
110
+
111
+ function toUint8Array(data) {
112
+ if (data instanceof Uint8Array) {
113
+ return data;
114
+ }
115
+ if (ArrayBuffer.isView(data)) {
116
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
117
+ }
118
+ return new Uint8Array(data);
119
+ }
120
+
121
+ async function normalizeBinaryPayload(data, overrides = {}) {
122
+ if (isBlobLike(data)) {
123
+ const fileName =
124
+ overrides.fileName ??
125
+ (typeof data.name === 'string' && data.name ? data.name : undefined) ??
126
+ 'upload.bin';
127
+ const contentType =
128
+ overrides.contentType ??
129
+ (typeof data.type === 'string' && data.type ? data.type : undefined) ??
130
+ guessContentType(fileName);
131
+ return {
132
+ fileName,
133
+ contentType,
134
+ blob: new Blob([await data.arrayBuffer()], { type: contentType }),
135
+ };
136
+ }
137
+
138
+ if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
139
+ const fileName = overrides.fileName ?? 'upload.bin';
140
+ const contentType = overrides.contentType ?? guessContentType(fileName);
141
+ return {
142
+ fileName,
143
+ contentType,
144
+ blob: new Blob([toUint8Array(data)], { type: contentType }),
145
+ };
146
+ }
147
+
148
+ throw new ConfigurationError(
149
+ 'Unsupported upload input. Use a file path string, Blob/File, ArrayBuffer, TypedArray, or { data, fileName }.',
150
+ );
151
+ }
152
+
153
+ export async function normalizeUploadSource(source, readPathSource) {
154
+ if (typeof source === 'string') {
155
+ if (!readPathSource) {
156
+ throw new ConfigurationError(
157
+ 'Front-end mode does not support filesystem path uploads. Pass File, Blob, ArrayBuffer, TypedArray, or { data, fileName } instead.',
158
+ );
159
+ }
160
+ return readPathSource(source);
161
+ }
162
+
163
+ if (isBinaryDescriptor(source)) {
164
+ return normalizeBinaryPayload(source.data ?? source.file, {
165
+ fileName: source.fileName,
166
+ contentType: source.contentType,
167
+ });
168
+ }
169
+
170
+ return normalizeBinaryPayload(source);
171
+ }
172
+
173
+ function headersFromInit(headersInit) {
174
+ return new Headers(headersInit ?? undefined);
175
+ }
176
+
177
+ export function buildRequestHeaders(options = {}, { authenticated = false } = {}) {
178
+ const headers = headersFromInit(options.headers);
179
+ if (authenticated && !headers.has('Authorization')) {
180
+ const resolvedToken = options.token ?? readEnv('LICOS_USER_TOKEN');
181
+ if (resolvedToken) {
182
+ headers.set('Authorization', `Bearer ${resolvedToken}`);
183
+ } else if (isNodeRuntime()) {
184
+ throw new ConfigurationError(
185
+ 'Missing authentication token. Pass options.token, provide an Authorization header, or set LICOS_USER_TOKEN.',
186
+ );
187
+ }
188
+ }
189
+ return headers;
190
+ }
191
+
192
+ export function buildRequestInit(options = {}, init = {}) {
193
+ const requestInit = { ...init };
194
+ if (options.credentials) {
195
+ requestInit.credentials = options.credentials;
196
+ }
197
+ return requestInit;
198
+ }
199
+
200
+ export async function readJsonEnvelope(response) {
201
+ let payload;
202
+ try {
203
+ payload = await response.json();
204
+ } catch (error) {
205
+ throw new ApiError('Platform API returned non-JSON response', {
206
+ status: response.status,
207
+ details: error instanceof Error ? error.message : String(error),
208
+ });
209
+ }
210
+
211
+ if (!response.ok || payload?.code !== 0) {
212
+ throw new ApiError(payload?.message ?? `Platform API failed: HTTP ${response.status}`, {
213
+ status: response.status,
214
+ code: payload?.code,
215
+ details: payload?.data ?? payload,
216
+ });
217
+ }
218
+ return payload.data;
219
+ }
220
+
221
+ export async function downloadBlob(fetchImpl, url, options = {}) {
222
+ const response = await fetchImpl(
223
+ url,
224
+ buildRequestInit(options, {
225
+ method: 'GET',
226
+ headers: buildRequestHeaders(options),
227
+ }),
228
+ );
229
+ if (!response.ok) {
230
+ throw new ApiError(`Platform download failed: HTTP ${response.status}`, {
231
+ status: response.status,
232
+ });
233
+ }
234
+ if (typeof response.blob === 'function') {
235
+ return response.blob();
236
+ }
237
+ return new Blob([await response.arrayBuffer()], {
238
+ type: response.headers.get('content-type') ?? undefined,
239
+ });
240
+ }
241
+
242
+ export async function uploadFile(fetchImpl, url, source, options = {}, readPathSource) {
243
+ const normalized = await normalizeUploadSource(source, readPathSource);
244
+ const formData = new FormData();
245
+ formData.append('file', normalized.blob, normalized.fileName);
246
+
247
+ const response = await fetchImpl(
248
+ url,
249
+ buildRequestInit(options, {
250
+ method: 'POST',
251
+ headers: buildRequestHeaders(options, { authenticated: true }),
252
+ body: formData,
253
+ }),
254
+ );
255
+ return readJsonEnvelope(response);
256
+ }