@twinfinity/twin 6.0.1 → 6.0.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/BuildInfo.js +3 -3
- package/dist/TwinClient.d.ts +227 -0
- package/dist/TwinClient.d.ts.map +1 -1
- package/dist/TwinClient.js +474 -28
- package/dist/TwinClient.js.map +1 -1
- package/dist/errors.d.ts +122 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +156 -0
- package/dist/errors.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/asset-attachment-api.d.ts +51 -0
- package/dist/types/asset-attachment-api.d.ts.map +1 -0
- package/dist/types/asset-attachment-api.js +8 -0
- package/dist/types/asset-attachment-api.js.map +1 -0
- package/dist/types/asset-types-api.d.ts +76 -0
- package/dist/types/asset-types-api.d.ts.map +1 -0
- package/dist/types/asset-types-api.js +5 -0
- package/dist/types/asset-types-api.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/twin-permissions-api.d.ts +20 -0
- package/dist/types/twin-permissions-api.d.ts.map +1 -0
- package/dist/types/twin-permissions-api.js +2 -0
- package/dist/types/twin-permissions-api.js.map +1 -0
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/AssetAttachmentConflictError.html +3 -0
- package/docs/classes/AssetAttachmentNotFoundError.html +2 -0
- package/docs/classes/AssetBatchError.html +1 -1
- package/docs/classes/AssetCatalogTooLargeError.html +16 -0
- package/docs/classes/AssetCategoryConflictError.html +3 -0
- package/docs/classes/AssetCategoryForbiddenError.html +2 -0
- package/docs/classes/AssetCategoryNotFoundError.html +2 -0
- package/docs/classes/AssetCategoryValidationError.html +3 -0
- package/docs/classes/AssetConflictError.html +1 -1
- package/docs/classes/AssetForbiddenError.html +1 -1
- package/docs/classes/AssetNotFoundError.html +1 -1
- package/docs/classes/AssetTypeConflictError.html +3 -0
- package/docs/classes/AssetTypeForbiddenError.html +2 -0
- package/docs/classes/AssetTypeNotFoundError.html +2 -0
- package/docs/classes/AssetTypeValidationError.html +3 -0
- package/docs/classes/AssetValidationError.html +1 -1
- package/docs/classes/ProcessorNotFoundError.html +1 -1
- package/docs/classes/TwinClient.html +154 -48
- package/docs/classes/TwinConflictError.html +1 -1
- package/docs/classes/TwinFileConflictError.html +1 -1
- package/docs/classes/TwinFileNotFoundError.html +1 -1
- package/docs/classes/TwinFileValidationError.html +1 -1
- package/docs/classes/TwinNotFoundError.html +1 -1
- package/docs/classes/TwinUnauthenticatedError.html +6 -0
- package/docs/classes/TwinValidationError.html +1 -1
- package/docs/functions/isAssetCatalogTooLargeErrorBody.html +6 -0
- package/docs/functions/readErrorBody.html +10 -0
- package/docs/hierarchy.html +1 -1
- package/docs/interfaces/ApiCreateUploadSessionResponse.html +7 -0
- package/docs/interfaces/AssetAttachmentPut.html +6 -0
- package/docs/interfaces/AssetCatalogCategory.html +7 -0
- package/docs/interfaces/AssetCatalogResponse.html +6 -0
- package/docs/interfaces/AssetCatalogTooLargeErrorBody.html +15 -0
- package/docs/interfaces/AssetCategoryDelete.html +2 -0
- package/docs/interfaces/AssetCategoryGet.html +14 -0
- package/docs/interfaces/AssetCategoryPut.html +7 -0
- package/docs/interfaces/AssetTypeDelete.html +2 -0
- package/docs/interfaces/AssetTypeGet.html +16 -0
- package/docs/interfaces/AssetTypePut.html +9 -0
- package/docs/interfaces/AttachmentGet.html +20 -0
- package/docs/interfaces/AttachmentPost.html +4 -0
- package/docs/interfaces/ITwinClient.html +155 -49
- package/docs/interfaces/MyPermissions.html +7 -0
- package/docs/interfaces/TwinErrorBody.html +2 -2
- package/docs/interfaces/TwinSpecification.html +1 -1
- package/docs/interfaces/UploadStatusGet.html +3 -0
- package/docs/modules.html +1 -1
- package/docs/types/TwinCapability.html +6 -0
- package/docs/types/TwinErrorCause.html +8 -0
- package/docs/types/TwinErrorItemKind.html +4 -0
- package/docs/types/UploadStatus.html +1 -0
- package/package.json +3 -3
- package/src/BuildInfo.ts +41 -41
- package/src/TwinClient.ts +2334 -1496
- package/src/errors.ts +379 -117
- package/src/index.ts +3 -3
- package/src/types/Twin.ts +7 -7
- package/src/types/TwinGeometryInfo.ts +7 -7
- package/src/types/asset-api.ts +60 -60
- package/src/types/asset-attachment-api.ts +56 -0
- package/src/types/asset-batch-api.ts +88 -88
- package/src/types/asset-types-api.ts +86 -0
- package/src/types/common.ts +10 -10
- package/src/types/generated.ts +574 -574
- package/src/types/index.ts +9 -6
- package/src/types/twin-api.ts +99 -99
- package/src/types/twin-file-api.ts +67 -67
- package/src/types/twin-permissions-api.ts +20 -0
package/dist/TwinClient.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { HttpMethod } from '@twinfinity/authentication';
|
|
2
2
|
import { readT8ClientModelWithFactories, readT8ClientModelWithGeometry, readT8GIModelWithFactories, T8EntityType, T8GIMeshDescriptor, T8GIStyle, T8GIRegion, T8Product, T8Project, T8Unit } from '@twinfinity/t8-client-model';
|
|
3
|
-
import { TwinValidationError, TwinConflictError, TwinNotFoundError, ProcessorNotFoundError, AssetValidationError, AssetForbiddenError, AssetConflictError, AssetNotFoundError, AssetBatchError, TwinFileValidationError, TwinFileConflictError, TwinFileNotFoundError } from './errors';
|
|
3
|
+
import { TwinValidationError, TwinConflictError, TwinNotFoundError, ProcessorNotFoundError, AssetValidationError, AssetForbiddenError, AssetConflictError, AssetNotFoundError, AssetAttachmentConflictError, AssetAttachmentNotFoundError, AssetBatchError, TwinFileValidationError, TwinFileConflictError, TwinFileNotFoundError, AssetCategoryValidationError, AssetCategoryConflictError, AssetCategoryNotFoundError, AssetCategoryForbiddenError, AssetTypeValidationError, AssetTypeConflictError, AssetTypeNotFoundError, AssetTypeForbiddenError, AssetCatalogTooLargeError, isAssetCatalogTooLargeErrorBody, TwinUnauthenticatedError, readErrorBody } from './errors';
|
|
4
4
|
export class TwinClient {
|
|
5
5
|
_httpClient;
|
|
6
6
|
_baseUrl;
|
|
7
|
+
_assetTypeCatalogUrl;
|
|
7
8
|
constructor(baseUrl, _httpClient) {
|
|
8
9
|
this._httpClient = _httpClient;
|
|
9
|
-
|
|
10
|
+
const base = String(baseUrl).replace(/\/+$/, '');
|
|
11
|
+
this._baseUrl = `${base}/_ps/twins`;
|
|
12
|
+
this._assetTypeCatalogUrl = `${base}/_ps/asset-type-catalog`;
|
|
10
13
|
}
|
|
11
14
|
async listTwins(relatedTo, page = undefined, limit = 100, signal) {
|
|
12
15
|
const params = new URLSearchParams();
|
|
@@ -18,14 +21,14 @@ export class TwinClient {
|
|
|
18
21
|
params.set('limit', limit.toString());
|
|
19
22
|
const url = params.toString() ? `${this._baseUrl}?${params}` : this._baseUrl;
|
|
20
23
|
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
21
|
-
headers: {
|
|
24
|
+
headers: { Accept: 'application/json' },
|
|
22
25
|
signal
|
|
23
26
|
});
|
|
24
27
|
if (response.ok) {
|
|
25
28
|
return (await response.json());
|
|
26
29
|
}
|
|
27
30
|
if (response.status === 400) {
|
|
28
|
-
const errorBody = await response
|
|
31
|
+
const errorBody = await readErrorBody(response);
|
|
29
32
|
throw new TwinValidationError(`Twin listing validation failed: ${errorBody.detail}`, errorBody);
|
|
30
33
|
}
|
|
31
34
|
const errorText = await response.text();
|
|
@@ -50,7 +53,7 @@ export class TwinClient {
|
|
|
50
53
|
const twinId = typeof twin === 'string' ? twin : twin.id;
|
|
51
54
|
const specifiedVersion = typeof twin === 'string' ? undefined : twin.version;
|
|
52
55
|
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}${specifiedVersion !== undefined ? `/versions/${specifiedVersion}` : ''}`, {
|
|
53
|
-
headers: {
|
|
56
|
+
headers: { Accept: 'application/json' },
|
|
54
57
|
signal
|
|
55
58
|
});
|
|
56
59
|
if (response.ok) {
|
|
@@ -72,14 +75,14 @@ export class TwinClient {
|
|
|
72
75
|
params.set('includeDeleted', 'true');
|
|
73
76
|
const url = `${this._baseUrl}/${id}/versions${params.toString() ? `?${params}` : ''}`;
|
|
74
77
|
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
75
|
-
headers: {
|
|
78
|
+
headers: { Accept: 'application/json' },
|
|
76
79
|
signal
|
|
77
80
|
});
|
|
78
81
|
if (response.ok) {
|
|
79
82
|
return (await response.json());
|
|
80
83
|
}
|
|
81
84
|
if (response.status === 400) {
|
|
82
|
-
const errorBody = await response
|
|
85
|
+
const errorBody = await readErrorBody(response);
|
|
83
86
|
throw new TwinValidationError(`Twin listing validation failed: ${errorBody.detail}`, errorBody);
|
|
84
87
|
}
|
|
85
88
|
const errorText = await response.text();
|
|
@@ -243,7 +246,7 @@ export class TwinClient {
|
|
|
243
246
|
return (await response.json());
|
|
244
247
|
}
|
|
245
248
|
if (response.status === 400) {
|
|
246
|
-
const errorBody = await response
|
|
249
|
+
const errorBody = await readErrorBody(response);
|
|
247
250
|
throw new TwinValidationError(`Twin validation failed: ${errorBody.detail}`, errorBody);
|
|
248
251
|
}
|
|
249
252
|
if (response.status === 409) {
|
|
@@ -295,7 +298,7 @@ export class TwinClient {
|
|
|
295
298
|
throw new Error(`Failed to restore twin: ${response.status} ${response.statusText} - ${errorText}`);
|
|
296
299
|
}
|
|
297
300
|
async getTwinVersionContent(twinId, twinVersion, signal) {
|
|
298
|
-
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/versions/${twinVersion}/contents`, { headers: {
|
|
301
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/versions/${twinVersion}/contents`, { headers: { Accept: 'application/json' }, signal });
|
|
299
302
|
if (response.ok) {
|
|
300
303
|
return (await response.json());
|
|
301
304
|
}
|
|
@@ -307,7 +310,7 @@ export class TwinClient {
|
|
|
307
310
|
}
|
|
308
311
|
async getTwinContent(twinId, signal) {
|
|
309
312
|
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/contents`, {
|
|
310
|
-
headers: {
|
|
313
|
+
headers: { Accept: 'application/json' },
|
|
311
314
|
signal
|
|
312
315
|
});
|
|
313
316
|
if (response.ok) {
|
|
@@ -321,7 +324,7 @@ export class TwinClient {
|
|
|
321
324
|
}
|
|
322
325
|
async getTwinDiagnostics(twinId, signal) {
|
|
323
326
|
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/diagnostics`, {
|
|
324
|
-
headers: {
|
|
327
|
+
headers: { Accept: 'application/json' },
|
|
325
328
|
signal
|
|
326
329
|
});
|
|
327
330
|
if (response.ok) {
|
|
@@ -334,7 +337,7 @@ export class TwinClient {
|
|
|
334
337
|
throw new Error(`Failed to get twin diagnostics: ${response.status} ${response.statusText} - ${errorText}`);
|
|
335
338
|
}
|
|
336
339
|
async getTwinVersionDiagnostics(twinId, twinVersion, signal) {
|
|
337
|
-
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/versions/${twinVersion}/diagnostics`, { headers: {
|
|
340
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/versions/${twinVersion}/diagnostics`, { headers: { Accept: 'application/json' }, signal });
|
|
338
341
|
if (response.ok) {
|
|
339
342
|
return (await response.json());
|
|
340
343
|
}
|
|
@@ -347,14 +350,14 @@ export class TwinClient {
|
|
|
347
350
|
async getTwinVersionEvents(twinId, twinVersion, offset = 0, limit = 100, signal) {
|
|
348
351
|
const url = `${this._baseUrl}/${twinId}/versions/${twinVersion}/events?offset=${offset}&limit=${limit}`;
|
|
349
352
|
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
350
|
-
headers: {
|
|
353
|
+
headers: { Accept: 'application/json' },
|
|
351
354
|
signal
|
|
352
355
|
});
|
|
353
356
|
if (response.ok) {
|
|
354
357
|
return (await response.json());
|
|
355
358
|
}
|
|
356
359
|
if (response.status === 400) {
|
|
357
|
-
const errorBody = await response
|
|
360
|
+
const errorBody = await readErrorBody(response);
|
|
358
361
|
throw new TwinValidationError(`Twin events validation failed: ${errorBody.detail}`, errorBody);
|
|
359
362
|
}
|
|
360
363
|
if (response.status === 404) {
|
|
@@ -366,14 +369,14 @@ export class TwinClient {
|
|
|
366
369
|
async getProcessorEvents(twinId, twinVersion, processorId, offset = 0, limit = 100, signal) {
|
|
367
370
|
const url = `${this._baseUrl}/${twinId}/versions/${twinVersion}/processors/${processorId}/events?offset=${offset}&limit=${limit}`;
|
|
368
371
|
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
369
|
-
headers: {
|
|
372
|
+
headers: { Accept: 'application/json' },
|
|
370
373
|
signal
|
|
371
374
|
});
|
|
372
375
|
if (response.ok) {
|
|
373
376
|
return (await response.json());
|
|
374
377
|
}
|
|
375
378
|
if (response.status === 400) {
|
|
376
|
-
const errorBody = await response
|
|
379
|
+
const errorBody = await readErrorBody(response);
|
|
377
380
|
throw new TwinValidationError(`Processor events validation failed: ${errorBody.detail}`, errorBody);
|
|
378
381
|
}
|
|
379
382
|
if (response.status === 404) {
|
|
@@ -386,14 +389,14 @@ export class TwinClient {
|
|
|
386
389
|
const includeDeleted = options?.includeDeleted ?? false;
|
|
387
390
|
const params = `offset=${offset}&limit=${limit}${includeDeleted ? '&includeDeleted=true' : ''}`;
|
|
388
391
|
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/assets?${params}`, {
|
|
389
|
-
headers: {
|
|
392
|
+
headers: { Accept: 'application/json' },
|
|
390
393
|
signal
|
|
391
394
|
});
|
|
392
395
|
if (response.ok) {
|
|
393
396
|
return (await response.json());
|
|
394
397
|
}
|
|
395
398
|
if (response.status === 400) {
|
|
396
|
-
const errorBody = await response
|
|
399
|
+
const errorBody = await readErrorBody(response);
|
|
397
400
|
throw new AssetValidationError(`Asset listing validation failed: ${errorBody.detail}`, errorBody);
|
|
398
401
|
}
|
|
399
402
|
if (response.status === 404) {
|
|
@@ -404,7 +407,7 @@ export class TwinClient {
|
|
|
404
407
|
}
|
|
405
408
|
async getAsset(twinId, assetId, signal) {
|
|
406
409
|
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/assets/${assetId}`, {
|
|
407
|
-
headers: {
|
|
410
|
+
headers: { Accept: 'application/json' },
|
|
408
411
|
signal
|
|
409
412
|
});
|
|
410
413
|
if (response.ok) {
|
|
@@ -426,7 +429,7 @@ export class TwinClient {
|
|
|
426
429
|
return (await response.json());
|
|
427
430
|
}
|
|
428
431
|
if (response.status === 400) {
|
|
429
|
-
const errorBody = await response
|
|
432
|
+
const errorBody = await readErrorBody(response);
|
|
430
433
|
throw new AssetValidationError(`Asset validation failed: ${errorBody.detail}`, errorBody);
|
|
431
434
|
}
|
|
432
435
|
const errorText = await response.text();
|
|
@@ -446,7 +449,7 @@ export class TwinClient {
|
|
|
446
449
|
throw new AssetConflictError('Asset has been modified by another user', conflictedAsset);
|
|
447
450
|
}
|
|
448
451
|
if (response.status === 400) {
|
|
449
|
-
const errorBody = await response
|
|
452
|
+
const errorBody = await readErrorBody(response);
|
|
450
453
|
throw new AssetValidationError(`Asset validation failed: ${errorBody.detail}`, errorBody);
|
|
451
454
|
}
|
|
452
455
|
const errorText = await response.text();
|
|
@@ -499,7 +502,7 @@ export class TwinClient {
|
|
|
499
502
|
return (await response.json());
|
|
500
503
|
}
|
|
501
504
|
if (response.status === 400) {
|
|
502
|
-
const errorBody = await response
|
|
505
|
+
const errorBody = await readErrorBody(response);
|
|
503
506
|
throw new AssetValidationError(`Asset batch validation failed: ${errorBody.detail}`, errorBody);
|
|
504
507
|
}
|
|
505
508
|
if (response.status === 403) {
|
|
@@ -523,12 +526,163 @@ export class TwinClient {
|
|
|
523
526
|
return (await response.json());
|
|
524
527
|
}
|
|
525
528
|
if (response.status === 400) {
|
|
526
|
-
const errorBody = await response
|
|
529
|
+
const errorBody = await readErrorBody(response);
|
|
527
530
|
throw new AssetValidationError(`Asset batch-get validation failed: ${errorBody.detail}`, errorBody);
|
|
528
531
|
}
|
|
529
532
|
const errorText = await response.text();
|
|
530
533
|
throw new Error(`Failed to batch-get assets: ${response.status} ${response.statusText} - ${errorText}`);
|
|
531
534
|
}
|
|
535
|
+
// ────────── Asset attachments ──────────
|
|
536
|
+
async listAssetAttachments(twinId, assetId, options, signal) {
|
|
537
|
+
const params = new URLSearchParams();
|
|
538
|
+
if (options?.includeDeleted)
|
|
539
|
+
params.set('includeDeleted', 'true');
|
|
540
|
+
if (options?.offset !== undefined)
|
|
541
|
+
params.set('offset', String(options.offset));
|
|
542
|
+
if (options?.limit !== undefined)
|
|
543
|
+
params.set('limit', String(options.limit));
|
|
544
|
+
if (options?.page)
|
|
545
|
+
params.set('page', options.page);
|
|
546
|
+
const url = `${this._baseUrl}/${twinId}/assets/${assetId}/attachments${params.toString() ? `?${params}` : ''}`;
|
|
547
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
548
|
+
headers: { Accept: 'application/json' },
|
|
549
|
+
signal
|
|
550
|
+
});
|
|
551
|
+
if (response.ok)
|
|
552
|
+
return (await response.json());
|
|
553
|
+
if (response.status === 404)
|
|
554
|
+
throw new AssetNotFoundError(`Asset ${assetId} not found`);
|
|
555
|
+
if (response.status === 400) {
|
|
556
|
+
const errorBody = await readErrorBody(response);
|
|
557
|
+
throw new AssetValidationError(`Attachment listing failed: ${errorBody.detail}`, errorBody);
|
|
558
|
+
}
|
|
559
|
+
const errorText = await response.text();
|
|
560
|
+
throw new Error(`Failed to list attachments: ${response.status} ${response.statusText} - ${errorText}`);
|
|
561
|
+
}
|
|
562
|
+
async getAssetAttachmentBlob(twinId, assetId, attachmentId, signal) {
|
|
563
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}`, { signal });
|
|
564
|
+
if (response.ok)
|
|
565
|
+
return response;
|
|
566
|
+
if (response.status === 404)
|
|
567
|
+
throw new AssetAttachmentNotFoundError(`Attachment ${attachmentId} not found`);
|
|
568
|
+
const errorText = await response.text();
|
|
569
|
+
throw new Error(`Failed to fetch attachment blob: ${response.status} ${response.statusText} - ${errorText}`);
|
|
570
|
+
}
|
|
571
|
+
async createAssetAttachmentUploadSession(twinId, assetId, attachmentId, body, signal) {
|
|
572
|
+
const response = await this._httpClient.fetch(HttpMethod.Post, `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}/createAttachmentUploadSession`, {
|
|
573
|
+
headers: { 'Content-Type': 'application/json' },
|
|
574
|
+
body: JSON.stringify(body),
|
|
575
|
+
signal
|
|
576
|
+
});
|
|
577
|
+
if (response.ok)
|
|
578
|
+
return (await response.json());
|
|
579
|
+
if (response.status === 409) {
|
|
580
|
+
const conflicted = (await response.json());
|
|
581
|
+
throw new AssetAttachmentConflictError(`Attachment ${attachmentId} already exists; pick a new id for retries`, conflicted);
|
|
582
|
+
}
|
|
583
|
+
if (response.status === 404)
|
|
584
|
+
throw new AssetNotFoundError(`Asset ${assetId} not found`);
|
|
585
|
+
if (response.status === 400) {
|
|
586
|
+
const errorBody = await readErrorBody(response);
|
|
587
|
+
throw new AssetValidationError(`Upload session creation failed: ${errorBody.detail}`, errorBody);
|
|
588
|
+
}
|
|
589
|
+
const errorText = await response.text();
|
|
590
|
+
throw new Error(`Failed to create upload session: ${response.status} ${response.statusText} - ${errorText}`);
|
|
591
|
+
}
|
|
592
|
+
async uploadAssetAttachmentFile(session, file, signal) {
|
|
593
|
+
// The token is encoded as a query param on the upload URL; we lift it
|
|
594
|
+
// out for the caller so they can poll uploadStatus without re-parsing.
|
|
595
|
+
const tokenMatch = session.uploadUrl.match(/token=([^&]+)/);
|
|
596
|
+
if (!tokenMatch) {
|
|
597
|
+
throw new Error(`Upload session URL is missing a token query parameter: ${session.uploadUrl}`);
|
|
598
|
+
}
|
|
599
|
+
const token = tokenMatch[1];
|
|
600
|
+
const chunkSize = Math.max(1, session.maxChunkSize || file.size);
|
|
601
|
+
const total = file.size;
|
|
602
|
+
let offset = 0;
|
|
603
|
+
// Even an empty file needs at least one PUT so the upload session
|
|
604
|
+
// gets a finalize signal. uploadChunks() in the integration tests
|
|
605
|
+
// unconditionally PUTs at least one chunk too.
|
|
606
|
+
do {
|
|
607
|
+
const end = Math.min(offset + chunkSize, total);
|
|
608
|
+
const chunk = file.slice(offset, end);
|
|
609
|
+
const lastByte = Math.max(0, end - 1);
|
|
610
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, session.uploadUrl, {
|
|
611
|
+
headers: { 'Content-Range': `bytes ${offset}-${lastByte}/${total}` },
|
|
612
|
+
body: chunk,
|
|
613
|
+
signal
|
|
614
|
+
});
|
|
615
|
+
if (!response.ok) {
|
|
616
|
+
const errorText = await response.text();
|
|
617
|
+
throw new Error(`Upload chunk failed at offset ${offset}: ${response.status} ${response.statusText} - ${errorText}`);
|
|
618
|
+
}
|
|
619
|
+
offset = end;
|
|
620
|
+
} while (offset < total);
|
|
621
|
+
return { token };
|
|
622
|
+
}
|
|
623
|
+
async getAssetAttachmentUploadStatus(twinId, assetId, token, signal) {
|
|
624
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/uploadStatus/${encodeURIComponent(token)}`, { headers: { Accept: 'application/json' }, signal });
|
|
625
|
+
if (response.ok)
|
|
626
|
+
return (await response.json());
|
|
627
|
+
if (response.status === 404)
|
|
628
|
+
return { status: 'NotFound' };
|
|
629
|
+
const errorText = await response.text();
|
|
630
|
+
throw new Error(`Failed to get upload status: ${response.status} ${response.statusText} - ${errorText}`);
|
|
631
|
+
}
|
|
632
|
+
async putAssetAttachment(twinId, assetId, attachmentId, body, signal) {
|
|
633
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}`, {
|
|
634
|
+
headers: { 'Content-Type': 'application/json' },
|
|
635
|
+
body: JSON.stringify(body),
|
|
636
|
+
signal
|
|
637
|
+
});
|
|
638
|
+
if (response.ok)
|
|
639
|
+
return (await response.json());
|
|
640
|
+
if (response.status === 409) {
|
|
641
|
+
const conflicted = (await response.json());
|
|
642
|
+
throw new AssetAttachmentConflictError('Attachment has been modified by another user', conflicted);
|
|
643
|
+
}
|
|
644
|
+
if (response.status === 404)
|
|
645
|
+
throw new AssetAttachmentNotFoundError(`Attachment ${attachmentId} not found`);
|
|
646
|
+
if (response.status === 400) {
|
|
647
|
+
const errorBody = await readErrorBody(response);
|
|
648
|
+
throw new AssetValidationError(`Attachment update failed: ${errorBody.detail}`, errorBody);
|
|
649
|
+
}
|
|
650
|
+
const errorText = await response.text();
|
|
651
|
+
throw new Error(`Failed to update attachment: ${response.status} ${response.statusText} - ${errorText}`);
|
|
652
|
+
}
|
|
653
|
+
async deleteAssetAttachment(twinId, assetId, attachmentId, etag, signal) {
|
|
654
|
+
const response = await this._httpClient.fetch(HttpMethod.Delete, `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}`, {
|
|
655
|
+
headers: { 'If-Match': etag },
|
|
656
|
+
signal
|
|
657
|
+
});
|
|
658
|
+
if (response.ok)
|
|
659
|
+
return (await response.json());
|
|
660
|
+
if (response.status === 409) {
|
|
661
|
+
const conflicted = (await response.json());
|
|
662
|
+
throw new AssetAttachmentConflictError('Attachment has been modified by another user', conflicted);
|
|
663
|
+
}
|
|
664
|
+
if (response.status === 404)
|
|
665
|
+
throw new AssetAttachmentNotFoundError(`Attachment ${attachmentId} not found`);
|
|
666
|
+
const errorText = await response.text();
|
|
667
|
+
throw new Error(`Failed to delete attachment: ${response.status} ${response.statusText} - ${errorText}`);
|
|
668
|
+
}
|
|
669
|
+
async restoreAssetAttachment(twinId, assetId, attachmentId, etag, signal) {
|
|
670
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}/restore`, {
|
|
671
|
+
headers: { 'Content-Type': 'application/json' },
|
|
672
|
+
body: JSON.stringify({ etag }),
|
|
673
|
+
signal
|
|
674
|
+
});
|
|
675
|
+
if (response.ok)
|
|
676
|
+
return (await response.json());
|
|
677
|
+
if (response.status === 409) {
|
|
678
|
+
const conflicted = (await response.json());
|
|
679
|
+
throw new AssetAttachmentConflictError('Attachment has been modified by another user', conflicted);
|
|
680
|
+
}
|
|
681
|
+
if (response.status === 404)
|
|
682
|
+
throw new AssetAttachmentNotFoundError(`Attachment ${attachmentId} not found`);
|
|
683
|
+
const errorText = await response.text();
|
|
684
|
+
throw new Error(`Failed to restore attachment: ${response.status} ${response.statusText} - ${errorText}`);
|
|
685
|
+
}
|
|
532
686
|
async listTwinFiles(twinId, options = {}, signal) {
|
|
533
687
|
const { includeDeleted = false, checkedIn = false, version, page = undefined, limit = 100 } = options;
|
|
534
688
|
if (version !== undefined && !checkedIn) {
|
|
@@ -549,14 +703,14 @@ export class TwinClient {
|
|
|
549
703
|
? `${this._baseUrl}/${twinId}/files?${params}`
|
|
550
704
|
: `${this._baseUrl}/${twinId}/files`;
|
|
551
705
|
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
552
|
-
headers: {
|
|
706
|
+
headers: { Accept: 'application/json' },
|
|
553
707
|
signal
|
|
554
708
|
});
|
|
555
709
|
if (response.ok) {
|
|
556
710
|
return (await response.json());
|
|
557
711
|
}
|
|
558
712
|
if (response.status === 400) {
|
|
559
|
-
const errorBody = await response
|
|
713
|
+
const errorBody = await readErrorBody(response);
|
|
560
714
|
throw new TwinFileValidationError(`Twin file listing validation failed: ${errorBody.detail}`, errorBody);
|
|
561
715
|
}
|
|
562
716
|
const errorText = await response.text();
|
|
@@ -564,7 +718,7 @@ export class TwinClient {
|
|
|
564
718
|
}
|
|
565
719
|
async getTwinFile(twinId, fileId, signal) {
|
|
566
720
|
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/files/${fileId}`, {
|
|
567
|
-
headers: {
|
|
721
|
+
headers: { Accept: 'application/json' },
|
|
568
722
|
signal
|
|
569
723
|
});
|
|
570
724
|
if (response.ok) {
|
|
@@ -590,7 +744,7 @@ export class TwinClient {
|
|
|
590
744
|
return (await response.json());
|
|
591
745
|
}
|
|
592
746
|
if (response.status === 400) {
|
|
593
|
-
const errorBody = await response
|
|
747
|
+
const errorBody = await readErrorBody(response);
|
|
594
748
|
throw new TwinFileValidationError(`Twin file validation failed`, errorBody);
|
|
595
749
|
}
|
|
596
750
|
if (response.status === 409) {
|
|
@@ -681,7 +835,7 @@ export class TwinClient {
|
|
|
681
835
|
return (await response.json());
|
|
682
836
|
}
|
|
683
837
|
if (response.status === 400) {
|
|
684
|
-
const errorBody = await response
|
|
838
|
+
const errorBody = await readErrorBody(response);
|
|
685
839
|
throw new TwinFileValidationError(`Twin file version listing validation failed: ${errorBody.detail}`, errorBody);
|
|
686
840
|
}
|
|
687
841
|
const errorText = await response.text();
|
|
@@ -712,5 +866,297 @@ export class TwinClient {
|
|
|
712
866
|
signal
|
|
713
867
|
});
|
|
714
868
|
}
|
|
869
|
+
// ---------- Asset categories ----------
|
|
870
|
+
async getMyPermissions(signal) {
|
|
871
|
+
const url = `${this._baseUrl}/permissions/me`;
|
|
872
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
873
|
+
headers: { Accept: 'application/json' },
|
|
874
|
+
signal
|
|
875
|
+
});
|
|
876
|
+
if (response.ok) {
|
|
877
|
+
return (await response.json());
|
|
878
|
+
}
|
|
879
|
+
// 401: no valid auth context. Throw so consumers can redirect to
|
|
880
|
+
// login / refresh tokens rather than mistake an expired session for
|
|
881
|
+
// "logged-in user with no grants."
|
|
882
|
+
if (response.status === 401) {
|
|
883
|
+
throw new TwinUnauthenticatedError('Not authenticated');
|
|
884
|
+
}
|
|
885
|
+
// 403: authenticated but no Twin-service capabilities. Surface as an
|
|
886
|
+
// empty array so consumers can treat it like a logged-in user with
|
|
887
|
+
// zero grants.
|
|
888
|
+
if (response.status === 403) {
|
|
889
|
+
return { permissions: [] };
|
|
890
|
+
}
|
|
891
|
+
throw new Error(`Failed to fetch permissions: ${response.status} ${response.statusText}`);
|
|
892
|
+
}
|
|
893
|
+
async getAssetTypeMetaSchema(signal) {
|
|
894
|
+
const url = `${this._assetTypeCatalogUrl}/meta-schema`;
|
|
895
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
896
|
+
headers: { Accept: 'application/schema+json, application/json' },
|
|
897
|
+
signal
|
|
898
|
+
});
|
|
899
|
+
if (!response.ok) {
|
|
900
|
+
throw new Error(`Failed to fetch meta-schema: ${response.status} ${response.statusText}`);
|
|
901
|
+
}
|
|
902
|
+
return (await response.json());
|
|
903
|
+
}
|
|
904
|
+
async listAssetCategories(options, signal) {
|
|
905
|
+
const params = new URLSearchParams();
|
|
906
|
+
if (options?.page !== undefined)
|
|
907
|
+
params.set('page', options.page);
|
|
908
|
+
if (options?.limit !== undefined)
|
|
909
|
+
params.set('limit', options.limit.toString());
|
|
910
|
+
if (options?.includeDeleted)
|
|
911
|
+
params.set('includeDeleted', 'true');
|
|
912
|
+
const url = params.toString()
|
|
913
|
+
? `${this._assetTypeCatalogUrl}/categories?${params}`
|
|
914
|
+
: `${this._assetTypeCatalogUrl}/categories`;
|
|
915
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
916
|
+
headers: { Accept: 'application/json' },
|
|
917
|
+
signal
|
|
918
|
+
});
|
|
919
|
+
if (response.ok)
|
|
920
|
+
return (await response.json());
|
|
921
|
+
if (response.status === 403)
|
|
922
|
+
throw new AssetCategoryForbiddenError('Asset category listing forbidden');
|
|
923
|
+
if (response.status === 400) {
|
|
924
|
+
const errorBody = await readErrorBody(response);
|
|
925
|
+
throw new AssetCategoryValidationError(`Asset category listing failed: ${errorBody.detail}`, errorBody);
|
|
926
|
+
}
|
|
927
|
+
throw new Error(`Failed to list asset categories: ${response.status} ${response.statusText}`);
|
|
928
|
+
}
|
|
929
|
+
async getAssetCategory(categoryId, signal) {
|
|
930
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._assetTypeCatalogUrl}/categories/${categoryId}`, {
|
|
931
|
+
headers: { Accept: 'application/json' },
|
|
932
|
+
signal
|
|
933
|
+
});
|
|
934
|
+
if (response.ok)
|
|
935
|
+
return (await response.json());
|
|
936
|
+
if (response.status === 403)
|
|
937
|
+
throw new AssetCategoryForbiddenError(`Asset category ${categoryId} read forbidden`);
|
|
938
|
+
if (response.status === 404)
|
|
939
|
+
throw new AssetCategoryNotFoundError(`Asset category ${categoryId} not found`);
|
|
940
|
+
throw new Error(`Failed to get asset category: ${response.status} ${response.statusText}`);
|
|
941
|
+
}
|
|
942
|
+
async putAssetCategory(categoryId, body, signal) {
|
|
943
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, `${this._assetTypeCatalogUrl}/categories/${categoryId}`, {
|
|
944
|
+
headers: { 'Content-Type': 'application/json' },
|
|
945
|
+
body: JSON.stringify(body),
|
|
946
|
+
signal
|
|
947
|
+
});
|
|
948
|
+
if (response.ok)
|
|
949
|
+
return (await response.json());
|
|
950
|
+
if (response.status === 403)
|
|
951
|
+
throw new AssetCategoryForbiddenError('Asset category upsert forbidden');
|
|
952
|
+
if (response.status === 409) {
|
|
953
|
+
const current = (await response.json());
|
|
954
|
+
throw new AssetCategoryConflictError('Asset category was modified by another user', current);
|
|
955
|
+
}
|
|
956
|
+
if (response.status === 400) {
|
|
957
|
+
const errorBody = await readErrorBody(response);
|
|
958
|
+
throw new AssetCategoryValidationError(`Asset category validation failed: ${errorBody.detail}`, errorBody);
|
|
959
|
+
}
|
|
960
|
+
const errorText = await response.text();
|
|
961
|
+
throw new Error(`Failed to upsert asset category: ${response.status} ${response.statusText} - ${errorText}`);
|
|
962
|
+
}
|
|
963
|
+
async deleteAssetCategory(categoryId, etag, signal) {
|
|
964
|
+
return this._categoryDeleteOrRestore(categoryId, etag, /*isRestore*/ false, signal);
|
|
965
|
+
}
|
|
966
|
+
async restoreAssetCategory(categoryId, etag, signal) {
|
|
967
|
+
return this._categoryDeleteOrRestore(categoryId, etag, /*isRestore*/ true, signal);
|
|
968
|
+
}
|
|
969
|
+
async _categoryDeleteOrRestore(categoryId, etag, isRestore, signal) {
|
|
970
|
+
const url = isRestore
|
|
971
|
+
? `${this._assetTypeCatalogUrl}/categories/${categoryId}/restore`
|
|
972
|
+
: `${this._assetTypeCatalogUrl}/categories/${categoryId}`;
|
|
973
|
+
const method = isRestore ? HttpMethod.Put : HttpMethod.Delete;
|
|
974
|
+
const response = await this._httpClient.fetch(method, url, {
|
|
975
|
+
headers: { 'Content-Type': 'application/json' },
|
|
976
|
+
body: JSON.stringify({ etag }),
|
|
977
|
+
signal
|
|
978
|
+
});
|
|
979
|
+
if (response.ok)
|
|
980
|
+
return (await response.json());
|
|
981
|
+
if (response.status === 403) {
|
|
982
|
+
const op = isRestore ? 'restore' : 'delete';
|
|
983
|
+
throw new AssetCategoryForbiddenError(`Asset category ${op} forbidden`);
|
|
984
|
+
}
|
|
985
|
+
if (response.status === 409) {
|
|
986
|
+
const current = (await response.json());
|
|
987
|
+
throw new AssetCategoryConflictError('Asset category was modified by another user', current);
|
|
988
|
+
}
|
|
989
|
+
if (response.status === 404)
|
|
990
|
+
throw new AssetCategoryNotFoundError(`Asset category ${categoryId} not found`);
|
|
991
|
+
const errorText = await response.text();
|
|
992
|
+
const op = isRestore ? 'restore' : 'delete';
|
|
993
|
+
throw new Error(`Failed to ${op} asset category: ${response.status} ${response.statusText} - ${errorText}`);
|
|
994
|
+
}
|
|
995
|
+
async listAssetCategoryVersions(categoryId, options, signal) {
|
|
996
|
+
const params = new URLSearchParams();
|
|
997
|
+
if (options?.page !== undefined)
|
|
998
|
+
params.set('page', options.page);
|
|
999
|
+
if (options?.limit !== undefined)
|
|
1000
|
+
params.set('limit', options.limit.toString());
|
|
1001
|
+
if (options?.includeDeleted)
|
|
1002
|
+
params.set('includeDeleted', 'true');
|
|
1003
|
+
const baseUrl = `${this._assetTypeCatalogUrl}/categories/${categoryId}/versions`;
|
|
1004
|
+
const url = params.toString() ? `${baseUrl}?${params}` : baseUrl;
|
|
1005
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
1006
|
+
headers: { Accept: 'application/json' },
|
|
1007
|
+
signal
|
|
1008
|
+
});
|
|
1009
|
+
if (response.ok)
|
|
1010
|
+
return (await response.json());
|
|
1011
|
+
if (response.status === 403)
|
|
1012
|
+
throw new AssetCategoryForbiddenError('Asset category version listing forbidden');
|
|
1013
|
+
if (response.status === 400) {
|
|
1014
|
+
const errorBody = await readErrorBody(response);
|
|
1015
|
+
throw new AssetCategoryValidationError(`Asset category version listing failed: ${errorBody.detail}`, errorBody);
|
|
1016
|
+
}
|
|
1017
|
+
if (response.status === 404)
|
|
1018
|
+
throw new AssetCategoryNotFoundError(`Asset category ${categoryId} not found`);
|
|
1019
|
+
throw new Error(`Failed to list asset category versions: ${response.status} ${response.statusText}`);
|
|
1020
|
+
}
|
|
1021
|
+
// ---------- Asset types ----------
|
|
1022
|
+
async listAssetTypes(categoryId, options, signal) {
|
|
1023
|
+
const params = new URLSearchParams();
|
|
1024
|
+
if (options?.page !== undefined)
|
|
1025
|
+
params.set('page', options.page);
|
|
1026
|
+
if (options?.limit !== undefined)
|
|
1027
|
+
params.set('limit', options.limit.toString());
|
|
1028
|
+
if (options?.includeDeleted)
|
|
1029
|
+
params.set('includeDeleted', 'true');
|
|
1030
|
+
const baseUrl = `${this._assetTypeCatalogUrl}/categories/${categoryId}/types`;
|
|
1031
|
+
const url = params.toString() ? `${baseUrl}?${params}` : baseUrl;
|
|
1032
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
1033
|
+
headers: { Accept: 'application/json' },
|
|
1034
|
+
signal
|
|
1035
|
+
});
|
|
1036
|
+
if (response.ok)
|
|
1037
|
+
return (await response.json());
|
|
1038
|
+
if (response.status === 403)
|
|
1039
|
+
throw new AssetTypeForbiddenError('Asset type listing forbidden');
|
|
1040
|
+
if (response.status === 400) {
|
|
1041
|
+
const errorBody = await readErrorBody(response);
|
|
1042
|
+
throw new AssetTypeValidationError(`Asset type listing failed: ${errorBody.detail}`, errorBody);
|
|
1043
|
+
}
|
|
1044
|
+
throw new Error(`Failed to list asset types: ${response.status} ${response.statusText}`);
|
|
1045
|
+
}
|
|
1046
|
+
async getAssetType(categoryId, typeId, signal) {
|
|
1047
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._assetTypeCatalogUrl}/categories/${categoryId}/types/${typeId}`, { headers: { Accept: 'application/json' }, signal });
|
|
1048
|
+
if (response.ok)
|
|
1049
|
+
return (await response.json());
|
|
1050
|
+
if (response.status === 403)
|
|
1051
|
+
throw new AssetTypeForbiddenError(`Asset type ${typeId} read forbidden`);
|
|
1052
|
+
if (response.status === 404)
|
|
1053
|
+
throw new AssetTypeNotFoundError(`Asset type ${typeId} not found`);
|
|
1054
|
+
throw new Error(`Failed to get asset type: ${response.status} ${response.statusText}`);
|
|
1055
|
+
}
|
|
1056
|
+
async listAssetTypeVersions(categoryId, typeId, options, signal) {
|
|
1057
|
+
const params = new URLSearchParams();
|
|
1058
|
+
if (options?.page !== undefined)
|
|
1059
|
+
params.set('page', options.page);
|
|
1060
|
+
if (options?.limit !== undefined)
|
|
1061
|
+
params.set('limit', options.limit.toString());
|
|
1062
|
+
if (options?.includeDeleted)
|
|
1063
|
+
params.set('includeDeleted', 'true');
|
|
1064
|
+
const baseUrl = `${this._assetTypeCatalogUrl}/categories/${categoryId}/types/${typeId}/versions`;
|
|
1065
|
+
const url = params.toString() ? `${baseUrl}?${params}` : baseUrl;
|
|
1066
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
1067
|
+
headers: { Accept: 'application/json' },
|
|
1068
|
+
signal
|
|
1069
|
+
});
|
|
1070
|
+
if (response.ok)
|
|
1071
|
+
return (await response.json());
|
|
1072
|
+
if (response.status === 403)
|
|
1073
|
+
throw new AssetTypeForbiddenError('Asset type version listing forbidden');
|
|
1074
|
+
if (response.status === 400) {
|
|
1075
|
+
const errorBody = await readErrorBody(response);
|
|
1076
|
+
throw new AssetTypeValidationError(`Asset type version listing failed: ${errorBody.detail}`, errorBody);
|
|
1077
|
+
}
|
|
1078
|
+
if (response.status === 404)
|
|
1079
|
+
throw new AssetTypeNotFoundError(`Asset type ${typeId} not found`);
|
|
1080
|
+
throw new Error(`Failed to list asset type versions: ${response.status} ${response.statusText}`);
|
|
1081
|
+
}
|
|
1082
|
+
async putAssetType(categoryId, typeId, body, signal) {
|
|
1083
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, `${this._assetTypeCatalogUrl}/categories/${categoryId}/types/${typeId}`, {
|
|
1084
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1085
|
+
body: JSON.stringify(body),
|
|
1086
|
+
signal
|
|
1087
|
+
});
|
|
1088
|
+
if (response.ok)
|
|
1089
|
+
return (await response.json());
|
|
1090
|
+
if (response.status === 403)
|
|
1091
|
+
throw new AssetTypeForbiddenError('Asset type upsert forbidden');
|
|
1092
|
+
if (response.status === 409) {
|
|
1093
|
+
const current = (await response.json());
|
|
1094
|
+
throw new AssetTypeConflictError('Asset type was modified by another user', current);
|
|
1095
|
+
}
|
|
1096
|
+
if (response.status === 400) {
|
|
1097
|
+
const errorBody = await readErrorBody(response);
|
|
1098
|
+
throw new AssetTypeValidationError(`Asset type validation failed: ${errorBody.detail}`, errorBody);
|
|
1099
|
+
}
|
|
1100
|
+
const errorText = await response.text();
|
|
1101
|
+
throw new Error(`Failed to upsert asset type: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1102
|
+
}
|
|
1103
|
+
async deleteAssetType(categoryId, typeId, etag, signal) {
|
|
1104
|
+
return this._typeDeleteOrRestore(categoryId, typeId, etag, /*isRestore*/ false, signal);
|
|
1105
|
+
}
|
|
1106
|
+
async restoreAssetType(categoryId, typeId, etag, signal) {
|
|
1107
|
+
return this._typeDeleteOrRestore(categoryId, typeId, etag, /*isRestore*/ true, signal);
|
|
1108
|
+
}
|
|
1109
|
+
async _typeDeleteOrRestore(categoryId, typeId, etag, isRestore, signal) {
|
|
1110
|
+
const url = isRestore
|
|
1111
|
+
? `${this._assetTypeCatalogUrl}/categories/${categoryId}/types/${typeId}/restore`
|
|
1112
|
+
: `${this._assetTypeCatalogUrl}/categories/${categoryId}/types/${typeId}`;
|
|
1113
|
+
const method = isRestore ? HttpMethod.Put : HttpMethod.Delete;
|
|
1114
|
+
const response = await this._httpClient.fetch(method, url, {
|
|
1115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1116
|
+
body: JSON.stringify({ etag }),
|
|
1117
|
+
signal
|
|
1118
|
+
});
|
|
1119
|
+
if (response.ok)
|
|
1120
|
+
return (await response.json());
|
|
1121
|
+
if (response.status === 403) {
|
|
1122
|
+
const op = isRestore ? 'restore' : 'delete';
|
|
1123
|
+
throw new AssetTypeForbiddenError(`Asset type ${op} forbidden`);
|
|
1124
|
+
}
|
|
1125
|
+
if (response.status === 409) {
|
|
1126
|
+
const current = (await response.json());
|
|
1127
|
+
throw new AssetTypeConflictError('Asset type was modified by another user', current);
|
|
1128
|
+
}
|
|
1129
|
+
if (response.status === 404)
|
|
1130
|
+
throw new AssetTypeNotFoundError(`Asset type ${typeId} not found`);
|
|
1131
|
+
const errorText = await response.text();
|
|
1132
|
+
const op = isRestore ? 'restore' : 'delete';
|
|
1133
|
+
throw new Error(`Failed to ${op} asset type: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1134
|
+
}
|
|
1135
|
+
async getAssetTypeCatalog(signal) {
|
|
1136
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, this._assetTypeCatalogUrl, {
|
|
1137
|
+
headers: { Accept: 'application/json' },
|
|
1138
|
+
signal
|
|
1139
|
+
});
|
|
1140
|
+
if (response.ok)
|
|
1141
|
+
return (await response.json());
|
|
1142
|
+
if (response.status === 403)
|
|
1143
|
+
throw new AssetCategoryForbiddenError('Asset type catalog read forbidden');
|
|
1144
|
+
if (response.status === 400) {
|
|
1145
|
+
// The server emits a single structured 400 here today —
|
|
1146
|
+
// `cause: 'CatalogTooLarge'` — to flag a tenant whose catalog
|
|
1147
|
+
// overflows the per-customer cap. Surface as a typed error so
|
|
1148
|
+
// picker UIs can render "use search" instead of crashing on
|
|
1149
|
+
// missing rows. Any other 400 (malformed CatalogTooLarge body,
|
|
1150
|
+
// future cause) falls through to the generic error below so the
|
|
1151
|
+
// caller still sees the raw response.
|
|
1152
|
+
const errorBody = await readErrorBody(response);
|
|
1153
|
+
if (isAssetCatalogTooLargeErrorBody(errorBody)) {
|
|
1154
|
+
throw new AssetCatalogTooLargeError(`Asset type catalog too large: ${errorBody.detail}`, errorBody);
|
|
1155
|
+
}
|
|
1156
|
+
throw new Error(`Failed to get asset-type catalog: ${response.status} ${response.statusText} - ${errorBody.detail}`);
|
|
1157
|
+
}
|
|
1158
|
+
const errorText = await response.text();
|
|
1159
|
+
throw new Error(`Failed to get asset-type catalog: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1160
|
+
}
|
|
715
1161
|
}
|
|
716
1162
|
//# sourceMappingURL=TwinClient.js.map
|