@unofficialbox/box-open-sdk 0.2.0 → 0.3.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kyle @ Unofficial Box
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  [![npm](https://img.shields.io/npm/v/@unofficialbox/box-open-sdk.svg)](https://www.npmjs.com/package/@unofficialbox/box-open-sdk)
7
7
 
8
- A **community, unofficial** Box API client for TypeScript — fully typed models
8
+ An **open source, community-built** Box API client for TypeScript — fully typed models
9
9
  for the whole Box surface, one manager per API area behind a single `Client`,
10
10
  and a `fetch`-based runtime with retry, backoff, and token refresh. Ships as a
11
11
  **dual ESM/CJS** package with bundled `.d.ts` declarations; no runtime
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ // Code generated by box-gantry. DO NOT EDIT.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ChunkedUpload = void 0;
5
+ /**
6
+ * ChunkedUpload orchestrates a Box chunked (multipart) upload over a Client: it
7
+ * creates an upload session, uploads the content's parts with bounded
8
+ * concurrency, and commits — for a new file (upload) or a new version of an
9
+ * existing file (uploadVersion). Box requires chunked upload for files at or
10
+ * above its minimum session size; smaller files use the single-shot endpoints.
11
+ */
12
+ class ChunkedUpload {
13
+ client;
14
+ /** Bounds parts in flight, capping peak memory. */
15
+ maxConcurrent = 4;
16
+ constructor(client) {
17
+ this.client = client;
18
+ }
19
+ /** Upload content as a new file named fileName into folderId. */
20
+ async upload(content, fileName, folderId) {
21
+ const session = await this.client.chunkedUploads.createFileUploadSession({
22
+ folder_id: folderId,
23
+ file_size: content.length,
24
+ file_name: fileName,
25
+ });
26
+ return this.finish(session, content);
27
+ }
28
+ /** Upload content as a new version of the existing file fileId. */
29
+ async uploadVersion(content, fileName, fileId) {
30
+ const session = await this.client.chunkedUploads.createFileVersionUploadSession(fileId, {
31
+ file_size: content.length,
32
+ file_name: fileName,
33
+ });
34
+ return this.finish(session, content);
35
+ }
36
+ async finish(session, content) {
37
+ if (session.id === undefined) {
38
+ throw new Error('gantry: upload session returned no id');
39
+ }
40
+ if (session.part_size === undefined || session.part_size <= 0) {
41
+ throw new Error('gantry: upload session returned a non-positive part_size');
42
+ }
43
+ const id = session.id;
44
+ const partSize = session.part_size;
45
+ const total = content.length;
46
+ const offsets = [];
47
+ for (let off = 0; off < total; off += partSize) {
48
+ offsets.push(off);
49
+ }
50
+ const parts = new Array(offsets.length);
51
+ // A bounded worker pool: each of maxConcurrent workers claims the next
52
+ // offset (the `next++` read is atomic between awaits on a single thread).
53
+ // Once any part fails, `failed` stops the others from starting new parts,
54
+ // so a failure early in a large upload doesn't push every remaining part.
55
+ let next = 0;
56
+ let failed = false;
57
+ const worker = async () => {
58
+ for (;;) {
59
+ if (failed) {
60
+ return;
61
+ }
62
+ const i = next++;
63
+ if (i >= offsets.length) {
64
+ return;
65
+ }
66
+ const start = offsets[i];
67
+ const end = Math.min(start + partSize, total);
68
+ try {
69
+ parts[i] = await this.uploadPart(id, content, start, end, total);
70
+ }
71
+ catch (err) {
72
+ failed = true;
73
+ throw err;
74
+ }
75
+ }
76
+ };
77
+ const workerCount = Math.min(this.maxConcurrent, offsets.length);
78
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
79
+ const digest = 'sha=' + (await sha1Base64(content));
80
+ return this.client.chunkedUploads.commitFileUploadSession(id, digest, { parts });
81
+ }
82
+ async uploadPart(id, content, start, end, total) {
83
+ const slice = content.subarray(start, end);
84
+ const digest = 'sha=' + (await sha1Base64(slice));
85
+ const contentRange = `bytes ${start}-${end - 1}/${total}`;
86
+ // The `as BlobPart` cast bridges the `Uint8Array<ArrayBufferLike>` vs
87
+ // `BufferSource` (ArrayBuffer-only) generic gap in the DOM lib, exactly as
88
+ // the runtime casts its `fetch` body; a plain `Uint8Array` is a valid part.
89
+ const uploaded = await this.client.chunkedUploads.updateFileUploadSession(id, digest, contentRange, new Blob([slice]));
90
+ if (uploaded.part === undefined) {
91
+ throw new Error('gantry: upload part returned no part');
92
+ }
93
+ return uploaded.part;
94
+ }
95
+ }
96
+ exports.ChunkedUpload = ChunkedUpload;
97
+ /** Base64 of the SHA-1 of data — the Box `Digest: sha=…` header value. Uses Web
98
+ * Crypto (browser and Node 18+), so it adds no dependency. */
99
+ async function sha1Base64(data) {
100
+ // `as BufferSource` bridges the `Uint8Array<ArrayBufferLike>` vs ArrayBuffer-only
101
+ // generic gap in the DOM lib (as the runtime does for its `fetch` body).
102
+ const digest = new Uint8Array(await crypto.subtle.digest('SHA-1', data));
103
+ let binary = '';
104
+ for (const byte of digest) {
105
+ binary += String.fromCharCode(byte);
106
+ }
107
+ return btoa(binary);
108
+ }
package/dist/cjs/index.js CHANGED
@@ -34,7 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.buildinfo = exports.auth = exports.runtime = exports.managers = exports.models = exports.Client = void 0;
37
+ exports.ChunkedUpload = exports.buildinfo = exports.auth = exports.runtime = exports.managers = exports.models = exports.Client = void 0;
38
38
  var client_js_1 = require("./client.js");
39
39
  Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return client_js_1.Client; } });
