@editframe/api 0.9.0-beta.3 → 0.10.0-beta.3

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.
@@ -2,21 +2,22 @@ import { Readable } from 'node:stream';
2
2
  import { z } from 'zod';
3
3
  import { Client } from '../client.ts';
4
4
  export declare const CreateCaptionFilePayload: z.ZodObject<{
5
- id: z.ZodString;
5
+ md5: z.ZodString;
6
6
  filename: z.ZodString;
7
7
  byte_size: z.ZodNumber;
8
8
  }, "strip", z.ZodTypeAny, {
9
- id: string;
9
+ md5: string;
10
10
  filename: string;
11
11
  byte_size: number;
12
12
  }, {
13
- id: string;
13
+ md5: string;
14
14
  filename: string;
15
15
  byte_size: number;
16
16
  }>;
17
17
  export interface CreateCaptionFileResult {
18
18
  complete: boolean | null;
19
19
  id: string;
20
+ md5: string;
20
21
  asset_id: string;
21
22
  }
22
23
  /**
@@ -3,7 +3,7 @@ import debug from "debug";
3
3
  const log = debug("ef:api:caption-file");
4
4
  const MAX_CAPTION_SIZE = 1024 * 1024 * 2;
5
5
  const CreateCaptionFilePayload = z.object({
6
- id: z.string(),
6
+ md5: z.string(),
7
7
  filename: z.string(),
8
8
  byte_size: z.number().int().max(MAX_CAPTION_SIZE)
9
9
  });
@@ -18,13 +18,10 @@ const restrictSize = (size) => {
18
18
  const createCaptionFile = async (client, payload) => {
19
19
  log("Creating caption file", payload);
20
20
  restrictSize(payload.byte_size);
21
- const response = await client.authenticatedFetch(
22
- "/api/video2/caption_files",
23
- {
24
- method: "POST",
25
- body: JSON.stringify(payload)
26
- }
27
- );
21
+ const response = await client.authenticatedFetch("/api/v1/caption_files", {
22
+ method: "POST",
23
+ body: JSON.stringify(payload)
24
+ });
28
25
  log("Caption file created", response);
29
26
  if (response.ok) {
30
27
  return await response.json();
@@ -37,7 +34,7 @@ const uploadCaptionFile = async (client, fileId, fileStream, fileSize) => {
37
34
  log("Uploading caption file", fileId);
38
35
  restrictSize(fileSize);
39
36
  const response = await client.authenticatedFetch(
40
- `/api/video2/caption_files/${fileId}/upload`,
37
+ `/api/v1/caption_files/${fileId}/upload`,
41
38
  {
42
39
  method: "POST",
43
40
  body: fileStream
@@ -2,21 +2,21 @@ import { Readable } from 'node:stream';
2
2
  import { z } from 'zod';
3
3
  import { Client } from '../client.ts';
4
4
  export declare const CreateImageFilePayload: z.ZodObject<{
5
- id: z.ZodString;
5
+ md5: z.ZodString;
6
6
  height: z.ZodNumber;
7
7
  width: z.ZodNumber;
8
8
  mime_type: z.ZodEnum<["image/jpeg", "image/png", "image/jpg", "image/webp"]>;
9
9
  filename: z.ZodString;
10
10
  byte_size: z.ZodNumber;
11
11
  }, "strip", z.ZodTypeAny, {
12
- id: string;
12
+ md5: string;
13
13
  filename: string;
14
14
  byte_size: number;
15
15
  height: number;
16
16
  width: number;
17
17
  mime_type: "image/jpeg" | "image/png" | "image/jpg" | "image/webp";
18
18
  }, {
19
- id: string;
19
+ md5: string;
20
20
  filename: string;
21
21
  byte_size: number;
22
22
  height: number;
@@ -26,6 +26,7 @@ export declare const CreateImageFilePayload: z.ZodObject<{
26
26
  export interface CreateImageFileResult {
27
27
  complete: boolean | null;
28
28
  id: string;
29
+ md5: string;
29
30
  asset_id: string;
30
31
  }
31
32
  export declare const createImageFile: (client: Client, payload: z.infer<typeof CreateImageFilePayload>) => Promise<CreateImageFileResult>;
@@ -4,7 +4,7 @@ import { uploadChunks } from "../uploadChunks.js";
4
4
  const log = debug("ef:api:image-file");
5
5
  const MAX_IMAGE_SIZE = 1024 * 1024 * 16;
6
6
  const CreateImageFilePayload = z.object({
7
- id: z.string(),
7
+ md5: z.string(),
8
8
  height: z.number().int(),
9
9
  width: z.number().int(),
10
10
  mime_type: z.enum(["image/jpeg", "image/png", "image/jpg", "image/webp"]),
@@ -14,7 +14,7 @@ const CreateImageFilePayload = z.object({
14
14
  const createImageFile = async (client, payload) => {
15
15
  log("Creating image file", payload);
16
16
  CreateImageFilePayload.parse(payload);
17
- const response = await client.authenticatedFetch("/api/video2/image_files", {
17
+ const response = await client.authenticatedFetch("/api/v1/image_files", {
18
18
  method: "POST",
19
19
  body: JSON.stringify(payload)
20
20
  });
@@ -34,7 +34,7 @@ const uploadImageFile = async (client, fileId, fileStream, fileSize) => {
34
34
  );
35
35
  }
36
36
  const result = await uploadChunks(client, {
37
- url: `/api/video2/image_files/${fileId}/upload`,
37
+ url: `/api/v1/image_files/${fileId}/upload`,
38
38
  fileSize,
39
39
  fileStream
40
40
  });
@@ -2,18 +2,20 @@ import { Readable } from 'node:stream';
2
2
  import { z } from 'zod';
3
3
  import { Client } from '../client.ts';
4
4
  export declare const CreateISOBMFFFilePayload: z.ZodObject<{
5
- id: z.ZodString;
5
+ md5: z.ZodString;
6
6
  filename: z.ZodString;
7
7
  }, "strip", z.ZodTypeAny, {
8
- id: string;
8
+ md5: string;
9
9
  filename: string;
10
10
  }, {
11
- id: string;
11
+ md5: string;
12
12
  filename: string;
13
13
  }>;
14
14
  export interface CreateISOBMFFFileResult {
15
15
  fragment_index_complete: boolean;
16
+ filename: string;
16
17
  id: string;
18
+ md5: string;
17
19
  asset_id: string;
18
20
  }
19
21
  export declare const createISOBMFFFile: (client: Client, payload: z.infer<typeof CreateISOBMFFFilePayload>) => Promise<CreateISOBMFFFileResult>;
@@ -3,18 +3,15 @@ import debug from "debug";
3
3
  const log = debug("ef:api:isobmff-file");
4
4
  const FILE_SIZE_LIMIT = 1024 * 1024 * 2;
5
5
  const CreateISOBMFFFilePayload = z.object({
6
- id: z.string(),
6
+ md5: z.string(),
7
7
  filename: z.string()
8
8
  });
9
9
  const createISOBMFFFile = async (client, payload) => {
10
10
  log("Creating isobmff file", payload);
11
- const response = await client.authenticatedFetch(
12
- "/api/video2/isobmff_files",
13
- {
14
- method: "POST",
15
- body: JSON.stringify(payload)
16
- }
17
- );
11
+ const response = await client.authenticatedFetch("/api/v1/isobmff_files", {
12
+ method: "POST",
13
+ body: JSON.stringify(payload)
14
+ });
18
15
  log("ISOBMFF file created", response);
19
16
  if (response.ok) {
20
17
  return await response.json();
@@ -29,7 +26,7 @@ const uploadFragmentIndex = async (client, fileId, fileStream, fileSize) => {
29
26
  throw new Error(`File size exceeds limit of ${FILE_SIZE_LIMIT} bytes`);
30
27
  }
31
28
  const response = await client.authenticatedFetch(
32
- `/api/video2/isobmff_files/${fileId}/index/upload`,
29
+ `/api/v1/isobmff_files/${fileId}/index/upload`,
33
30
  {
34
31
  method: "POST",
35
32
  body: fileStream
@@ -27,13 +27,10 @@ const CreateISOBMFFTrackPayload = z.discriminatedUnion("type", [
27
27
  const createISOBMFFTrack = async (client, payload) => {
28
28
  log("Creating isobmff track", payload);
29
29
  CreateISOBMFFTrackPayload.parse(payload);
30
- const response = await client.authenticatedFetch(
31
- "/api/video2/isobmff_tracks",
32
- {
33
- method: "POST",
34
- body: JSON.stringify(payload)
35
- }
36
- );
30
+ const response = await client.authenticatedFetch("/api/v1/isobmff_tracks", {
31
+ method: "POST",
32
+ body: JSON.stringify(payload)
33
+ });
37
34
  log("ISOBMFF track created", response);
38
35
  if (response.ok) {
39
36
  return await response.json();
@@ -45,7 +42,7 @@ const createISOBMFFTrack = async (client, payload) => {
45
42
  const uploadISOBMFFTrack = async (client, fileId, trackId, fileStream, trackSize) => {
46
43
  log("Uploading fragment track", fileId);
47
44
  await uploadChunks(client, {
48
- url: `/api/video2/isobmff_tracks/${fileId}/${trackId}/upload`,
45
+ url: `/api/v1/isobmff_tracks/${fileId}/${trackId}/upload`,
49
46
  fileStream,
50
47
  fileSize: trackSize
51
48
  });
@@ -2,7 +2,7 @@ import { Readable } from 'node:stream';
2
2
  import { z } from 'zod';
3
3
  import { Client } from '../client.ts';
4
4
  export declare const CreateRenderPayload: z.ZodObject<{
5
- id: z.ZodString;
5
+ md5: z.ZodString;
6
6
  fps: z.ZodNumber;
7
7
  width: z.ZodNumber;
8
8
  height: z.ZodNumber;
@@ -10,7 +10,7 @@ export declare const CreateRenderPayload: z.ZodObject<{
10
10
  duration_ms: z.ZodNumber;
11
11
  strategy: z.ZodEnum<["v1", "v2"]>;
12
12
  }, "strip", z.ZodTypeAny, {
13
- id: string;
13
+ md5: string;
14
14
  height: number;
15
15
  width: number;
16
16
  strategy: "v1" | "v2";
@@ -18,7 +18,7 @@ export declare const CreateRenderPayload: z.ZodObject<{
18
18
  fps: number;
19
19
  work_slice_ms: number;
20
20
  }, {
21
- id: string;
21
+ md5: string;
22
22
  height: number;
23
23
  width: number;
24
24
  strategy: "v1" | "v2";
@@ -27,8 +27,9 @@ export declare const CreateRenderPayload: z.ZodObject<{
27
27
  work_slice_ms: number;
28
28
  }>;
29
29
  export interface CreateRenderResult {
30
- status: "complete" | "created" | "failed" | "pending" | "rendering";
31
30
  id: string;
31
+ md5: string;
32
+ status: "complete" | "created" | "failed" | "pending" | "rendering";
32
33
  }
33
34
  export declare const createRender: (client: Client, payload: z.infer<typeof CreateRenderPayload>) => Promise<CreateRenderResult>;
34
35
  export declare const uploadRender: (client: Client, fileId: string, fileStream: Readable, folderSize: number) => Promise<unknown>;
@@ -3,7 +3,7 @@ import debug from "debug";
3
3
  const log = debug("ef:api:renders");
4
4
  const FILE_SIZE_LIMIT = 1024 * 1024 * 16;
5
5
  const CreateRenderPayload = z.object({
6
- id: z.string().uuid(),
6
+ md5: z.string(),
7
7
  fps: z.number(),
8
8
  width: z.number().int(),
9
9
  height: z.number().int(),
@@ -13,7 +13,7 @@ const CreateRenderPayload = z.object({
13
13
  });
14
14
  const createRender = async (client, payload) => {
15
15
  log("Creating render", payload);
16
- const response = await client.authenticatedFetch("/api/video2/renders", {
16
+ const response = await client.authenticatedFetch("/api/v1/renders", {
17
17
  method: "POST",
18
18
  body: JSON.stringify(payload)
19
19
  });
@@ -32,7 +32,7 @@ const uploadRender = async (client, fileId, fileStream, folderSize) => {
32
32
  throw new Error(`File size exceeds limit of ${FILE_SIZE_LIMIT} bytes`);
33
33
  }
34
34
  const response = await client.authenticatedFetch(
35
- `/api/video2/renders/${fileId}/upload`,
35
+ `/api/v1/renders/${fileId}/upload`,
36
36
  {
37
37
  method: "POST",
38
38
  body: fileStream
@@ -3,17 +3,17 @@ import { z } from 'zod';
3
3
  import { Client } from '../client.ts';
4
4
  declare const FileProcessors: z.ZodEffects<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"isobmff">, z.ZodLiteral<"image">, z.ZodLiteral<"captions">]>, "many">, ("captions" | "image" | "isobmff")[], ("captions" | "image" | "isobmff")[]>;
5
5
  export declare const CreateUnprocessedFilePayload: z.ZodObject<{
6
- id: z.ZodString;
6
+ md5: z.ZodString;
7
7
  filename: z.ZodString;
8
8
  processes: z.ZodOptional<z.ZodEffects<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"isobmff">, z.ZodLiteral<"image">, z.ZodLiteral<"captions">]>, "many">, ("captions" | "image" | "isobmff")[], ("captions" | "image" | "isobmff")[]>>;
9
9
  byte_size: z.ZodNumber;
10
10
  }, "strip", z.ZodTypeAny, {
11
- id: string;
11
+ md5: string;
12
12
  filename: string;
13
13
  byte_size: number;
14
14
  processes?: ("captions" | "image" | "isobmff")[] | undefined;
15
15
  }, {
16
- id: string;
16
+ md5: string;
17
17
  filename: string;
18
18
  byte_size: number;
19
19
  processes?: ("captions" | "image" | "isobmff")[] | undefined;
@@ -28,14 +28,18 @@ export declare const UpdateUnprocessedFilePayload: z.ZodObject<{
28
28
  export interface CreateUnprocessedFileResult {
29
29
  byte_size: number;
30
30
  next_byte: number;
31
+ complete: boolean;
31
32
  id: string;
33
+ md5: string;
32
34
  processes: z.infer<typeof FileProcessors>;
33
35
  asset_id: string;
34
36
  }
35
37
  export interface UpdateUnprocessedFileResult {
36
38
  byte_size?: number;
37
39
  next_byte: number;
40
+ complete: boolean;
38
41
  id: string;
42
+ md5: string;
39
43
  processes: z.infer<typeof FileProcessors>;
40
44
  asset_id: string;
41
45
  }
@@ -22,7 +22,7 @@ const FileProcessors = z.array(FileProcessor).refine(
22
22
  );
23
23
  const MAX_FILE_SIZE = 1024 * 1024 * 1024;
24
24
  const CreateUnprocessedFilePayload = z.object({
25
- id: z.string(),
25
+ md5: z.string(),
26
26
  filename: z.string(),
27
27
  processes: FileProcessors.optional(),
28
28
  byte_size: z.number().int().max(MAX_FILE_SIZE)
@@ -80,55 +80,69 @@ const uploadUnprocessedFile = async (client, fileId, fileStream, fileSize) => {
80
80
  });
81
81
  log("Unprocessed file upload complete");
82
82
  };
83
+ const processResource = async (client, filename, md5, byteSize, processor, doUpload) => {
84
+ log("Processing", { filename, md5, byteSize, processor });
85
+ const unprocessedFile = await createUnprocessedFile(client, {
86
+ md5,
87
+ processes: [],
88
+ filename,
89
+ byte_size: byteSize
90
+ });
91
+ if (unprocessedFile.complete === false) {
92
+ await doUpload(unprocessedFile.id);
93
+ }
94
+ if (unprocessedFile.processes.includes(processor)) {
95
+ log("File already processed", unprocessedFile);
96
+ return unprocessedFile;
97
+ }
98
+ const fileInformation = await updateUnprocessedFile(
99
+ client,
100
+ unprocessedFile.id,
101
+ {
102
+ processes: [processor]
103
+ }
104
+ );
105
+ log("File processed", fileInformation);
106
+ return fileInformation;
107
+ };
83
108
  const buildBufferProcessor = (processor) => {
84
109
  return async (client, buffer, filename = "buffer") => {
85
110
  log(`Processing file buffer: ${processor}`, filename);
86
- const fileId = md5Buffer(buffer);
87
- log("File ID", fileId);
88
- log(`File size: ${buffer.byteLength} bytes`);
89
- await createUnprocessedFile(client, {
90
- id: fileId,
91
- processes: [],
111
+ const md5 = md5Buffer(buffer);
112
+ return await processResource(
113
+ client,
92
114
  filename,
93
- byte_size: buffer.byteLength
94
- });
95
- const readStream = new Readable({
96
- read() {
97
- readStream.push(buffer);
98
- readStream.push(null);
115
+ md5,
116
+ buffer.byteLength,
117
+ processor,
118
+ async (id) => {
119
+ const readStream = new Readable({
120
+ read() {
121
+ readStream.push(buffer);
122
+ readStream.push(null);
123
+ }
124
+ });
125
+ await uploadUnprocessedFile(client, id, readStream, buffer.byteLength);
99
126
  }
100
- });
101
- await uploadUnprocessedFile(client, fileId, readStream, buffer.byteLength);
102
- const fileInformation = await updateUnprocessedFile(client, fileId, {
103
- processes: [processor]
104
- });
105
- log("File processed", fileInformation);
106
- return fileInformation;
127
+ );
107
128
  };
108
129
  };
109
130
  const buildFileProcessor = (processor) => {
110
131
  return async (client, filePath) => {
111
132
  log(`Processing file ${processor}`, filePath);
112
- const fileId = await md5FilePath(filePath);
113
- log("File ID", fileId);
114
- await createUnprocessedFile(client, {
115
- id: fileId,
116
- processes: [],
117
- filename: basename(filePath),
118
- byte_size: (await stat(filePath)).size
119
- });
120
- const readStream = createReadStream(filePath);
121
- await uploadUnprocessedFile(
133
+ const md5 = await md5FilePath(filePath);
134
+ const byteSize = (await stat(filePath)).size;
135
+ return await processResource(
122
136
  client,
123
- fileId,
124
- readStream,
125
- (await stat(filePath)).size
137
+ basename(filePath),
138
+ md5,
139
+ byteSize,
140
+ processor,
141
+ async (id) => {
142
+ const readStream = createReadStream(filePath);
143
+ return await uploadUnprocessedFile(client, id, readStream, byteSize);
144
+ }
126
145
  );
127
- const fileInformation = await updateUnprocessedFile(client, fileId, {
128
- processes: [processor]
129
- });
130
- log("File processed", fileInformation);
131
- return fileInformation;
132
146
  };
133
147
  };
134
148
  const processAVFileBuffer = buildBufferProcessor("isobmff");
@@ -40,6 +40,12 @@ async function uploadChunks(client, {
40
40
  fileStream,
41
41
  chunkSizeBytes = CHUNK_SIZE_BYTES
42
42
  }) {
43
+ log("Checking upload status", url);
44
+ const uploadStatus = await client.authenticatedFetch(url);
45
+ if (uploadStatus.status === 200) {
46
+ log("Fragment track already uploaded");
47
+ return;
48
+ }
43
49
  let chunkNumber = 0;
44
50
  let complete = false;
45
51
  for await (const chunkBuffer of streamChunker(fileStream, chunkSizeBytes)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/api",
3
- "version": "0.9.0-beta.3",
3
+ "version": "0.10.0-beta.3",
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.9.0-beta.3",
31
+ "@editframe/assets": "0.10.0-beta.3",
32
32
  "debug": "^4.3.5",
33
33
  "jsonwebtoken": "^9.0.2",
34
34
  "node-fetch": "^3.3.2",
@@ -18,7 +18,7 @@ describe("CaptionFile", () => {
18
18
  test("Throws when file is too large", async () => {
19
19
  await expect(
20
20
  createCaptionFile(client, {
21
- id: "test-id",
21
+ md5: "test-md5",
22
22
  filename: "test",
23
23
  byte_size: 1024 * 1024 * 3,
24
24
  }),
@@ -29,14 +29,14 @@ describe("CaptionFile", () => {
29
29
 
30
30
  test("Throws when server returns an error", async () => {
31
31
  server.use(
32
- http.post("http://localhost/api/video2/caption_files", () =>
32
+ http.post("http://localhost/api/v1/caption_files", () =>
33
33
  HttpResponse.text("Internal Server Error", { status: 500 }),
34
34
  ),
35
35
  );
36
36
 
37
37
  await expect(
38
38
  createCaptionFile(client, {
39
- id: "test-id",
39
+ md5: "test-md5",
40
40
  filename: "test",
41
41
  byte_size: 4,
42
42
  }),
@@ -47,7 +47,7 @@ describe("CaptionFile", () => {
47
47
 
48
48
  test("Returns json data from the http response", async () => {
49
49
  server.use(
50
- http.post("http://localhost/api/video2/caption_files", () =>
50
+ http.post("http://localhost/api/v1/caption_files", () =>
51
51
  HttpResponse.json(
52
52
  { id: "test-id" },
53
53
  { status: 200, statusText: "OK" },
@@ -56,7 +56,7 @@ describe("CaptionFile", () => {
56
56
  );
57
57
 
58
58
  const response = await createCaptionFile(client, {
59
- id: "test-id",
59
+ md5: "test-md5",
60
60
  filename: "test",
61
61
  byte_size: 4,
62
62
  });
@@ -81,9 +81,8 @@ describe("CaptionFile", () => {
81
81
 
82
82
  test("Throws when server returns an error", async () => {
83
83
  server.use(
84
- http.post(
85
- "http://localhost/api/video2/caption_files/test-id/upload",
86
- () => HttpResponse.text("Internal Server Error", { status: 500 }),
84
+ http.post("http://localhost/api/v1/caption_files/test-id/upload", () =>
85
+ HttpResponse.text("Internal Server Error", { status: 500 }),
87
86
  ),
88
87
  );
89
88
 
@@ -101,13 +100,11 @@ describe("CaptionFile", () => {
101
100
 
102
101
  test("Returns json data from the http response", async () => {
103
102
  server.use(
104
- http.post(
105
- "http://localhost/api/video2/caption_files/test-id/upload",
106
- () =>
107
- HttpResponse.json(
108
- { id: "test-id" },
109
- { status: 200, statusText: "OK" },
110
- ),
103
+ http.post("http://localhost/api/v1/caption_files/test-id/upload", () =>
104
+ HttpResponse.json(
105
+ { id: "test-id" },
106
+ { status: 200, statusText: "OK" },
107
+ ),
111
108
  ),
112
109
  );
113
110
 
@@ -10,7 +10,7 @@ const log = debug("ef:api:caption-file");
10
10
  const MAX_CAPTION_SIZE = 1024 * 1024 * 2; // 2MB
11
11
 
12
12
  export const CreateCaptionFilePayload = z.object({
13
- id: z.string(),
13
+ md5: z.string(),
14
14
  filename: z.string(),
15
15
  byte_size: z.number().int().max(MAX_CAPTION_SIZE),
16
16
  });
@@ -18,6 +18,7 @@ export const CreateCaptionFilePayload = z.object({
18
18
  export interface CreateCaptionFileResult {
19
19
  complete: boolean | null;
20
20
  id: string;
21
+ md5: string;
21
22
  asset_id: string;
22
23
  }
23
24
 
@@ -52,13 +53,10 @@ export const createCaptionFile = async (
52
53
  ) => {
53
54
  log("Creating caption file", payload);
54
55
  restrictSize(payload.byte_size);
55
- const response = await client.authenticatedFetch(
56
- "/api/video2/caption_files",
57
- {
58
- method: "POST",
59
- body: JSON.stringify(payload),
60
- },
61
- );
56
+ const response = await client.authenticatedFetch("/api/v1/caption_files", {
57
+ method: "POST",
58
+ body: JSON.stringify(payload),
59
+ });
62
60
  log("Caption file created", response);
63
61
 
64
62
  if (response.ok) {
@@ -80,7 +78,7 @@ export const uploadCaptionFile = async (
80
78
  restrictSize(fileSize);
81
79
 
82
80
  const response = await client.authenticatedFetch(
83
- `/api/video2/caption_files/${fileId}/upload`,
81
+ `/api/v1/caption_files/${fileId}/upload`,
84
82
  {
85
83
  method: "POST",
86
84
  body: fileStream,