@editframe/api 0.10.0-beta.7 → 0.11.0-beta.2
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/dist/resources/image-file.d.ts +1 -1
- package/dist/resources/image-file.js +5 -11
- package/dist/resources/isobmff-track.d.ts +1 -1
- package/dist/resources/isobmff-track.js +4 -4
- package/dist/resources/unprocessed-file.d.ts +30 -5
- package/dist/resources/unprocessed-file.js +57 -40
- package/dist/uploadChunks.d.ts +10 -1
- package/dist/uploadChunks.js +55 -27
- package/package.json +2 -2
- package/src/resources/image-file.test.ts +9 -6
- package/src/resources/image-file.ts +4 -12
- package/src/resources/isobmff-track.test.ts +9 -43
- package/src/resources/isobmff-track.ts +3 -4
- package/src/resources/unprocessed-file.test.ts +45 -22
- package/src/resources/unprocessed-file.ts +62 -45
|
@@ -30,4 +30,4 @@ export interface CreateImageFileResult {
|
|
|
30
30
|
asset_id: string;
|
|
31
31
|
}
|
|
32
32
|
export declare const createImageFile: (client: Client, payload: z.infer<typeof CreateImageFilePayload>) => Promise<CreateImageFileResult>;
|
|
33
|
-
export declare const uploadImageFile: (client: Client, fileId: string, fileStream: Readable, fileSize: number) =>
|
|
33
|
+
export declare const uploadImageFile: (client: Client, fileId: string, fileStream: Readable, fileSize: number) => import('../uploadChunks.ts').IteratorWithPromise<import('../uploadChunks.ts').UploadChunkEvent>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import debug from "debug";
|
|
2
|
+
import { z } from "zod";
|
|
3
3
|
import { uploadChunks } from "../uploadChunks.js";
|
|
4
4
|
const log = debug("ef:api:image-file");
|
|
5
5
|
const MAX_IMAGE_SIZE = 1024 * 1024 * 16;
|
|
@@ -26,20 +26,14 @@ const createImageFile = async (client, payload) => {
|
|
|
26
26
|
`Failed to create file ${response.status} ${response.statusText}`
|
|
27
27
|
);
|
|
28
28
|
};
|
|
29
|
-
const uploadImageFile =
|
|
29
|
+
const uploadImageFile = (client, fileId, fileStream, fileSize) => {
|
|
30
30
|
log("Uploading image file", fileId);
|
|
31
|
-
|
|
32
|
-
throw new Error(
|
|
33
|
-
`File size ${fileSize} bytes exceeds limit ${MAX_IMAGE_SIZE} bytes`
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
const result = await uploadChunks(client, {
|
|
31
|
+
return uploadChunks(client, {
|
|
37
32
|
url: `/api/v1/image_files/${fileId}/upload`,
|
|
38
33
|
fileSize,
|
|
39
|
-
fileStream
|
|
34
|
+
fileStream,
|
|
35
|
+
maxSize: MAX_IMAGE_SIZE
|
|
40
36
|
});
|
|
41
|
-
log("Image file upload complete");
|
|
42
|
-
return result;
|
|
43
37
|
};
|
|
44
38
|
export {
|
|
45
39
|
CreateImageFilePayload,
|
|
@@ -269,4 +269,4 @@ export interface CreateISOBMFFTrackResult {
|
|
|
269
269
|
complete: boolean;
|
|
270
270
|
}
|
|
271
271
|
export declare const createISOBMFFTrack: (client: Client, payload: z.infer<typeof CreateISOBMFFTrackPayload>) => Promise<CreateISOBMFFTrackResult>;
|
|
272
|
-
export declare const uploadISOBMFFTrack: (client: Client, fileId: string, trackId: number, fileStream: Readable, trackSize: number) =>
|
|
272
|
+
export declare const uploadISOBMFFTrack: (client: Client, fileId: string, trackId: number, fileStream: Readable, trackSize: number) => import('../uploadChunks.ts').IteratorWithPromise<import('../uploadChunks.ts').UploadChunkEvent>;
|
|
@@ -39,14 +39,14 @@ const createISOBMFFTrack = async (client, payload) => {
|
|
|
39
39
|
`Failed to create isobmff track ${response.status} ${response.statusText}`
|
|
40
40
|
);
|
|
41
41
|
};
|
|
42
|
-
const uploadISOBMFFTrack =
|
|
42
|
+
const uploadISOBMFFTrack = (client, fileId, trackId, fileStream, trackSize) => {
|
|
43
43
|
log("Uploading fragment track", fileId);
|
|
44
|
-
|
|
44
|
+
return uploadChunks(client, {
|
|
45
45
|
url: `/api/v1/isobmff_tracks/${fileId}/${trackId}/upload`,
|
|
46
46
|
fileStream,
|
|
47
|
-
fileSize: trackSize
|
|
47
|
+
fileSize: trackSize,
|
|
48
|
+
maxSize: MAX_TRACK_SIZE
|
|
48
49
|
});
|
|
49
|
-
log("Fragment track upload complete");
|
|
50
50
|
};
|
|
51
51
|
export {
|
|
52
52
|
CreateISOBMFFTrackPayload,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { Client } from '../client.ts';
|
|
4
|
+
import { IteratorWithPromise, UploadChunkEvent } from '../uploadChunks.ts';
|
|
4
5
|
declare const FileProcessors: z.ZodEffects<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"isobmff">, z.ZodLiteral<"image">, z.ZodLiteral<"captions">, z.ZodString]>, "many">, string[], string[]>;
|
|
5
6
|
export declare const CreateUnprocessedFilePayload: z.ZodObject<{
|
|
6
7
|
md5: z.ZodString;
|
|
@@ -45,9 +46,33 @@ export interface UpdateUnprocessedFileResult {
|
|
|
45
46
|
}
|
|
46
47
|
export declare const createUnprocessedFile: (client: Client, payload: z.infer<typeof CreateUnprocessedFilePayload>) => Promise<CreateUnprocessedFileResult>;
|
|
47
48
|
export declare const updateUnprocessedFile: (client: Client, fileId: string, payload: Partial<z.infer<typeof UpdateUnprocessedFilePayload>>) => Promise<UpdateUnprocessedFileResult>;
|
|
48
|
-
export declare const uploadUnprocessedFile: (client: Client, fileId: string, fileStream: Readable, fileSize: number) =>
|
|
49
|
-
export declare const processAVFileBuffer: (client: Client, buffer: Buffer, filename?: string) =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
export declare const uploadUnprocessedFile: (client: Client, fileId: string, fileStream: Readable, fileSize: number) => IteratorWithPromise<UploadChunkEvent>;
|
|
50
|
+
export declare const processAVFileBuffer: (client: Client, buffer: Buffer, filename?: string) => {
|
|
51
|
+
progress(): AsyncGenerator<{
|
|
52
|
+
type: string;
|
|
53
|
+
progress: number;
|
|
54
|
+
}, void, unknown>;
|
|
55
|
+
file: () => Promise<UpdateUnprocessedFileResult>;
|
|
56
|
+
};
|
|
57
|
+
export declare const processAVFile: (client: Client, filePath: string) => {
|
|
58
|
+
progress: () => Promise<AsyncGenerator<{
|
|
59
|
+
type: string;
|
|
60
|
+
progress: number;
|
|
61
|
+
}, void, unknown>>;
|
|
62
|
+
file: () => Promise<UpdateUnprocessedFileResult>;
|
|
63
|
+
};
|
|
64
|
+
export declare const processImageFileBuffer: (client: Client, buffer: Buffer, filename?: string) => {
|
|
65
|
+
progress(): AsyncGenerator<{
|
|
66
|
+
type: string;
|
|
67
|
+
progress: number;
|
|
68
|
+
}, void, unknown>;
|
|
69
|
+
file: () => Promise<UpdateUnprocessedFileResult>;
|
|
70
|
+
};
|
|
71
|
+
export declare const processImageFile: (client: Client, filePath: string) => {
|
|
72
|
+
progress: () => Promise<AsyncGenerator<{
|
|
73
|
+
type: string;
|
|
74
|
+
progress: number;
|
|
75
|
+
}, void, unknown>>;
|
|
76
|
+
file: () => Promise<UpdateUnprocessedFileResult>;
|
|
77
|
+
};
|
|
53
78
|
export {};
|
|
@@ -72,78 +72,95 @@ const updateUnprocessedFile = async (client, fileId, payload) => {
|
|
|
72
72
|
`Failed to update unprocessed file ${response.status} ${response.statusText}`
|
|
73
73
|
);
|
|
74
74
|
};
|
|
75
|
-
const uploadUnprocessedFile =
|
|
75
|
+
const uploadUnprocessedFile = (client, fileId, fileStream, fileSize) => {
|
|
76
76
|
log("Uploading unprocessed file", fileId);
|
|
77
|
-
|
|
77
|
+
return uploadChunks(client, {
|
|
78
78
|
url: `/api/v1/unprocessed_files/${fileId}/upload`,
|
|
79
79
|
fileSize,
|
|
80
|
-
fileStream
|
|
80
|
+
fileStream,
|
|
81
|
+
maxSize: MAX_FILE_SIZE
|
|
81
82
|
});
|
|
82
|
-
log("Unprocessed file upload complete");
|
|
83
83
|
};
|
|
84
|
-
const processResource =
|
|
84
|
+
const processResource = (client, filename, md5, byteSize, processor, doUpload) => {
|
|
85
85
|
log("Processing", { filename, md5, byteSize, processor });
|
|
86
|
-
const
|
|
86
|
+
const createFilePromise = createUnprocessedFile(client, {
|
|
87
87
|
md5,
|
|
88
88
|
processes: [],
|
|
89
89
|
filename,
|
|
90
90
|
byte_size: byteSize
|
|
91
91
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
return {
|
|
93
|
+
async *progress() {
|
|
94
|
+
const unprocessedFile = await createFilePromise;
|
|
95
|
+
if (unprocessedFile.complete) {
|
|
96
|
+
yield { type: "progress", progress: 0 };
|
|
97
|
+
yield { type: "progress", progress: 1 };
|
|
98
|
+
}
|
|
99
|
+
yield* doUpload(unprocessedFile.id);
|
|
100
|
+
},
|
|
101
|
+
file: async () => {
|
|
102
|
+
const unprocessedFile = await createFilePromise;
|
|
103
|
+
if (unprocessedFile.complete) {
|
|
104
|
+
return unprocessedFile;
|
|
105
|
+
}
|
|
106
|
+
await doUpload(unprocessedFile.id).whenUploaded();
|
|
107
|
+
const fileInformation = await updateUnprocessedFile(
|
|
108
|
+
client,
|
|
109
|
+
unprocessedFile.id,
|
|
110
|
+
{
|
|
111
|
+
processes: [processor]
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
log("File processed", fileInformation);
|
|
115
|
+
return fileInformation;
|
|
104
116
|
}
|
|
105
|
-
|
|
106
|
-
log("File processed", fileInformation);
|
|
107
|
-
return fileInformation;
|
|
117
|
+
};
|
|
108
118
|
};
|
|
109
119
|
const buildBufferProcessor = (processor) => {
|
|
110
|
-
return
|
|
120
|
+
return (client, buffer, filename = "buffer") => {
|
|
111
121
|
log(`Processing file buffer: ${processor}`, filename);
|
|
112
122
|
const md5 = md5Buffer(buffer);
|
|
113
|
-
return
|
|
123
|
+
return processResource(
|
|
114
124
|
client,
|
|
115
125
|
filename,
|
|
116
126
|
md5,
|
|
117
127
|
buffer.byteLength,
|
|
118
128
|
processor,
|
|
119
|
-
|
|
129
|
+
(id) => {
|
|
120
130
|
const readStream = new Readable({
|
|
121
131
|
read() {
|
|
122
132
|
readStream.push(buffer);
|
|
123
133
|
readStream.push(null);
|
|
124
134
|
}
|
|
125
135
|
});
|
|
126
|
-
|
|
136
|
+
return uploadUnprocessedFile(client, id, readStream, buffer.byteLength);
|
|
127
137
|
}
|
|
128
138
|
);
|
|
129
139
|
};
|
|
130
140
|
};
|
|
131
141
|
const buildFileProcessor = (processor) => {
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
return (client, filePath) => {
|
|
143
|
+
const processPromise = async () => {
|
|
144
|
+
const [md5, { size: byteSize }] = await Promise.all([
|
|
145
|
+
md5FilePath(filePath),
|
|
146
|
+
stat(filePath)
|
|
147
|
+
]);
|
|
148
|
+
return processResource(
|
|
149
|
+
client,
|
|
150
|
+
basename(filePath),
|
|
151
|
+
md5,
|
|
152
|
+
byteSize,
|
|
153
|
+
processor,
|
|
154
|
+
(id) => {
|
|
155
|
+
const readStream = createReadStream(filePath);
|
|
156
|
+
return uploadUnprocessedFile(client, id, readStream, byteSize);
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
progress: async () => (await processPromise()).progress(),
|
|
162
|
+
file: async () => (await processPromise()).file()
|
|
163
|
+
};
|
|
147
164
|
};
|
|
148
165
|
};
|
|
149
166
|
const processAVFileBuffer = buildBufferProcessor("isobmff");
|
package/dist/uploadChunks.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
2
|
import { Client } from './client.ts';
|
|
3
|
+
export interface IteratorWithPromise<T> extends AsyncGenerator<T, void, unknown> {
|
|
4
|
+
whenUploaded: () => Promise<T[]>;
|
|
5
|
+
}
|
|
6
|
+
export declare const fakeCompleteUpload: () => IteratorWithPromise<UploadChunkEvent>;
|
|
3
7
|
interface UploadChunksOptions {
|
|
4
8
|
url: string;
|
|
5
9
|
fileStream: Readable;
|
|
6
10
|
fileSize: number;
|
|
11
|
+
maxSize: number;
|
|
7
12
|
chunkSizeBytes?: number;
|
|
8
13
|
}
|
|
9
|
-
export
|
|
14
|
+
export interface UploadChunkEvent {
|
|
15
|
+
type: "progress";
|
|
16
|
+
progress: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function uploadChunks(client: Client, { url, fileSize, fileStream, maxSize, chunkSizeBytes, }: UploadChunksOptions): IteratorWithPromise<UploadChunkEvent>;
|
|
10
19
|
export {};
|
package/dist/uploadChunks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
|
-
import { streamChunker } from "./streamChunker.js";
|
|
3
2
|
import { CHUNK_SIZE_BYTES } from "./CHUNK_SIZE_BYTES.js";
|
|
3
|
+
import { streamChunker } from "./streamChunker.js";
|
|
4
4
|
const log = debug("ef:api:uploadChunk");
|
|
5
5
|
const uploadChunk = async (client, {
|
|
6
6
|
url,
|
|
@@ -34,37 +34,65 @@ const uploadChunk = async (client, {
|
|
|
34
34
|
`Failed to upload chunk ${chunkNumber} for ${url} ${response.status} ${response.statusText}`
|
|
35
35
|
);
|
|
36
36
|
};
|
|
37
|
-
|
|
37
|
+
function uploadChunks(client, {
|
|
38
38
|
url,
|
|
39
39
|
fileSize,
|
|
40
40
|
fileStream,
|
|
41
|
+
maxSize,
|
|
41
42
|
chunkSizeBytes = CHUNK_SIZE_BYTES
|
|
42
43
|
}) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
44
|
+
const makeGenerator = async function* () {
|
|
45
|
+
if (fileSize > maxSize) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`File size ${fileSize} bytes exceeds limit ${maxSize} bytes`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
log("Checking upload status", url);
|
|
51
|
+
const uploadStatus = await client.authenticatedFetch(url);
|
|
52
|
+
yield { type: "progress", progress: 0 };
|
|
53
|
+
if (uploadStatus.status === 200) {
|
|
54
|
+
log("Fragment track already uploaded");
|
|
55
|
+
yield { type: "progress", progress: 1 };
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let chunkNumber = 0;
|
|
59
|
+
let complete = false;
|
|
60
|
+
for await (const chunkBuffer of streamChunker(fileStream, chunkSizeBytes)) {
|
|
61
|
+
log(`Uploading chunk ${chunkNumber}`);
|
|
62
|
+
({ complete } = await uploadChunk(client, {
|
|
63
|
+
url,
|
|
64
|
+
chunkBuffer,
|
|
65
|
+
chunkNumber,
|
|
66
|
+
fileSize,
|
|
67
|
+
chunkSizeBytes
|
|
68
|
+
}));
|
|
69
|
+
chunkNumber++;
|
|
70
|
+
yield {
|
|
71
|
+
type: "progress",
|
|
72
|
+
progress: Math.min(1, chunkNumber / (fileSize / chunkSizeBytes))
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (!fileStream.readableEnded) {
|
|
76
|
+
throw new Error("Did not read entire file stream");
|
|
77
|
+
}
|
|
78
|
+
if (!complete) {
|
|
79
|
+
throw new Error("Did not complete upload");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const generator = makeGenerator();
|
|
83
|
+
generator.whenUploaded = async () => {
|
|
84
|
+
if (fileSize > maxSize) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`File size ${fileSize} bytes exceeds limit ${maxSize} bytes`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const events = [];
|
|
90
|
+
for await (const event of generator) {
|
|
91
|
+
events.push(event);
|
|
92
|
+
}
|
|
93
|
+
return events;
|
|
94
|
+
};
|
|
95
|
+
return generator;
|
|
68
96
|
}
|
|
69
97
|
export {
|
|
70
98
|
uploadChunks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0-beta.2",
|
|
4
4
|
"description": "API functions for EditFrame",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"vite-tsconfig-paths": "^4.3.2"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@editframe/assets": "0.
|
|
31
|
+
"@editframe/assets": "0.11.0-beta.2",
|
|
32
32
|
"debug": "^4.3.5",
|
|
33
33
|
"jsonwebtoken": "^9.0.2",
|
|
34
34
|
"node-fetch": "^3.3.2",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { test, expect, beforeAll, afterEach, afterAll, describe } from "vitest";
|
|
2
1
|
import { http, HttpResponse } from "msw";
|
|
3
2
|
import { setupServer } from "msw/node";
|
|
3
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
4
4
|
import { ZodError } from "zod";
|
|
5
5
|
|
|
6
6
|
import { Client } from "../client.ts";
|
|
7
|
-
import { createImageFile, uploadImageFile } from "./image-file.ts";
|
|
8
7
|
import { readableFromBuffers } from "../readableFromBuffers.ts";
|
|
8
|
+
import { createImageFile, uploadImageFile } from "./image-file.ts";
|
|
9
9
|
|
|
10
10
|
const server = setupServer();
|
|
11
11
|
const client = new Client("ef_TEST_TOKEN", "http://localhost");
|
|
@@ -96,7 +96,7 @@ describe("ImageFile", () => {
|
|
|
96
96
|
"test-file-id",
|
|
97
97
|
readableFromBuffers(Buffer.from("test")),
|
|
98
98
|
1024 * 1024 * 17,
|
|
99
|
-
),
|
|
99
|
+
).whenUploaded(),
|
|
100
100
|
).rejects.toThrowError(
|
|
101
101
|
"File size 17825792 bytes exceeds limit 16777216 bytes",
|
|
102
102
|
);
|
|
@@ -117,7 +117,7 @@ describe("ImageFile", () => {
|
|
|
117
117
|
"test-file-id",
|
|
118
118
|
readableFromBuffers(Buffer.from("test")),
|
|
119
119
|
4,
|
|
120
|
-
),
|
|
120
|
+
).whenUploaded(),
|
|
121
121
|
).rejects.toThrowError(
|
|
122
122
|
"Failed to upload chunk 0 for /api/v1/image_files/test-file-id/upload 500 Internal Server Error",
|
|
123
123
|
);
|
|
@@ -138,8 +138,11 @@ describe("ImageFile", () => {
|
|
|
138
138
|
"test-file-id",
|
|
139
139
|
readableFromBuffers(Buffer.from("test")),
|
|
140
140
|
4,
|
|
141
|
-
),
|
|
142
|
-
).resolves.
|
|
141
|
+
).whenUploaded(),
|
|
142
|
+
).resolves.toEqual([
|
|
143
|
+
{ type: "progress", progress: 0 },
|
|
144
|
+
{ type: "progress", progress: 1 },
|
|
145
|
+
]);
|
|
143
146
|
});
|
|
144
147
|
});
|
|
145
148
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Readable } from "node:stream";
|
|
2
2
|
|
|
3
|
-
import { z } from "zod";
|
|
4
3
|
import debug from "debug";
|
|
4
|
+
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import type { Client } from "../client.ts";
|
|
7
7
|
import { uploadChunks } from "../uploadChunks.ts";
|
|
@@ -48,26 +48,18 @@ export const createImageFile = async (
|
|
|
48
48
|
);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
export const uploadImageFile =
|
|
51
|
+
export const uploadImageFile = (
|
|
52
52
|
client: Client,
|
|
53
53
|
fileId: string,
|
|
54
54
|
fileStream: Readable,
|
|
55
55
|
fileSize: number,
|
|
56
56
|
) => {
|
|
57
57
|
log("Uploading image file", fileId);
|
|
58
|
-
if (fileSize > MAX_IMAGE_SIZE) {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`File size ${fileSize} bytes exceeds limit ${MAX_IMAGE_SIZE} bytes`,
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
58
|
|
|
64
|
-
|
|
59
|
+
return uploadChunks(client, {
|
|
65
60
|
url: `/api/v1/image_files/${fileId}/upload`,
|
|
66
61
|
fileSize,
|
|
67
62
|
fileStream,
|
|
63
|
+
maxSize: MAX_IMAGE_SIZE,
|
|
68
64
|
});
|
|
69
|
-
|
|
70
|
-
log("Image file upload complete");
|
|
71
|
-
|
|
72
|
-
return result;
|
|
73
65
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { test, expect, beforeAll, afterEach, afterAll, describe } from "vitest";
|
|
2
1
|
import { http, HttpResponse } from "msw";
|
|
3
2
|
import { setupServer } from "msw/node";
|
|
3
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
4
4
|
import { ZodError } from "zod";
|
|
5
5
|
|
|
6
|
+
import { createTestTrack } from "TEST/createTestTrack.ts";
|
|
6
7
|
import { Client } from "../client.ts";
|
|
7
|
-
import { createISOBMFFTrack, uploadISOBMFFTrack } from "./isobmff-track.ts";
|
|
8
8
|
import { readableFromBuffers } from "../readableFromBuffers.ts";
|
|
9
|
+
import { createISOBMFFTrack, uploadISOBMFFTrack } from "./isobmff-track.ts";
|
|
9
10
|
|
|
10
11
|
const server = setupServer();
|
|
11
12
|
const client = new Client("ef_TEST_TOKEN", "http://localhost");
|
|
@@ -93,7 +94,7 @@ describe("ISOBMFF Track", () => {
|
|
|
93
94
|
1,
|
|
94
95
|
readableFromBuffers(Buffer.from("test")),
|
|
95
96
|
4,
|
|
96
|
-
),
|
|
97
|
+
).whenUploaded(),
|
|
97
98
|
).rejects.toThrowError(
|
|
98
99
|
"Failed to upload chunk 0 for /api/v1/isobmff_tracks/test-file/1/upload 500 Internal Server Error",
|
|
99
100
|
);
|
|
@@ -115,46 +116,11 @@ describe("ISOBMFF Track", () => {
|
|
|
115
116
|
1,
|
|
116
117
|
readableFromBuffers(Buffer.from("test")),
|
|
117
118
|
4,
|
|
118
|
-
),
|
|
119
|
-
).resolves.
|
|
119
|
+
).whenUploaded(),
|
|
120
|
+
).resolves.toEqual([
|
|
121
|
+
{ type: "progress", progress: 0 },
|
|
122
|
+
{ type: "progress", progress: 1 },
|
|
123
|
+
]);
|
|
120
124
|
});
|
|
121
125
|
});
|
|
122
126
|
});
|
|
123
|
-
|
|
124
|
-
function createTestTrack(
|
|
125
|
-
options: Partial<Parameters<typeof createISOBMFFTrack>[1]> = {},
|
|
126
|
-
) {
|
|
127
|
-
return Object.assign(
|
|
128
|
-
{
|
|
129
|
-
file_id: "test-id",
|
|
130
|
-
track_id: 1,
|
|
131
|
-
type: "audio",
|
|
132
|
-
probe_info: {
|
|
133
|
-
channels: 2,
|
|
134
|
-
sample_rate: "44100",
|
|
135
|
-
duration: 1000,
|
|
136
|
-
duration_ts: 1000,
|
|
137
|
-
start_time: 0,
|
|
138
|
-
start_pts: 0,
|
|
139
|
-
r_frame_rate: "100",
|
|
140
|
-
channel_layout: "stereo",
|
|
141
|
-
codec_tag_string: "mp3",
|
|
142
|
-
codec_long_name: "MP3",
|
|
143
|
-
codec_type: "audio",
|
|
144
|
-
codec_tag: "0x0000",
|
|
145
|
-
codec_name: "aac",
|
|
146
|
-
bits_per_sample: 16,
|
|
147
|
-
index: 0,
|
|
148
|
-
sample_fmt: "s16",
|
|
149
|
-
time_base: "100",
|
|
150
|
-
avg_frame_rate: "100",
|
|
151
|
-
disposition: {},
|
|
152
|
-
bit_rate: "100",
|
|
153
|
-
},
|
|
154
|
-
duration_ms: 1000,
|
|
155
|
-
codec_name: "mp3",
|
|
156
|
-
byte_size: 1024 * 1024 * 5,
|
|
157
|
-
} as const,
|
|
158
|
-
options,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
@@ -63,7 +63,7 @@ export const createISOBMFFTrack = async (
|
|
|
63
63
|
);
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
export const uploadISOBMFFTrack =
|
|
66
|
+
export const uploadISOBMFFTrack = (
|
|
67
67
|
client: Client,
|
|
68
68
|
fileId: string,
|
|
69
69
|
trackId: number,
|
|
@@ -72,11 +72,10 @@ export const uploadISOBMFFTrack = async (
|
|
|
72
72
|
) => {
|
|
73
73
|
log("Uploading fragment track", fileId);
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
return uploadChunks(client, {
|
|
76
76
|
url: `/api/v1/isobmff_tracks/${fileId}/${trackId}/upload`,
|
|
77
77
|
fileStream,
|
|
78
78
|
fileSize: trackSize,
|
|
79
|
+
maxSize: MAX_TRACK_SIZE,
|
|
79
80
|
});
|
|
80
|
-
|
|
81
|
-
log("Fragment track upload complete");
|
|
82
81
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
|
|
3
|
-
import { test, expect, beforeAll, afterEach, afterAll, describe } from "vitest";
|
|
4
3
|
import { http, HttpResponse } from "msw";
|
|
5
4
|
import { setupServer } from "msw/node";
|
|
5
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
6
6
|
import { ZodError } from "zod";
|
|
7
7
|
|
|
8
8
|
import { Client } from "../client.ts";
|
|
9
|
+
import { readableFromBuffers } from "../readableFromBuffers.ts";
|
|
9
10
|
import {
|
|
10
11
|
createUnprocessedFile,
|
|
11
12
|
processAVFile,
|
|
@@ -15,7 +16,6 @@ import {
|
|
|
15
16
|
updateUnprocessedFile,
|
|
16
17
|
uploadUnprocessedFile,
|
|
17
18
|
} from "./unprocessed-file.ts";
|
|
18
|
-
import { readableFromBuffers } from "../readableFromBuffers.ts";
|
|
19
19
|
|
|
20
20
|
const server = setupServer();
|
|
21
21
|
const client = new Client("ef_TEST_TOKEN", "http://localhost");
|
|
@@ -147,7 +147,7 @@ describe("Unprocessed File", () => {
|
|
|
147
147
|
"test-file",
|
|
148
148
|
readableFromBuffers(Buffer.from("test")),
|
|
149
149
|
4,
|
|
150
|
-
),
|
|
150
|
+
).whenUploaded(),
|
|
151
151
|
).rejects.toThrowError(
|
|
152
152
|
"Failed to upload chunk 0 for /api/v1/unprocessed_files/test-file/upload 500 Internal Server Error",
|
|
153
153
|
);
|
|
@@ -168,8 +168,11 @@ describe("Unprocessed File", () => {
|
|
|
168
168
|
"test-file",
|
|
169
169
|
readableFromBuffers(Buffer.from("test")),
|
|
170
170
|
4,
|
|
171
|
-
),
|
|
172
|
-
).resolves.
|
|
171
|
+
).whenUploaded(),
|
|
172
|
+
).resolves.toEqual([
|
|
173
|
+
{ type: "progress", progress: 0 },
|
|
174
|
+
{ type: "progress", progress: 1 },
|
|
175
|
+
]);
|
|
173
176
|
});
|
|
174
177
|
});
|
|
175
178
|
|
|
@@ -183,7 +186,7 @@ describe("Unprocessed File", () => {
|
|
|
183
186
|
);
|
|
184
187
|
|
|
185
188
|
await expect(
|
|
186
|
-
processAVFileBuffer(client, Buffer.from("test"), "test-file"),
|
|
189
|
+
processAVFileBuffer(client, Buffer.from("test"), "test-file").file(),
|
|
187
190
|
).rejects.toThrowError(
|
|
188
191
|
"Failed to create unprocessed file 500 Internal Server Error",
|
|
189
192
|
);
|
|
@@ -209,7 +212,7 @@ describe("Unprocessed File", () => {
|
|
|
209
212
|
);
|
|
210
213
|
|
|
211
214
|
await expect(
|
|
212
|
-
processAVFileBuffer(client, Buffer.from("test"), "test-file"),
|
|
215
|
+
processAVFileBuffer(client, Buffer.from("test"), "test-file").file(),
|
|
213
216
|
).rejects.toThrowError(
|
|
214
217
|
"Failed to upload chunk 0 for /api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload 500 Internal Server Error",
|
|
215
218
|
);
|
|
@@ -239,7 +242,7 @@ describe("Unprocessed File", () => {
|
|
|
239
242
|
);
|
|
240
243
|
|
|
241
244
|
await expect(
|
|
242
|
-
processAVFileBuffer(client, Buffer.from("test"), "test-file"),
|
|
245
|
+
processAVFileBuffer(client, Buffer.from("test"), "test-file").file(),
|
|
243
246
|
).rejects.toThrowError(
|
|
244
247
|
"Failed to update unprocessed file 500 Internal Server Error",
|
|
245
248
|
);
|
|
@@ -269,7 +272,7 @@ describe("Unprocessed File", () => {
|
|
|
269
272
|
);
|
|
270
273
|
|
|
271
274
|
await expect(
|
|
272
|
-
processAVFileBuffer(client, Buffer.from("test"), "test-file"),
|
|
275
|
+
processAVFileBuffer(client, Buffer.from("test"), "test-file").file(),
|
|
273
276
|
).resolves.toEqual({ test: "response" });
|
|
274
277
|
});
|
|
275
278
|
});
|
|
@@ -282,7 +285,9 @@ describe("Unprocessed File", () => {
|
|
|
282
285
|
),
|
|
283
286
|
);
|
|
284
287
|
|
|
285
|
-
await expect(
|
|
288
|
+
await expect(
|
|
289
|
+
processAVFile(client, TEST_AV_FILE).file(),
|
|
290
|
+
).rejects.toThrowError(
|
|
286
291
|
"Failed to create unprocessed file 500 Internal Server Error",
|
|
287
292
|
);
|
|
288
293
|
});
|
|
@@ -306,7 +311,9 @@ describe("Unprocessed File", () => {
|
|
|
306
311
|
),
|
|
307
312
|
);
|
|
308
313
|
|
|
309
|
-
await expect(
|
|
314
|
+
await expect(
|
|
315
|
+
processAVFile(client, TEST_AV_FILE).file(),
|
|
316
|
+
).rejects.toThrowError(
|
|
310
317
|
"Failed to upload chunk 0 for /api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload 500 Internal Server Error",
|
|
311
318
|
);
|
|
312
319
|
});
|
|
@@ -334,7 +341,9 @@ describe("Unprocessed File", () => {
|
|
|
334
341
|
),
|
|
335
342
|
);
|
|
336
343
|
|
|
337
|
-
await expect(
|
|
344
|
+
await expect(
|
|
345
|
+
processAVFile(client, TEST_AV_FILE).file(),
|
|
346
|
+
).rejects.toThrowError(
|
|
338
347
|
"Failed to update unprocessed file 500 Internal Server Error",
|
|
339
348
|
);
|
|
340
349
|
});
|
|
@@ -362,9 +371,11 @@ describe("Unprocessed File", () => {
|
|
|
362
371
|
),
|
|
363
372
|
);
|
|
364
373
|
|
|
365
|
-
await expect(processAVFile(client, TEST_AV_FILE)).resolves.toEqual(
|
|
366
|
-
|
|
367
|
-
|
|
374
|
+
await expect(processAVFile(client, TEST_AV_FILE).file()).resolves.toEqual(
|
|
375
|
+
{
|
|
376
|
+
test: "response",
|
|
377
|
+
},
|
|
378
|
+
);
|
|
368
379
|
});
|
|
369
380
|
});
|
|
370
381
|
|
|
@@ -377,7 +388,7 @@ describe("Unprocessed File", () => {
|
|
|
377
388
|
);
|
|
378
389
|
|
|
379
390
|
await expect(
|
|
380
|
-
processImageFileBuffer(client, Buffer.from("test"), "test-file"),
|
|
391
|
+
processImageFileBuffer(client, Buffer.from("test"), "test-file").file(),
|
|
381
392
|
).rejects.toThrowError(
|
|
382
393
|
"Failed to create unprocessed file 500 Internal Server Error",
|
|
383
394
|
);
|
|
@@ -403,7 +414,7 @@ describe("Unprocessed File", () => {
|
|
|
403
414
|
);
|
|
404
415
|
|
|
405
416
|
await expect(
|
|
406
|
-
processImageFileBuffer(client, Buffer.from("test"), "test-file"),
|
|
417
|
+
processImageFileBuffer(client, Buffer.from("test"), "test-file").file(),
|
|
407
418
|
).rejects.toThrowError(
|
|
408
419
|
"Failed to upload chunk 0 for /api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload 500 Internal Server Error",
|
|
409
420
|
);
|
|
@@ -433,7 +444,7 @@ describe("Unprocessed File", () => {
|
|
|
433
444
|
);
|
|
434
445
|
|
|
435
446
|
await expect(
|
|
436
|
-
processImageFileBuffer(client, Buffer.from("test"), "test-file"),
|
|
447
|
+
processImageFileBuffer(client, Buffer.from("test"), "test-file").file(),
|
|
437
448
|
).rejects.toThrowError(
|
|
438
449
|
"Failed to update unprocessed file 500 Internal Server Error",
|
|
439
450
|
);
|
|
@@ -463,7 +474,7 @@ describe("Unprocessed File", () => {
|
|
|
463
474
|
);
|
|
464
475
|
|
|
465
476
|
await expect(
|
|
466
|
-
processImageFileBuffer(client, Buffer.from("test"), "test-file"),
|
|
477
|
+
processImageFileBuffer(client, Buffer.from("test"), "test-file").file(),
|
|
467
478
|
).resolves.toEqual({ test: "response" });
|
|
468
479
|
});
|
|
469
480
|
});
|
|
@@ -476,7 +487,9 @@ describe("Unprocessed File", () => {
|
|
|
476
487
|
),
|
|
477
488
|
);
|
|
478
489
|
|
|
479
|
-
await expect(
|
|
490
|
+
await expect(
|
|
491
|
+
processImageFile(client, TEST_AV_FILE).file(),
|
|
492
|
+
).rejects.toThrowError(
|
|
480
493
|
"Failed to create unprocessed file 500 Internal Server Error",
|
|
481
494
|
);
|
|
482
495
|
});
|
|
@@ -500,7 +513,9 @@ describe("Unprocessed File", () => {
|
|
|
500
513
|
),
|
|
501
514
|
);
|
|
502
515
|
|
|
503
|
-
await expect(
|
|
516
|
+
await expect(
|
|
517
|
+
processImageFile(client, TEST_AV_FILE).file(),
|
|
518
|
+
).rejects.toThrowError(
|
|
504
519
|
"Failed to upload chunk 0 for /api/v1/unprocessed_files/098f6bcd-4621-d373-cade-4e832627b4f6/upload 500 Internal Server Error",
|
|
505
520
|
);
|
|
506
521
|
});
|
|
@@ -528,7 +543,9 @@ describe("Unprocessed File", () => {
|
|
|
528
543
|
),
|
|
529
544
|
);
|
|
530
545
|
|
|
531
|
-
await expect(
|
|
546
|
+
await expect(
|
|
547
|
+
processImageFile(client, TEST_AV_FILE).file(),
|
|
548
|
+
).rejects.toThrowError(
|
|
532
549
|
"Failed to update unprocessed file 500 Internal Server Error",
|
|
533
550
|
);
|
|
534
551
|
});
|
|
@@ -555,6 +572,12 @@ describe("Unprocessed File", () => {
|
|
|
555
572
|
() => HttpResponse.json({ test: "response" }, { status: 200 }),
|
|
556
573
|
),
|
|
557
574
|
);
|
|
575
|
+
|
|
576
|
+
await expect(
|
|
577
|
+
processImageFile(client, TEST_AV_FILE).file(),
|
|
578
|
+
).resolves.toEqual({
|
|
579
|
+
test: "response",
|
|
580
|
+
});
|
|
558
581
|
});
|
|
559
582
|
});
|
|
560
583
|
});
|
|
@@ -9,7 +9,11 @@ import { z } from "zod";
|
|
|
9
9
|
import { md5Buffer, md5FilePath } from "@editframe/assets";
|
|
10
10
|
|
|
11
11
|
import type { Client } from "../client.ts";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
type IteratorWithPromise,
|
|
14
|
+
type UploadChunkEvent,
|
|
15
|
+
uploadChunks,
|
|
16
|
+
} from "../uploadChunks.ts";
|
|
13
17
|
|
|
14
18
|
const log = debug("ef:api:unprocessed-file");
|
|
15
19
|
|
|
@@ -118,7 +122,7 @@ export const updateUnprocessedFile = async (
|
|
|
118
122
|
);
|
|
119
123
|
};
|
|
120
124
|
|
|
121
|
-
export const uploadUnprocessedFile =
|
|
125
|
+
export const uploadUnprocessedFile = (
|
|
122
126
|
client: Client,
|
|
123
127
|
fileId: string,
|
|
124
128
|
fileStream: Readable,
|
|
@@ -126,64 +130,71 @@ export const uploadUnprocessedFile = async (
|
|
|
126
130
|
) => {
|
|
127
131
|
log("Uploading unprocessed file", fileId);
|
|
128
132
|
|
|
129
|
-
|
|
133
|
+
return uploadChunks(client, {
|
|
130
134
|
url: `/api/v1/unprocessed_files/${fileId}/upload`,
|
|
131
135
|
fileSize,
|
|
132
136
|
fileStream,
|
|
137
|
+
maxSize: MAX_FILE_SIZE,
|
|
133
138
|
});
|
|
134
|
-
|
|
135
|
-
log("Unprocessed file upload complete");
|
|
136
139
|
};
|
|
137
140
|
|
|
138
|
-
const processResource =
|
|
141
|
+
const processResource = (
|
|
139
142
|
client: Client,
|
|
140
143
|
filename: string,
|
|
141
144
|
md5: string,
|
|
142
145
|
byteSize: number,
|
|
143
146
|
processor: z.infer<typeof FileProcessor>,
|
|
144
|
-
doUpload: (id: string) =>
|
|
147
|
+
doUpload: (id: string) => IteratorWithPromise<UploadChunkEvent>,
|
|
145
148
|
) => {
|
|
146
149
|
log("Processing", { filename, md5, byteSize, processor });
|
|
147
150
|
|
|
148
|
-
const
|
|
151
|
+
const createFilePromise = createUnprocessedFile(client, {
|
|
149
152
|
md5: md5,
|
|
150
153
|
processes: [],
|
|
151
154
|
filename,
|
|
152
155
|
byte_size: byteSize,
|
|
153
156
|
});
|
|
154
157
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const fileInformation = await updateUnprocessedFile(
|
|
165
|
-
client,
|
|
166
|
-
unprocessedFile.id,
|
|
167
|
-
{
|
|
168
|
-
processes: [processor],
|
|
158
|
+
return {
|
|
159
|
+
async *progress() {
|
|
160
|
+
const unprocessedFile = await createFilePromise;
|
|
161
|
+
if (unprocessedFile.complete) {
|
|
162
|
+
yield { type: "progress", progress: 0 };
|
|
163
|
+
yield { type: "progress", progress: 1 };
|
|
164
|
+
}
|
|
165
|
+
yield* doUpload(unprocessedFile.id);
|
|
169
166
|
},
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
167
|
+
file: async () => {
|
|
168
|
+
const unprocessedFile = await createFilePromise;
|
|
169
|
+
if (unprocessedFile.complete) {
|
|
170
|
+
return unprocessedFile;
|
|
171
|
+
}
|
|
172
|
+
await doUpload(unprocessedFile.id).whenUploaded();
|
|
173
|
+
const fileInformation = await updateUnprocessedFile(
|
|
174
|
+
client,
|
|
175
|
+
unprocessedFile.id,
|
|
176
|
+
{
|
|
177
|
+
processes: [processor],
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
log("File processed", fileInformation);
|
|
181
|
+
return fileInformation;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
173
184
|
};
|
|
174
185
|
|
|
175
186
|
const buildBufferProcessor = (processor: z.infer<typeof FileProcessor>) => {
|
|
176
|
-
return
|
|
187
|
+
return (client: Client, buffer: Buffer, filename = "buffer") => {
|
|
177
188
|
log(`Processing file buffer: ${processor}`, filename);
|
|
178
189
|
const md5 = md5Buffer(buffer);
|
|
179
190
|
|
|
180
|
-
return
|
|
191
|
+
return processResource(
|
|
181
192
|
client,
|
|
182
193
|
filename,
|
|
183
194
|
md5,
|
|
184
195
|
buffer.byteLength,
|
|
185
196
|
processor,
|
|
186
|
-
|
|
197
|
+
(id: string) => {
|
|
187
198
|
const readStream = new Readable({
|
|
188
199
|
read() {
|
|
189
200
|
readStream.push(buffer);
|
|
@@ -191,29 +202,35 @@ const buildBufferProcessor = (processor: z.infer<typeof FileProcessor>) => {
|
|
|
191
202
|
},
|
|
192
203
|
});
|
|
193
204
|
|
|
194
|
-
|
|
205
|
+
return uploadUnprocessedFile(client, id, readStream, buffer.byteLength);
|
|
195
206
|
},
|
|
196
207
|
);
|
|
197
208
|
};
|
|
198
209
|
};
|
|
199
210
|
|
|
200
211
|
const buildFileProcessor = (processor: z.infer<typeof FileProcessor>) => {
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
return (client: Client, filePath: string) => {
|
|
213
|
+
const processPromise = async () => {
|
|
214
|
+
const [md5, { size: byteSize }] = await Promise.all([
|
|
215
|
+
md5FilePath(filePath),
|
|
216
|
+
stat(filePath),
|
|
217
|
+
]);
|
|
218
|
+
return processResource(
|
|
219
|
+
client,
|
|
220
|
+
basename(filePath),
|
|
221
|
+
md5,
|
|
222
|
+
byteSize,
|
|
223
|
+
processor,
|
|
224
|
+
(id: string) => {
|
|
225
|
+
const readStream = createReadStream(filePath);
|
|
226
|
+
return uploadUnprocessedFile(client, id, readStream, byteSize);
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
progress: async () => (await processPromise()).progress(),
|
|
232
|
+
file: async () => (await processPromise()).file(),
|
|
233
|
+
};
|
|
217
234
|
};
|
|
218
235
|
};
|
|
219
236
|
|