40
40
  exports.models = __importStar(require("./models/index.js"));
@@ -42,3 +42,5 @@ exports.managers = __importStar(require("./managers/index.js"));
42
42
  exports.runtime = __importStar(require("./runtime.js"));
43
43
  exports.auth = __importStar(require("./auth.js"));
44
44
  exports.buildinfo = __importStar(require("./buildinfo.js"));
45
+ var chunked_upload_js_1 = require("./chunked_upload.js");
46
+ Object.defineProperty(exports, "ChunkedUpload", { enumerable: true, get: function () { return chunked_upload_js_1.ChunkedUpload; } });
@@ -43,7 +43,7 @@ class ChunkedUploadsManager {
43
43
  constructor(session) {
44
44
  this.session = session;
45
45
  }
46
- async createFileUploadSessions(body) {
46
+ async createFileUploadSession(body) {
47
47
  let url = this.session.baseUrl("upload");
48
48
  url += "/files";
49
49
  url += "/upload_sessions";
@@ -54,7 +54,7 @@ class ChunkedUploadsManager {
54
54
  const data = runtime.responseBytes(resp);
55
55
  return JSON.parse(new TextDecoder().decode(data));
56
56
  }
57
- async createFileByIdUploadSessions(fileId, body) {
57
+ async createFileVersionUploadSession(fileId, body) {
58
58
  let url = this.session.baseUrl("upload");
59
59
  url += "/files";
60
60
  url += '/' + (0, internal_js_1.pathEscape)(fileId);
@@ -84,8 +84,8 @@ class ChunkedUploadsManager {
84
84
  let req = this.session.newRequest("PUT", url);
85
85
  req = runtime.withHeader(req, "digest", digest);
86
86
  req = runtime.withHeader(req, "content-range", contentRange);
87
- void body;
88
- req = runtime.withStreamBody(req, runtime.Stream.empty(), 'application/octet-stream');
87
+ const fileBytes = new Uint8Array(await body.arrayBuffer());
88
+ req = runtime.withStreamBody(req, new runtime.Stream(fileBytes), 'application/octet-stream');
89
89
  const resp = await this.session.fetch(req);
90
90
  const data = runtime.responseBytes(resp);
91
91
  return JSON.parse(new TextDecoder().decode(data));
@@ -0,0 +1,104 @@
1
+ // Code generated by box-gantry. DO NOT EDIT.
2
+ /**
3
+ * ChunkedUpload orchestrates a Box chunked (multipart) upload over a Client: it
4
+ * creates an upload session, uploads the content's parts with bounded
5
+ * concurrency, and commits — for a new file (upload) or a new version of an
6
+ * existing file (uploadVersion). Box requires chunked upload for files at or
7
+ * above its minimum session size; smaller files use the single-shot endpoints.
8
+ */
9
+ export class ChunkedUpload {
10
+ client;
11
+ /** Bounds parts in flight, capping peak memory. */
12
+ maxConcurrent = 4;
13
+ constructor(client) {
14
+ this.client = client;
15
+ }
16
+ /** Upload content as a new file named fileName into folderId. */
17
+ async upload(content, fileName, folderId) {
18
+ const session = await this.client.chunkedUploads.createFileUploadSession({
19
+ folder_id: folderId,
20
+ file_size: content.length,
21
+ file_name: fileName,
22
+ });
23
+ return this.finish(session, content);
24
+ }
25
+ /** Upload content as a new version of the existing file fileId. */
26
+ async uploadVersion(content, fileName, fileId) {
27
+ const session = await this.client.chunkedUploads.createFileVersionUploadSession(fileId, {
28
+ file_size: content.length,
29
+ file_name: fileName,
30
+ });
31
+ return this.finish(session, content);
32
+ }
33
+ async finish(session, content) {
34
+ if (session.id === undefined) {
35
+ throw new Error('gantry: upload session returned no id');
36
+ }
37
+ if (session.part_size === undefined || session.part_size <= 0) {
38
+ throw new Error('gantry: upload session returned a non-positive part_size');
39
+ }
40
+ const id = session.id;
41
+ const partSize = session.part_size;
42
+ const total = content.length;
43
+ const offsets = [];
44
+ for (let off = 0; off < total; off += partSize) {
45
+ offsets.push(off);
46
+ }
47
+ const parts = new Array(offsets.length);
48
+ // A bounded worker pool: each of maxConcurrent workers claims the next
49
+ // offset (the `next++` read is atomic between awaits on a single thread).
50
+ // Once any part fails, `failed` stops the others from starting new parts,
51
+ // so a failure early in a large upload doesn't push every remaining part.
52
+ let next = 0;
53
+ let failed = false;
54
+ const worker = async () => {
55
+ for (;;) {
56
+ if (failed) {
57
+ return;
58
+ }
59
+ const i = next++;
60
+ if (i >= offsets.length) {
61
+ return;
62
+ }
63
+ const start = offsets[i];
64
+ const end = Math.min(start + partSize, total);
65
+ try {
66
+ parts[i] = await this.uploadPart(id, content, start, end, total);
67
+ }
68
+ catch (err) {
69
+ failed = true;
70
+ throw err;
71
+ }
72
+ }
73
+ };
74
+ const workerCount = Math.min(this.maxConcurrent, offsets.length);
75
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
76
+ const digest = 'sha=' + (await sha1Base64(content));
77
+ return this.client.chunkedUploads.commitFileUploadSession(id, digest, { parts });
78
+ }
79
+ async uploadPart(id, content, start, end, total) {
80
+ const slice = content.subarray(start, end);
81
+ const digest = 'sha=' + (await sha1Base64(slice));
82
+ const contentRange = `bytes ${start}-${end - 1}/${total}`;
83
+ // The `as BlobPart` cast bridges the `Uint8Array<ArrayBufferLike>` vs
84
+ // `BufferSource` (ArrayBuffer-only) generic gap in the DOM lib, exactly as
85
+ // the runtime casts its `fetch` body; a plain `Uint8Array` is a valid part.
86
+ const uploaded = await this.client.chunkedUploads.updateFileUploadSession(id, digest, contentRange, new Blob([slice]));
87
+ if (uploaded.part === undefined) {
88
+ throw new Error('gantry: upload part returned no part');
89
+ }
90
+ return uploaded.part;
91
+ }
92
+ }
93
+ /** Base64 of the SHA-1 of data — the Box `Digest: sha=…` header value. Uses Web
94
+ * Crypto (browser and Node 18+), so it adds no dependency. */
95
+ async function sha1Base64(data) {
96
+ // `as BufferSource` bridges the `Uint8Array<ArrayBufferLike>` vs ArrayBuffer-only
97
+ // generic gap in the DOM lib (as the runtime does for its `fetch` body).
98
+ const digest = new Uint8Array(await crypto.subtle.digest('SHA-1', data));
99
+ let binary = '';
100
+ for (const byte of digest) {
101
+ binary += String.fromCharCode(byte);
102
+ }
103
+ return btoa(binary);
104
+ }
package/dist/esm/index.js CHANGED
@@ -5,3 +5,4 @@ export * as managers from './managers/index.js';
5
5
  export * as runtime from './runtime.js';
6
6
  export * as auth from './auth.js';
7
7
  export * as buildinfo from './buildinfo.js';
8
+ export { ChunkedUpload } from './chunked_upload.js';
@@ -7,7 +7,7 @@ export class ChunkedUploadsManager {
7
7
  constructor(session) {
8
8
  this.session = session;
9
9
  }
10
- async createFileUploadSessions(body) {
10
+ async createFileUploadSession(body) {
11
11
  let url = this.session.baseUrl("upload");
12
12
  url += "/files";
13
13
  url += "/upload_sessions";
@@ -18,7 +18,7 @@ export class ChunkedUploadsManager {
18
18
  const data = runtime.responseBytes(resp);
19
19
  return JSON.parse(new TextDecoder().decode(data));
20
20
  }
21
- async createFileByIdUploadSessions(fileId, body) {
21
+ async createFileVersionUploadSession(fileId, body) {
22
22
  let url = this.session.baseUrl("upload");
23
23
  url += "/files";
24
24
  url += '/' + pathEscape(fileId);
@@ -48,8 +48,8 @@ export class ChunkedUploadsManager {
48
48
  let req = this.session.newRequest("PUT", url);
49
49
  req = runtime.withHeader(req, "digest", digest);
50
50
  req = runtime.withHeader(req, "content-range", contentRange);
51
- void body;
52
- req = runtime.withStreamBody(req, runtime.Stream.empty(), 'application/octet-stream');
51
+ const fileBytes = new Uint8Array(await body.arrayBuffer());
52
+ req = runtime.withStreamBody(req, new runtime.Stream(fileBytes), 'application/octet-stream');
53
53
  const resp = await this.session.fetch(req);
54
54
  const data = runtime.responseBytes(resp);
55
55
  return JSON.parse(new TextDecoder().decode(data));
@@ -0,0 +1,21 @@
1
+ import type { Client } from './client.js';
2
+ import type * as models from './models/index.js';
3
+ /**
4
+ * ChunkedUpload orchestrates a Box chunked (multipart) upload over a Client: it
5
+ * creates an upload session, uploads the content's parts with bounded
6
+ * concurrency, and commits — for a new file (upload) or a new version of an
7
+ * existing file (uploadVersion). Box requires chunked upload for files at or
8
+ * above its minimum session size; smaller files use the single-shot endpoints.
9
+ */
10
+ export declare class ChunkedUpload {
11
+ private readonly client;
12
+ /** Bounds parts in flight, capping peak memory. */
13
+ private readonly maxConcurrent;
14
+ constructor(client: Client);
15
+ /** Upload content as a new file named fileName into folderId. */
16
+ upload(content: Uint8Array, fileName: string, folderId: string): Promise<models.schemas.Files>;
17
+ /** Upload content as a new version of the existing file fileId. */
18
+ uploadVersion(content: Uint8Array, fileName: string, fileId: string): Promise<models.schemas.Files>;
19
+ private finish;
20
+ private uploadPart;
21
+ }
@@ -4,3 +4,4 @@ export * as managers from './managers/index.js';
4
4
  export * as runtime from './runtime.js';
5
5
  export * as auth from './auth.js';
6
6
  export * as buildinfo from './buildinfo.js';
7
+ export { ChunkedUpload } from './chunked_upload.js';
@@ -14,8 +14,8 @@ export interface ChunkedUploadsManagerCommitFileUploadSessionOptions {
14
14
  export declare class ChunkedUploadsManager {
15
15
  private readonly session;
16
16
  constructor(session: runtime.Client);
17
- createFileUploadSessions(body: models.schemas.FileUploadSessionsCreateRequest): Promise<models.schemas.UploadSession>;
18
- createFileByIdUploadSessions(fileId: string, body: models.schemas.FileIdUploadSessionsCreateRequest): Promise<models.schemas.UploadSession>;
17
+ createFileUploadSession(body: models.schemas.FileUploadSessionCreateRequest): Promise<models.schemas.UploadSession>;
18
+ createFileVersionUploadSession(fileId: string, body: models.schemas.FileVersionUploadSessionCreateRequest): Promise<models.schemas.UploadSession>;
19
19
  getFileUploadSession(uploadSessionId: string): Promise<models.schemas.UploadSession>;
20
20
  updateFileUploadSession(uploadSessionId: string, digest: string, contentRange: string, body: Blob): Promise<models.schemas.UploadedPart>;
21
21
  deleteFileUploadSession(uploadSessionId: string): Promise<void>;
@@ -3059,12 +3059,12 @@ export interface OptionFileContentRequest {
3059
3059
  size?: number;
3060
3060
  parent?: PostFileIdParent;
3061
3061
  }
3062
- export interface FileUploadSessionsCreateRequest {
3062
+ export interface FileUploadSessionCreateRequest {
3063
3063
  folder_id: string;
3064
3064
  file_size: number;
3065
3065
  file_name: string;
3066
3066
  }
3067
- export interface FileIdUploadSessionsCreateRequest {
3067
+ export interface FileVersionUploadSessionCreateRequest {
3068
3068
  file_size: number;
3069
3069
  file_name?: string;
3070
3070
  }
@@ -3,21 +3,21 @@
3
3
 
4
4
  Reach these methods through the `chunkedUploads` field on `Client`.
5
5
 
6
- ## createFileUploadSessions
6
+ ## createFileUploadSession
7
7
 
8
8
  `POST /files/upload_sessions`
9
9
 
10
- **Request body** (`application/json`): `FileUploadSessionsCreateRequest`
10
+ **Request body** (`application/json`): `FileUploadSessionCreateRequest`
11
11
 
12
12
  **Returns:** `UploadSession`
13
13
 
14
14
  **Example**
15
15
 
16
16
  ```ts
17
- const result = await client.chunkedUploads.createFileUploadSessions({ /* … */ });
17
+ const result = await client.chunkedUploads.createFileUploadSession({ /* … */ });
18
18
  ```
19
19
 
20
- ## createFileByIdUploadSessions
20
+ ## createFileVersionUploadSession
21
21
 
22
22
  `POST /files/{file_id}/upload_sessions`
23
23
 
@@ -25,14 +25,14 @@ const result = await client.chunkedUploads.createFileUploadSessions({ /* … */
25
25
  |---|---|---|---|
26
26
  | `file_id` | path | `string` | yes |
27
27
 
28
- **Request body** (`application/json`): `FileIdUploadSessionsCreateRequest`
28
+ **Request body** (`application/json`): `FileVersionUploadSessionCreateRequest`
29
29
 
30
30
  **Returns:** `UploadSession`
31
31
 
32
32
  **Example**
33
33
 
34
34
  ```ts
35
- const result = await client.chunkedUploads.createFileByIdUploadSessions("FILE_ID", { /* … */ });
35
+ const result = await client.chunkedUploads.createFileVersionUploadSession("FILE_ID", { /* … */ });
36
36
  ```
37
37
 
38
38
  ## getFileUploadSession
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@unofficialbox/box-open-sdk",
3
- "version": "0.2.0",
4
- "description": "Community, unofficial Box API client for TypeScript — typed models, async managers, and a fetch runtime with retry, backoff, and token refresh.",
3
+ "version": "0.3.1",
4
+ "description": "Box API client for TypeScript (open source, community, punk rock) — typed models, async managers, and a fetch runtime with retry, backoff, and token refresh.",
5
5
  "keywords": ["box", "box-api", "sdk", "api-client", "typescript", "unofficial"],
6
6
  "license": "MIT",
7
- "repository": { "type": "git", "url": "git+https://github.com/unofficialbox/box-open-ts-sdk.git" },
7
+ "repository": { "type": "git", "url": "git+https://github.com/unofficialbox/box-gantry.git" },
8
8
  "homepage": "https://github.com/unofficialbox/box-open-ts-sdk",
9
9
  "type": "module",
10
10
  "sideEffects": false,