@air/api-rest 0.1.2 → 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/dist/index.cjs +126 -46
- package/dist/index.d.cts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +125 -46
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -50,7 +50,11 @@ __export(index_exports, {
|
|
|
50
50
|
Tags: () => Tags,
|
|
51
51
|
TimeoutError: () => import_api_core2.TimeoutError,
|
|
52
52
|
Uploads: () => Uploads,
|
|
53
|
-
VERSION: () => import_api_core2.VERSION
|
|
53
|
+
VERSION: () => import_api_core2.VERSION,
|
|
54
|
+
Workspaces: () => Workspaces,
|
|
55
|
+
buildAuthorizationUrl: () => import_api_core2.buildAuthorizationUrl,
|
|
56
|
+
exchangeAuthorizationCode: () => import_api_core2.exchangeAuthorizationCode,
|
|
57
|
+
generatePKCEChallenge: () => import_api_core2.generatePKCEChallenge
|
|
54
58
|
});
|
|
55
59
|
module.exports = __toCommonJS(index_exports);
|
|
56
60
|
|
|
@@ -420,22 +424,23 @@ var Uploads = class {
|
|
|
420
424
|
});
|
|
421
425
|
}
|
|
422
426
|
async uploadFile(params, options = {}) {
|
|
423
|
-
const
|
|
427
|
+
const meta = await resolveFileMeta(params);
|
|
424
428
|
const uploadResponse = await this.create({
|
|
425
|
-
fileName,
|
|
426
|
-
ext,
|
|
427
|
-
size,
|
|
428
|
-
mime,
|
|
429
|
+
fileName: meta.fileName,
|
|
430
|
+
ext: meta.ext,
|
|
431
|
+
size: meta.size,
|
|
432
|
+
mime: meta.mime,
|
|
429
433
|
parentBoardId: options.parentBoardId,
|
|
430
434
|
assetId: options.assetId,
|
|
431
435
|
customFields: options.customFields,
|
|
432
436
|
tags: options.tags
|
|
433
437
|
});
|
|
434
438
|
if (isSmallUpload(uploadResponse)) {
|
|
435
|
-
|
|
439
|
+
const data = await materializeData(meta);
|
|
440
|
+
await this._uploadSmallFile(uploadResponse.uploadUrl, data, options.onProgress, meta.size);
|
|
436
441
|
return { assetId: uploadResponse.assetId, versionId: uploadResponse.versionId };
|
|
437
442
|
}
|
|
438
|
-
await this._uploadLargeFile(uploadResponse,
|
|
443
|
+
await this._uploadLargeFile(uploadResponse, meta, options.onProgress);
|
|
439
444
|
return { assetId: uploadResponse.assetId, versionId: uploadResponse.versionId };
|
|
440
445
|
}
|
|
441
446
|
async _uploadSmallFile(url, data, onProgress, totalSize) {
|
|
@@ -451,42 +456,73 @@ var Uploads = class {
|
|
|
451
456
|
onProgress({ percentage: 100, uploadedBytes: totalSize, totalBytes: totalSize });
|
|
452
457
|
}
|
|
453
458
|
}
|
|
454
|
-
async _uploadLargeFile(uploadInfo,
|
|
459
|
+
async _uploadLargeFile(uploadInfo, meta, onProgress) {
|
|
455
460
|
const { multiPartUploadId, key } = uploadInfo;
|
|
456
|
-
const
|
|
461
|
+
const { size: totalSize } = meta;
|
|
457
462
|
const partCount = Math.ceil(totalSize / DEFAULT_PART_SIZE);
|
|
458
463
|
const parts = [];
|
|
459
464
|
let uploadedBytes = 0;
|
|
460
465
|
const fetchFn = this.client.fetchFn;
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
466
|
+
if (meta.source.type === "path") {
|
|
467
|
+
const fs = await import("fs");
|
|
468
|
+
const fd = fs.openSync(meta.source.filePath, "r");
|
|
469
|
+
try {
|
|
470
|
+
for (let i = 0; i < partCount; i++) {
|
|
471
|
+
const partNumber = i + 1;
|
|
472
|
+
const start = i * DEFAULT_PART_SIZE;
|
|
473
|
+
const end = Math.min(start + DEFAULT_PART_SIZE, totalSize);
|
|
474
|
+
const length = end - start;
|
|
475
|
+
const chunk = Buffer.alloc(length);
|
|
476
|
+
const bytesRead = fs.readSync(fd, chunk, 0, length, start);
|
|
477
|
+
const body = bytesRead < length ? chunk.subarray(0, bytesRead) : chunk;
|
|
478
|
+
const { url } = await this.getPartUploadUrl({ multiPartUploadId, key, partNumber });
|
|
479
|
+
const response = await fetchFn(url, { method: "PUT", body });
|
|
480
|
+
if (!response.ok) {
|
|
481
|
+
throw new Error(`Part ${partNumber} upload failed with status ${response.status}`);
|
|
482
|
+
}
|
|
483
|
+
const etag = response.headers.get("etag");
|
|
484
|
+
if (!etag) {
|
|
485
|
+
throw new Error(`Part ${partNumber} upload did not return an ETag`);
|
|
486
|
+
}
|
|
487
|
+
parts.push({ etag, partNumber });
|
|
488
|
+
uploadedBytes += bytesRead;
|
|
489
|
+
if (onProgress) {
|
|
490
|
+
onProgress({
|
|
491
|
+
percentage: Math.round(uploadedBytes / totalSize * 100),
|
|
492
|
+
uploadedBytes,
|
|
493
|
+
totalBytes: totalSize
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} finally {
|
|
498
|
+
fs.closeSync(fd);
|
|
481
499
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
500
|
+
} else {
|
|
501
|
+
const buffer = await materializeData(meta);
|
|
502
|
+
const buf = buffer instanceof Buffer ? buffer : Buffer.from(buffer);
|
|
503
|
+
for (let i = 0; i < partCount; i++) {
|
|
504
|
+
const partNumber = i + 1;
|
|
505
|
+
const start = i * DEFAULT_PART_SIZE;
|
|
506
|
+
const end = Math.min(start + DEFAULT_PART_SIZE, totalSize);
|
|
507
|
+
const chunk = buf.subarray(start, end);
|
|
508
|
+
const { url } = await this.getPartUploadUrl({ multiPartUploadId, key, partNumber });
|
|
509
|
+
const response = await fetchFn(url, { method: "PUT", body: chunk });
|
|
510
|
+
if (!response.ok) {
|
|
511
|
+
throw new Error(`Part ${partNumber} upload failed with status ${response.status}`);
|
|
512
|
+
}
|
|
513
|
+
const etag = response.headers.get("etag");
|
|
514
|
+
if (!etag) {
|
|
515
|
+
throw new Error(`Part ${partNumber} upload did not return an ETag`);
|
|
516
|
+
}
|
|
517
|
+
parts.push({ etag, partNumber });
|
|
518
|
+
uploadedBytes += chunk.byteLength;
|
|
519
|
+
if (onProgress) {
|
|
520
|
+
onProgress({
|
|
521
|
+
percentage: Math.round(uploadedBytes / totalSize * 100),
|
|
522
|
+
uploadedBytes,
|
|
523
|
+
totalBytes: totalSize
|
|
524
|
+
});
|
|
525
|
+
}
|
|
490
526
|
}
|
|
491
527
|
}
|
|
492
528
|
await this.completeMultipart({ multiPartUploadId, key, parts });
|
|
@@ -495,16 +531,22 @@ var Uploads = class {
|
|
|
495
531
|
function isSmallUpload(response) {
|
|
496
532
|
return "uploadUrl" in response;
|
|
497
533
|
}
|
|
498
|
-
async function
|
|
534
|
+
async function resolveFileMeta(params) {
|
|
499
535
|
if (params.filePath) {
|
|
500
536
|
const fs = await import("fs");
|
|
501
537
|
const path = await import("path");
|
|
502
|
-
const
|
|
538
|
+
const stat = fs.statSync(params.filePath);
|
|
503
539
|
const parsed = path.parse(params.filePath);
|
|
504
540
|
const fileName = params.fileName ?? parsed.name;
|
|
505
541
|
const ext = params.ext ?? parsed.ext.replace(/^\./, "");
|
|
506
542
|
const mime = params.mime ?? guessMime(ext);
|
|
507
|
-
return {
|
|
543
|
+
return {
|
|
544
|
+
source: { type: "path", filePath: params.filePath },
|
|
545
|
+
fileName,
|
|
546
|
+
ext,
|
|
547
|
+
mime,
|
|
548
|
+
size: stat.size
|
|
549
|
+
};
|
|
508
550
|
}
|
|
509
551
|
if (params.buffer) {
|
|
510
552
|
const data = params.buffer instanceof Buffer ? params.buffer : Buffer.from(params.buffer);
|
|
@@ -513,7 +555,7 @@ async function resolveFileData(params) {
|
|
|
513
555
|
}
|
|
514
556
|
const mime = params.mime ?? guessMime(params.ext);
|
|
515
557
|
return {
|
|
516
|
-
|
|
558
|
+
source: { type: "buffer", data },
|
|
517
559
|
fileName: params.fileName,
|
|
518
560
|
ext: params.ext,
|
|
519
561
|
mime,
|
|
@@ -521,17 +563,35 @@ async function resolveFileData(params) {
|
|
|
521
563
|
};
|
|
522
564
|
}
|
|
523
565
|
if (params.file) {
|
|
524
|
-
const arrayBuffer = await params.file.arrayBuffer();
|
|
525
|
-
const data = new Uint8Array(arrayBuffer);
|
|
526
566
|
const name = params.file.name;
|
|
527
567
|
const dotIndex = name.lastIndexOf(".");
|
|
528
568
|
const fileName = params.fileName ?? (dotIndex > 0 ? name.substring(0, dotIndex) : name);
|
|
529
569
|
const ext = params.ext ?? (dotIndex > 0 ? name.substring(dotIndex + 1) : "");
|
|
530
570
|
const mime = params.mime ?? (params.file.type || guessMime(ext));
|
|
531
|
-
return {
|
|
571
|
+
return {
|
|
572
|
+
source: { type: "file", file: params.file },
|
|
573
|
+
fileName,
|
|
574
|
+
ext,
|
|
575
|
+
mime,
|
|
576
|
+
size: params.file.size
|
|
577
|
+
};
|
|
532
578
|
}
|
|
533
579
|
throw new Error("One of filePath, buffer, or file must be provided");
|
|
534
580
|
}
|
|
581
|
+
async function materializeData(meta) {
|
|
582
|
+
switch (meta.source.type) {
|
|
583
|
+
case "path": {
|
|
584
|
+
const fs = await import("fs");
|
|
585
|
+
return fs.readFileSync(meta.source.filePath);
|
|
586
|
+
}
|
|
587
|
+
case "buffer":
|
|
588
|
+
return meta.source.data;
|
|
589
|
+
case "file": {
|
|
590
|
+
const arrayBuffer = await meta.source.file.arrayBuffer();
|
|
591
|
+
return new Uint8Array(arrayBuffer);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
535
595
|
function guessMime(ext) {
|
|
536
596
|
const mimeMap = {
|
|
537
597
|
jpg: "image/jpeg",
|
|
@@ -564,6 +624,20 @@ function guessMime(ext) {
|
|
|
564
624
|
return mimeMap[ext.toLowerCase()] ?? "application/octet-stream";
|
|
565
625
|
}
|
|
566
626
|
|
|
627
|
+
// src/resources/workspaces.ts
|
|
628
|
+
var Workspaces = class {
|
|
629
|
+
constructor(client) {
|
|
630
|
+
this.client = client;
|
|
631
|
+
}
|
|
632
|
+
async list() {
|
|
633
|
+
const response = await this.client.request({
|
|
634
|
+
method: "GET",
|
|
635
|
+
path: "/workspaces"
|
|
636
|
+
});
|
|
637
|
+
return response.data;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
567
641
|
// src/api.ts
|
|
568
642
|
var AirApi = class extends import_api_core.AirBase {
|
|
569
643
|
assets;
|
|
@@ -573,6 +647,7 @@ var AirApi = class extends import_api_core.AirBase {
|
|
|
573
647
|
roles;
|
|
574
648
|
imports;
|
|
575
649
|
uploads;
|
|
650
|
+
workspaces;
|
|
576
651
|
constructor(options = {}) {
|
|
577
652
|
super(options);
|
|
578
653
|
this.assets = new Assets(this);
|
|
@@ -582,6 +657,7 @@ var AirApi = class extends import_api_core.AirBase {
|
|
|
582
657
|
this.roles = new Roles(this);
|
|
583
658
|
this.imports = new Imports(this);
|
|
584
659
|
this.uploads = new Uploads(this);
|
|
660
|
+
this.workspaces = new Workspaces(this);
|
|
585
661
|
}
|
|
586
662
|
};
|
|
587
663
|
|
|
@@ -609,5 +685,9 @@ var import_api_core2 = require("@air/api-core");
|
|
|
609
685
|
Tags,
|
|
610
686
|
TimeoutError,
|
|
611
687
|
Uploads,
|
|
612
|
-
VERSION
|
|
688
|
+
VERSION,
|
|
689
|
+
Workspaces,
|
|
690
|
+
buildAuthorizationUrl,
|
|
691
|
+
exchangeAuthorizationCode,
|
|
692
|
+
generatePKCEChallenge
|
|
613
693
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CursorPageParams, AirBase, PagePromise, AirBaseOptions } from '@air/api-core';
|
|
2
|
-
export { APIError, AirBase, AirBaseOptions, AuthenticationError, BadRequestError, ConnectionError, CursorPage, CursorPageParams, CursorPageResponse, InternalServerError, NotFoundError, PagePromise, PermissionError, RateLimitError, RequestOptions, TimeoutError, VERSION } from '@air/api-core';
|
|
2
|
+
export { APIError, AirBase, AirBaseOptions, AuthenticationError, BadRequestError, BuildAuthorizationUrlOptions, ConnectionError, CursorPage, CursorPageParams, CursorPageResponse, ExchangeAuthorizationCodeOptions, InternalServerError, NotFoundError, OAuthAccessToken, OAuthClientAuthMethod, PKCEChallenge, PagePromise, PermissionError, RateLimitError, RequestOptions, TimeoutError, VERSION, buildAuthorizationUrl, exchangeAuthorizationCode, generatePKCEChallenge } from '@air/api-core';
|
|
3
3
|
|
|
4
4
|
interface Tag {
|
|
5
5
|
id: string;
|
|
@@ -341,6 +341,23 @@ interface CompleteMultipartParams {
|
|
|
341
341
|
partNumber: number;
|
|
342
342
|
}[];
|
|
343
343
|
}
|
|
344
|
+
type FileSource = {
|
|
345
|
+
type: "path";
|
|
346
|
+
filePath: string;
|
|
347
|
+
} | {
|
|
348
|
+
type: "buffer";
|
|
349
|
+
data: Buffer;
|
|
350
|
+
} | {
|
|
351
|
+
type: "file";
|
|
352
|
+
file: File;
|
|
353
|
+
};
|
|
354
|
+
interface FileMeta {
|
|
355
|
+
source: FileSource;
|
|
356
|
+
fileName: string;
|
|
357
|
+
ext: string;
|
|
358
|
+
mime: string;
|
|
359
|
+
size: number;
|
|
360
|
+
}
|
|
344
361
|
interface UploadFileParams {
|
|
345
362
|
filePath?: string;
|
|
346
363
|
buffer?: Buffer | ArrayBuffer;
|
|
@@ -384,6 +401,20 @@ declare class Uploads {
|
|
|
384
401
|
private _uploadLargeFile;
|
|
385
402
|
}
|
|
386
403
|
|
|
404
|
+
interface Workspace {
|
|
405
|
+
id: string;
|
|
406
|
+
name: string;
|
|
407
|
+
}
|
|
408
|
+
interface WorkspaceListResponse {
|
|
409
|
+
data: Workspace[];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
declare class Workspaces {
|
|
413
|
+
private client;
|
|
414
|
+
constructor(client: AirBase);
|
|
415
|
+
list(): Promise<Workspace[]>;
|
|
416
|
+
}
|
|
417
|
+
|
|
387
418
|
interface AirApiOptions extends AirBaseOptions {
|
|
388
419
|
}
|
|
389
420
|
declare class AirApi extends AirBase {
|
|
@@ -394,7 +425,8 @@ declare class AirApi extends AirBase {
|
|
|
394
425
|
readonly roles: Roles;
|
|
395
426
|
readonly imports: Imports;
|
|
396
427
|
readonly uploads: Uploads;
|
|
428
|
+
readonly workspaces: Workspaces;
|
|
397
429
|
constructor(options?: AirApiOptions);
|
|
398
430
|
}
|
|
399
431
|
|
|
400
|
-
export { type AddBoardAssetsParams, type AddGuestParams, type AddVersionTagParams, AirApi, type AirApiOptions, type Asset, type AssetBoardListParams, type AssetCustomFieldValue, type AssetListParams, type AssetVersion, type AssetVersionDownloadUrl, type AssetVersionListResponse, type AssetVersionUpdateParams, type AssetVersionUrls, Assets, type Board, type BoardCreateParams, type BoardListParams, type BoardUpdateParams, Boards, type CompleteMultipartParams, type CustomField, type CustomFieldCreateParams, type CustomFieldListParams, type CustomFieldType, type CustomFieldUpdateParams, type CustomFieldValue, type CustomFieldValueCreateParams, type CustomFieldValueUpdateParams, CustomFields, type GetPartUploadUrlParams, type GetPartUploadUrlResponse, type Guest, type GuestListParams, type GuestListResponse, type ImportCreateParams, type ImportCreateResponse, type ImportStatus, type ImportStatusResponse, Imports, type LargeUploadResponse, type Role, type RoleListParams, type RoleListResponse, Roles, type SetBoardCustomFieldParams, type SetCustomFieldParams, type SmallUploadResponse, type SmartTag, type Tag, type TagCreateParams, type TagListParams, type TagUpdateParams, Tags, type UpdateGuestParams, type UploadCreateParams, type UploadCreateResponse, type UploadFileOptions, type UploadFileParams, type UploadFileResponse, Uploads };
|
|
432
|
+
export { type AddBoardAssetsParams, type AddGuestParams, type AddVersionTagParams, AirApi, type AirApiOptions, type Asset, type AssetBoardListParams, type AssetCustomFieldValue, type AssetListParams, type AssetVersion, type AssetVersionDownloadUrl, type AssetVersionListResponse, type AssetVersionUpdateParams, type AssetVersionUrls, Assets, type Board, type BoardCreateParams, type BoardListParams, type BoardUpdateParams, Boards, type CompleteMultipartParams, type CustomField, type CustomFieldCreateParams, type CustomFieldListParams, type CustomFieldType, type CustomFieldUpdateParams, type CustomFieldValue, type CustomFieldValueCreateParams, type CustomFieldValueUpdateParams, CustomFields, type FileMeta, type FileSource, type GetPartUploadUrlParams, type GetPartUploadUrlResponse, type Guest, type GuestListParams, type GuestListResponse, type ImportCreateParams, type ImportCreateResponse, type ImportStatus, type ImportStatusResponse, Imports, type LargeUploadResponse, type Role, type RoleListParams, type RoleListResponse, Roles, type SetBoardCustomFieldParams, type SetCustomFieldParams, type SmallUploadResponse, type SmartTag, type Tag, type TagCreateParams, type TagListParams, type TagUpdateParams, Tags, type UpdateGuestParams, type UploadCreateParams, type UploadCreateResponse, type UploadFileOptions, type UploadFileParams, type UploadFileResponse, Uploads, type Workspace, type WorkspaceListResponse, Workspaces };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CursorPageParams, AirBase, PagePromise, AirBaseOptions } from '@air/api-core';
|
|
2
|
-
export { APIError, AirBase, AirBaseOptions, AuthenticationError, BadRequestError, ConnectionError, CursorPage, CursorPageParams, CursorPageResponse, InternalServerError, NotFoundError, PagePromise, PermissionError, RateLimitError, RequestOptions, TimeoutError, VERSION } from '@air/api-core';
|
|
2
|
+
export { APIError, AirBase, AirBaseOptions, AuthenticationError, BadRequestError, BuildAuthorizationUrlOptions, ConnectionError, CursorPage, CursorPageParams, CursorPageResponse, ExchangeAuthorizationCodeOptions, InternalServerError, NotFoundError, OAuthAccessToken, OAuthClientAuthMethod, PKCEChallenge, PagePromise, PermissionError, RateLimitError, RequestOptions, TimeoutError, VERSION, buildAuthorizationUrl, exchangeAuthorizationCode, generatePKCEChallenge } from '@air/api-core';
|
|
3
3
|
|
|
4
4
|
interface Tag {
|
|
5
5
|
id: string;
|
|
@@ -341,6 +341,23 @@ interface CompleteMultipartParams {
|
|
|
341
341
|
partNumber: number;
|
|
342
342
|
}[];
|
|
343
343
|
}
|
|
344
|
+
type FileSource = {
|
|
345
|
+
type: "path";
|
|
346
|
+
filePath: string;
|
|
347
|
+
} | {
|
|
348
|
+
type: "buffer";
|
|
349
|
+
data: Buffer;
|
|
350
|
+
} | {
|
|
351
|
+
type: "file";
|
|
352
|
+
file: File;
|
|
353
|
+
};
|
|
354
|
+
interface FileMeta {
|
|
355
|
+
source: FileSource;
|
|
356
|
+
fileName: string;
|
|
357
|
+
ext: string;
|
|
358
|
+
mime: string;
|
|
359
|
+
size: number;
|
|
360
|
+
}
|
|
344
361
|
interface UploadFileParams {
|
|
345
362
|
filePath?: string;
|
|
346
363
|
buffer?: Buffer | ArrayBuffer;
|
|
@@ -384,6 +401,20 @@ declare class Uploads {
|
|
|
384
401
|
private _uploadLargeFile;
|
|
385
402
|
}
|
|
386
403
|
|
|
404
|
+
interface Workspace {
|
|
405
|
+
id: string;
|
|
406
|
+
name: string;
|
|
407
|
+
}
|
|
408
|
+
interface WorkspaceListResponse {
|
|
409
|
+
data: Workspace[];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
declare class Workspaces {
|
|
413
|
+
private client;
|
|
414
|
+
constructor(client: AirBase);
|
|
415
|
+
list(): Promise<Workspace[]>;
|
|
416
|
+
}
|
|
417
|
+
|
|
387
418
|
interface AirApiOptions extends AirBaseOptions {
|
|
388
419
|
}
|
|
389
420
|
declare class AirApi extends AirBase {
|
|
@@ -394,7 +425,8 @@ declare class AirApi extends AirBase {
|
|
|
394
425
|
readonly roles: Roles;
|
|
395
426
|
readonly imports: Imports;
|
|
396
427
|
readonly uploads: Uploads;
|
|
428
|
+
readonly workspaces: Workspaces;
|
|
397
429
|
constructor(options?: AirApiOptions);
|
|
398
430
|
}
|
|
399
431
|
|
|
400
|
-
export { type AddBoardAssetsParams, type AddGuestParams, type AddVersionTagParams, AirApi, type AirApiOptions, type Asset, type AssetBoardListParams, type AssetCustomFieldValue, type AssetListParams, type AssetVersion, type AssetVersionDownloadUrl, type AssetVersionListResponse, type AssetVersionUpdateParams, type AssetVersionUrls, Assets, type Board, type BoardCreateParams, type BoardListParams, type BoardUpdateParams, Boards, type CompleteMultipartParams, type CustomField, type CustomFieldCreateParams, type CustomFieldListParams, type CustomFieldType, type CustomFieldUpdateParams, type CustomFieldValue, type CustomFieldValueCreateParams, type CustomFieldValueUpdateParams, CustomFields, type GetPartUploadUrlParams, type GetPartUploadUrlResponse, type Guest, type GuestListParams, type GuestListResponse, type ImportCreateParams, type ImportCreateResponse, type ImportStatus, type ImportStatusResponse, Imports, type LargeUploadResponse, type Role, type RoleListParams, type RoleListResponse, Roles, type SetBoardCustomFieldParams, type SetCustomFieldParams, type SmallUploadResponse, type SmartTag, type Tag, type TagCreateParams, type TagListParams, type TagUpdateParams, Tags, type UpdateGuestParams, type UploadCreateParams, type UploadCreateResponse, type UploadFileOptions, type UploadFileParams, type UploadFileResponse, Uploads };
|
|
432
|
+
export { type AddBoardAssetsParams, type AddGuestParams, type AddVersionTagParams, AirApi, type AirApiOptions, type Asset, type AssetBoardListParams, type AssetCustomFieldValue, type AssetListParams, type AssetVersion, type AssetVersionDownloadUrl, type AssetVersionListResponse, type AssetVersionUpdateParams, type AssetVersionUrls, Assets, type Board, type BoardCreateParams, type BoardListParams, type BoardUpdateParams, Boards, type CompleteMultipartParams, type CustomField, type CustomFieldCreateParams, type CustomFieldListParams, type CustomFieldType, type CustomFieldUpdateParams, type CustomFieldValue, type CustomFieldValueCreateParams, type CustomFieldValueUpdateParams, CustomFields, type FileMeta, type FileSource, type GetPartUploadUrlParams, type GetPartUploadUrlResponse, type Guest, type GuestListParams, type GuestListResponse, type ImportCreateParams, type ImportCreateResponse, type ImportStatus, type ImportStatusResponse, Imports, type LargeUploadResponse, type Role, type RoleListParams, type RoleListResponse, Roles, type SetBoardCustomFieldParams, type SetCustomFieldParams, type SmallUploadResponse, type SmartTag, type Tag, type TagCreateParams, type TagListParams, type TagUpdateParams, Tags, type UpdateGuestParams, type UploadCreateParams, type UploadCreateResponse, type UploadFileOptions, type UploadFileParams, type UploadFileResponse, Uploads, type Workspace, type WorkspaceListResponse, Workspaces };
|
package/dist/index.js
CHANGED
|
@@ -364,22 +364,23 @@ var Uploads = class {
|
|
|
364
364
|
});
|
|
365
365
|
}
|
|
366
366
|
async uploadFile(params, options = {}) {
|
|
367
|
-
const
|
|
367
|
+
const meta = await resolveFileMeta(params);
|
|
368
368
|
const uploadResponse = await this.create({
|
|
369
|
-
fileName,
|
|
370
|
-
ext,
|
|
371
|
-
size,
|
|
372
|
-
mime,
|
|
369
|
+
fileName: meta.fileName,
|
|
370
|
+
ext: meta.ext,
|
|
371
|
+
size: meta.size,
|
|
372
|
+
mime: meta.mime,
|
|
373
373
|
parentBoardId: options.parentBoardId,
|
|
374
374
|
assetId: options.assetId,
|
|
375
375
|
customFields: options.customFields,
|
|
376
376
|
tags: options.tags
|
|
377
377
|
});
|
|
378
378
|
if (isSmallUpload(uploadResponse)) {
|
|
379
|
-
|
|
379
|
+
const data = await materializeData(meta);
|
|
380
|
+
await this._uploadSmallFile(uploadResponse.uploadUrl, data, options.onProgress, meta.size);
|
|
380
381
|
return { assetId: uploadResponse.assetId, versionId: uploadResponse.versionId };
|
|
381
382
|
}
|
|
382
|
-
await this._uploadLargeFile(uploadResponse,
|
|
383
|
+
await this._uploadLargeFile(uploadResponse, meta, options.onProgress);
|
|
383
384
|
return { assetId: uploadResponse.assetId, versionId: uploadResponse.versionId };
|
|
384
385
|
}
|
|
385
386
|
async _uploadSmallFile(url, data, onProgress, totalSize) {
|
|
@@ -395,42 +396,73 @@ var Uploads = class {
|
|
|
395
396
|
onProgress({ percentage: 100, uploadedBytes: totalSize, totalBytes: totalSize });
|
|
396
397
|
}
|
|
397
398
|
}
|
|
398
|
-
async _uploadLargeFile(uploadInfo,
|
|
399
|
+
async _uploadLargeFile(uploadInfo, meta, onProgress) {
|
|
399
400
|
const { multiPartUploadId, key } = uploadInfo;
|
|
400
|
-
const
|
|
401
|
+
const { size: totalSize } = meta;
|
|
401
402
|
const partCount = Math.ceil(totalSize / DEFAULT_PART_SIZE);
|
|
402
403
|
const parts = [];
|
|
403
404
|
let uploadedBytes = 0;
|
|
404
405
|
const fetchFn = this.client.fetchFn;
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
406
|
+
if (meta.source.type === "path") {
|
|
407
|
+
const fs = await import("fs");
|
|
408
|
+
const fd = fs.openSync(meta.source.filePath, "r");
|
|
409
|
+
try {
|
|
410
|
+
for (let i = 0; i < partCount; i++) {
|
|
411
|
+
const partNumber = i + 1;
|
|
412
|
+
const start = i * DEFAULT_PART_SIZE;
|
|
413
|
+
const end = Math.min(start + DEFAULT_PART_SIZE, totalSize);
|
|
414
|
+
const length = end - start;
|
|
415
|
+
const chunk = Buffer.alloc(length);
|
|
416
|
+
const bytesRead = fs.readSync(fd, chunk, 0, length, start);
|
|
417
|
+
const body = bytesRead < length ? chunk.subarray(0, bytesRead) : chunk;
|
|
418
|
+
const { url } = await this.getPartUploadUrl({ multiPartUploadId, key, partNumber });
|
|
419
|
+
const response = await fetchFn(url, { method: "PUT", body });
|
|
420
|
+
if (!response.ok) {
|
|
421
|
+
throw new Error(`Part ${partNumber} upload failed with status ${response.status}`);
|
|
422
|
+
}
|
|
423
|
+
const etag = response.headers.get("etag");
|
|
424
|
+
if (!etag) {
|
|
425
|
+
throw new Error(`Part ${partNumber} upload did not return an ETag`);
|
|
426
|
+
}
|
|
427
|
+
parts.push({ etag, partNumber });
|
|
428
|
+
uploadedBytes += bytesRead;
|
|
429
|
+
if (onProgress) {
|
|
430
|
+
onProgress({
|
|
431
|
+
percentage: Math.round(uploadedBytes / totalSize * 100),
|
|
432
|
+
uploadedBytes,
|
|
433
|
+
totalBytes: totalSize
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} finally {
|
|
438
|
+
fs.closeSync(fd);
|
|
425
439
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
440
|
+
} else {
|
|
441
|
+
const buffer = await materializeData(meta);
|
|
442
|
+
const buf = buffer instanceof Buffer ? buffer : Buffer.from(buffer);
|
|
443
|
+
for (let i = 0; i < partCount; i++) {
|
|
444
|
+
const partNumber = i + 1;
|
|
445
|
+
const start = i * DEFAULT_PART_SIZE;
|
|
446
|
+
const end = Math.min(start + DEFAULT_PART_SIZE, totalSize);
|
|
447
|
+
const chunk = buf.subarray(start, end);
|
|
448
|
+
const { url } = await this.getPartUploadUrl({ multiPartUploadId, key, partNumber });
|
|
449
|
+
const response = await fetchFn(url, { method: "PUT", body: chunk });
|
|
450
|
+
if (!response.ok) {
|
|
451
|
+
throw new Error(`Part ${partNumber} upload failed with status ${response.status}`);
|
|
452
|
+
}
|
|
453
|
+
const etag = response.headers.get("etag");
|
|
454
|
+
if (!etag) {
|
|
455
|
+
throw new Error(`Part ${partNumber} upload did not return an ETag`);
|
|
456
|
+
}
|
|
457
|
+
parts.push({ etag, partNumber });
|
|
458
|
+
uploadedBytes += chunk.byteLength;
|
|
459
|
+
if (onProgress) {
|
|
460
|
+
onProgress({
|
|
461
|
+
percentage: Math.round(uploadedBytes / totalSize * 100),
|
|
462
|
+
uploadedBytes,
|
|
463
|
+
totalBytes: totalSize
|
|
464
|
+
});
|
|
465
|
+
}
|
|
434
466
|
}
|
|
435
467
|
}
|
|
436
468
|
await this.completeMultipart({ multiPartUploadId, key, parts });
|
|
@@ -439,16 +471,22 @@ var Uploads = class {
|
|
|
439
471
|
function isSmallUpload(response) {
|
|
440
472
|
return "uploadUrl" in response;
|
|
441
473
|
}
|
|
442
|
-
async function
|
|
474
|
+
async function resolveFileMeta(params) {
|
|
443
475
|
if (params.filePath) {
|
|
444
476
|
const fs = await import("fs");
|
|
445
477
|
const path = await import("path");
|
|
446
|
-
const
|
|
478
|
+
const stat = fs.statSync(params.filePath);
|
|
447
479
|
const parsed = path.parse(params.filePath);
|
|
448
480
|
const fileName = params.fileName ?? parsed.name;
|
|
449
481
|
const ext = params.ext ?? parsed.ext.replace(/^\./, "");
|
|
450
482
|
const mime = params.mime ?? guessMime(ext);
|
|
451
|
-
return {
|
|
483
|
+
return {
|
|
484
|
+
source: { type: "path", filePath: params.filePath },
|
|
485
|
+
fileName,
|
|
486
|
+
ext,
|
|
487
|
+
mime,
|
|
488
|
+
size: stat.size
|
|
489
|
+
};
|
|
452
490
|
}
|
|
453
491
|
if (params.buffer) {
|
|
454
492
|
const data = params.buffer instanceof Buffer ? params.buffer : Buffer.from(params.buffer);
|
|
@@ -457,7 +495,7 @@ async function resolveFileData(params) {
|
|
|
457
495
|
}
|
|
458
496
|
const mime = params.mime ?? guessMime(params.ext);
|
|
459
497
|
return {
|
|
460
|
-
|
|
498
|
+
source: { type: "buffer", data },
|
|
461
499
|
fileName: params.fileName,
|
|
462
500
|
ext: params.ext,
|
|
463
501
|
mime,
|
|
@@ -465,17 +503,35 @@ async function resolveFileData(params) {
|
|
|
465
503
|
};
|
|
466
504
|
}
|
|
467
505
|
if (params.file) {
|
|
468
|
-
const arrayBuffer = await params.file.arrayBuffer();
|
|
469
|
-
const data = new Uint8Array(arrayBuffer);
|
|
470
506
|
const name = params.file.name;
|
|
471
507
|
const dotIndex = name.lastIndexOf(".");
|
|
472
508
|
const fileName = params.fileName ?? (dotIndex > 0 ? name.substring(0, dotIndex) : name);
|
|
473
509
|
const ext = params.ext ?? (dotIndex > 0 ? name.substring(dotIndex + 1) : "");
|
|
474
510
|
const mime = params.mime ?? (params.file.type || guessMime(ext));
|
|
475
|
-
return {
|
|
511
|
+
return {
|
|
512
|
+
source: { type: "file", file: params.file },
|
|
513
|
+
fileName,
|
|
514
|
+
ext,
|
|
515
|
+
mime,
|
|
516
|
+
size: params.file.size
|
|
517
|
+
};
|
|
476
518
|
}
|
|
477
519
|
throw new Error("One of filePath, buffer, or file must be provided");
|
|
478
520
|
}
|
|
521
|
+
async function materializeData(meta) {
|
|
522
|
+
switch (meta.source.type) {
|
|
523
|
+
case "path": {
|
|
524
|
+
const fs = await import("fs");
|
|
525
|
+
return fs.readFileSync(meta.source.filePath);
|
|
526
|
+
}
|
|
527
|
+
case "buffer":
|
|
528
|
+
return meta.source.data;
|
|
529
|
+
case "file": {
|
|
530
|
+
const arrayBuffer = await meta.source.file.arrayBuffer();
|
|
531
|
+
return new Uint8Array(arrayBuffer);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
479
535
|
function guessMime(ext) {
|
|
480
536
|
const mimeMap = {
|
|
481
537
|
jpg: "image/jpeg",
|
|
@@ -508,6 +564,20 @@ function guessMime(ext) {
|
|
|
508
564
|
return mimeMap[ext.toLowerCase()] ?? "application/octet-stream";
|
|
509
565
|
}
|
|
510
566
|
|
|
567
|
+
// src/resources/workspaces.ts
|
|
568
|
+
var Workspaces = class {
|
|
569
|
+
constructor(client) {
|
|
570
|
+
this.client = client;
|
|
571
|
+
}
|
|
572
|
+
async list() {
|
|
573
|
+
const response = await this.client.request({
|
|
574
|
+
method: "GET",
|
|
575
|
+
path: "/workspaces"
|
|
576
|
+
});
|
|
577
|
+
return response.data;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
|
|
511
581
|
// src/api.ts
|
|
512
582
|
var AirApi = class extends AirBase {
|
|
513
583
|
assets;
|
|
@@ -517,6 +587,7 @@ var AirApi = class extends AirBase {
|
|
|
517
587
|
roles;
|
|
518
588
|
imports;
|
|
519
589
|
uploads;
|
|
590
|
+
workspaces;
|
|
520
591
|
constructor(options = {}) {
|
|
521
592
|
super(options);
|
|
522
593
|
this.assets = new Assets(this);
|
|
@@ -526,6 +597,7 @@ var AirApi = class extends AirBase {
|
|
|
526
597
|
this.roles = new Roles(this);
|
|
527
598
|
this.imports = new Imports(this);
|
|
528
599
|
this.uploads = new Uploads(this);
|
|
600
|
+
this.workspaces = new Workspaces(this);
|
|
529
601
|
}
|
|
530
602
|
};
|
|
531
603
|
|
|
@@ -543,7 +615,10 @@ import {
|
|
|
543
615
|
TimeoutError,
|
|
544
616
|
CursorPage,
|
|
545
617
|
PagePromise,
|
|
546
|
-
VERSION
|
|
618
|
+
VERSION,
|
|
619
|
+
buildAuthorizationUrl,
|
|
620
|
+
exchangeAuthorizationCode,
|
|
621
|
+
generatePKCEChallenge
|
|
547
622
|
} from "@air/api-core";
|
|
548
623
|
export {
|
|
549
624
|
APIError,
|
|
@@ -566,5 +641,9 @@ export {
|
|
|
566
641
|
Tags,
|
|
567
642
|
TimeoutError,
|
|
568
643
|
Uploads,
|
|
569
|
-
VERSION
|
|
644
|
+
VERSION,
|
|
645
|
+
Workspaces,
|
|
646
|
+
buildAuthorizationUrl,
|
|
647
|
+
exchangeAuthorizationCode,
|
|
648
|
+
generatePKCEChallenge
|
|
570
649
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@air/api-rest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed REST client for Air API",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"build": "tsup",
|
|
27
27
|
"test": "vitest run",
|
|
28
28
|
"typecheck": "tsc -b",
|
|
29
|
-
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
29
|
+
"clean": "rm -rf dist .tsc-out tsconfig.tsbuildinfo"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@air/api-core": "0.
|
|
32
|
+
"@air/api-core": "0.2.0"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=18"
|