@twinfinity/twin 5.0.3-ci.27942
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.d.ts +25 -0
- package/dist/BuildInfo.d.ts.map +1 -0
- package/dist/BuildInfo.js +15 -0
- package/dist/BuildInfo.js.map +1 -0
- package/dist/TwinClient.d.ts +156 -0
- package/dist/TwinClient.d.ts.map +1 -0
- package/dist/TwinClient.js +682 -0
- package/dist/TwinClient.js.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +79 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/Twin.d.ts +7 -0
- package/dist/types/Twin.d.ts.map +1 -0
- package/dist/types/Twin.js +6 -0
- package/dist/types/Twin.js.map +1 -0
- package/dist/types/TwinGeometryInfo.d.ts +7 -0
- package/dist/types/TwinGeometryInfo.d.ts.map +1 -0
- package/dist/types/TwinGeometryInfo.js +2 -0
- package/dist/types/TwinGeometryInfo.js.map +1 -0
- package/dist/types/asset-api.d.ts +57 -0
- package/dist/types/asset-api.d.ts.map +1 -0
- package/dist/types/asset-api.js +2 -0
- package/dist/types/asset-api.js.map +1 -0
- package/dist/types/asset-batch-api.d.ts +71 -0
- package/dist/types/asset-batch-api.d.ts.map +1 -0
- package/dist/types/asset-batch-api.js +2 -0
- package/dist/types/asset-batch-api.js.map +1 -0
- package/dist/types/common.d.ts +10 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +2 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/generated.d.ts +388 -0
- package/dist/types/generated.d.ts.map +1 -0
- package/dist/types/generated.js +2 -0
- package/dist/types/generated.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/twin-api.d.ts +88 -0
- package/dist/types/twin-api.d.ts.map +1 -0
- package/dist/types/twin-api.js +2 -0
- package/dist/types/twin-api.js.map +1 -0
- package/dist/types/twin-file-api.d.ts +61 -0
- package/dist/types/twin-file-api.d.ts.map +1 -0
- package/dist/types/twin-file-api.js +2 -0
- package/dist/types/twin-file-api.js.map +1 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/highlight.css +22 -0
- package/docs/assets/icons.js +18 -0
- package/docs/assets/icons.svg +1 -0
- package/docs/assets/main.js +60 -0
- package/docs/assets/navigation.js +1 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1633 -0
- package/docs/classes/AssetBatchError.html +3 -0
- package/docs/classes/AssetConflictError.html +3 -0
- package/docs/classes/AssetNotFoundError.html +2 -0
- package/docs/classes/AssetValidationError.html +2 -0
- package/docs/classes/ProcessorNotFoundError.html +2 -0
- package/docs/classes/TwinClient.html +59 -0
- package/docs/classes/TwinConflictError.html +3 -0
- package/docs/classes/TwinFileConflictError.html +3 -0
- package/docs/classes/TwinFileNotFoundError.html +2 -0
- package/docs/classes/TwinFileValidationError.html +3 -0
- package/docs/classes/TwinNotFoundError.html +2 -0
- package/docs/classes/TwinValidationError.html +3 -0
- package/docs/hierarchy.html +1 -0
- package/docs/index.html +1 -0
- package/docs/interfaces/AssetBatchDeleteOperation.html +4 -0
- package/docs/interfaces/AssetBatchGetRequest.html +2 -0
- package/docs/interfaces/AssetBatchGetResponse.html +3 -0
- package/docs/interfaces/AssetBatchRequest.html +2 -0
- package/docs/interfaces/AssetBatchResponse.html +3 -0
- package/docs/interfaces/AssetBatchRestoreOperation.html +4 -0
- package/docs/interfaces/AssetBatchUpsertItem.html +10 -0
- package/docs/interfaces/AssetBatchUpsertOperation.html +4 -0
- package/docs/interfaces/AssetGet.html +16 -0
- package/docs/interfaces/AssetListItem.html +11 -0
- package/docs/interfaces/AssetPosition.html +4 -0
- package/docs/interfaces/AssetPost.html +10 -0
- package/docs/interfaces/AssetPut.html +10 -0
- package/docs/interfaces/BatchItemError.html +4 -0
- package/docs/interfaces/BatchItemResult.html +6 -0
- package/docs/interfaces/BatchSummary.html +4 -0
- package/docs/interfaces/Diagnostics.html +4 -0
- package/docs/interfaces/DiagnosticsSummary.html +4 -0
- package/docs/interfaces/ElementQuantities.html +3 -0
- package/docs/interfaces/EntityCollection.html +10 -0
- package/docs/interfaces/Facility.html +15 -0
- package/docs/interfaces/FacilityPart.html +14 -0
- package/docs/interfaces/FilePatch.html +7 -0
- package/docs/interfaces/FilePatchSet.html +5 -0
- package/docs/interfaces/GenericDiagnosticEntry.html +4 -0
- package/docs/interfaces/ITwinClient.html +34 -0
- package/docs/interfaces/IfcDiagnosticEntry.html +5 -0
- package/docs/interfaces/IfcDiagnosticNode.html +6 -0
- package/docs/interfaces/IfcDiagnosticNodeData.html +6 -0
- package/docs/interfaces/ListTwinItem.html +14 -0
- package/docs/interfaces/Location.html +10 -0
- package/docs/interfaces/PaginatedResponse.html +3 -0
- package/docs/interfaces/Pagination.html +4 -0
- package/docs/interfaces/Property.html +4 -0
- package/docs/interfaces/PropertySet.html +6 -0
- package/docs/interfaces/SimpleQuantity.html +6 -0
- package/docs/interfaces/SinglePropertyValue.html +5 -0
- package/docs/interfaces/Site.html +18 -0
- package/docs/interfaces/Space.html +11 -0
- package/docs/interfaces/TwinComputedPropertySetsResponse.html +4 -0
- package/docs/interfaces/TwinContentResponse.html +6 -0
- package/docs/interfaces/TwinDelete.html +2 -0
- package/docs/interfaces/TwinDiagnosticEntry.html +5 -0
- package/docs/interfaces/TwinDiagnosticNode.html +4 -0
- package/docs/interfaces/TwinDiagnosticNodeData.html +9 -0
- package/docs/interfaces/TwinDiagnosticsResponse.html +5 -0
- package/docs/interfaces/TwinErrorBody.html +4 -0
- package/docs/interfaces/TwinFileDelete.html +2 -0
- package/docs/interfaces/TwinFileGet.html +15 -0
- package/docs/interfaces/TwinFileListOptions.html +6 -0
- package/docs/interfaces/TwinFilePut.html +8 -0
- package/docs/interfaces/TwinFileReference.html +4 -0
- package/docs/interfaces/TwinFileRestore.html +2 -0
- package/docs/interfaces/TwinFileSource.html +4 -0
- package/docs/interfaces/TwinFileTransform.html +11 -0
- package/docs/interfaces/TwinFileVersionListOptions.html +4 -0
- package/docs/interfaces/TwinGet.html +30 -0
- package/docs/interfaces/TwinProcessorEvent.html +7 -0
- package/docs/interfaces/TwinProcessorResponse.html +5 -0
- package/docs/interfaces/TwinPut.html +23 -0
- package/docs/interfaces/TwinSpecification.html +20 -0
- package/docs/interfaces/TwinVersionContentResponse.html +7 -0
- package/docs/types/AssetBatchGetStatus.html +1 -0
- package/docs/types/AssetBatchOperation.html +1 -0
- package/docs/types/AssetBatchOperationStatus.html +1 -0
- package/docs/types/DiagnosticCategory.html +2 -0
- package/docs/types/DiagnosticLevel.html +2 -0
- package/docs/types/EpsgType.html +1 -0
- package/docs/types/FacilityClass.html +3 -0
- package/docs/types/FacilityPartClass.html +3 -0
- package/docs/types/IfcDiagnosticCode.html +1 -0
- package/docs/types/Op.html +2 -0
- package/docs/types/SinglePropertyValueType.html +3 -0
- package/docs/types/SingleQuantityValueType.html +3 -0
- package/docs/types/SiteType.html +3 -0
- package/docs/types/SpaceType.html +2 -0
- package/docs/types/TwinDiagnosticCode.html +1 -0
- package/package.json +56 -0
- package/src/BuildInfo.ts +41 -0
- package/src/TwinClient.ts +1274 -0
- package/src/errors.ts +107 -0
- package/src/index.ts +3 -0
- package/src/tsconfig.json +13 -0
- package/src/types/Twin.ts +7 -0
- package/src/types/TwinGeometryInfo.ts +7 -0
- package/src/types/asset-api.ts +60 -0
- package/src/types/asset-batch-api.ts +88 -0
- package/src/types/common.ts +10 -0
- package/src/types/generated.ts +574 -0
- package/src/types/index.ts +6 -0
- package/src/types/twin-api.ts +99 -0
- package/src/types/twin-file-api.ts +67 -0
|
@@ -0,0 +1,1274 @@
|
|
|
1
|
+
import { TwinfinityHttpClient, HttpMethod } from '@twinfinity/authentication';
|
|
2
|
+
import {
|
|
3
|
+
readT8ClientModelWithFactories,
|
|
4
|
+
readT8GIModelWithFactories,
|
|
5
|
+
T8ClientEntityTypeDTO,
|
|
6
|
+
T8ClientModel,
|
|
7
|
+
T8ClientModelDTO,
|
|
8
|
+
T8ClientProductConnectionsDTO,
|
|
9
|
+
T8ClientProductDTO,
|
|
10
|
+
T8ClientProjectConnectionsDTO,
|
|
11
|
+
T8ClientProjectDTO,
|
|
12
|
+
T8ClientUnitDTO,
|
|
13
|
+
T8EntityType,
|
|
14
|
+
T8GIMeshDescriptor,
|
|
15
|
+
T8GIMeshDescriptorDTO,
|
|
16
|
+
T8GIStyle,
|
|
17
|
+
T8GIStyleDTO,
|
|
18
|
+
T8GIRegion,
|
|
19
|
+
T8GIRegionDTO,
|
|
20
|
+
T8Product,
|
|
21
|
+
T8Project,
|
|
22
|
+
T8Unit
|
|
23
|
+
} from '@twinfinity/t8-client-model';
|
|
24
|
+
import type { T8GIModelGeneric } from '@twinfinity/t8-client-model';
|
|
25
|
+
import type {
|
|
26
|
+
PaginatedResponse,
|
|
27
|
+
ListTwinItem,
|
|
28
|
+
TwinGet,
|
|
29
|
+
TwinPut,
|
|
30
|
+
TwinVersionContentResponse,
|
|
31
|
+
TwinProcessorEvent,
|
|
32
|
+
TwinDiagnosticsResponse,
|
|
33
|
+
AssetListItem,
|
|
34
|
+
AssetGet,
|
|
35
|
+
AssetPost,
|
|
36
|
+
AssetPut,
|
|
37
|
+
AssetBatchOperation,
|
|
38
|
+
AssetBatchResponse,
|
|
39
|
+
AssetBatchGetResponse,
|
|
40
|
+
TwinFileGet,
|
|
41
|
+
TwinFilePut,
|
|
42
|
+
TwinFileDelete,
|
|
43
|
+
TwinFileListOptions,
|
|
44
|
+
TwinFileVersionListOptions,
|
|
45
|
+
TwinContentResponse
|
|
46
|
+
} from './types';
|
|
47
|
+
import {
|
|
48
|
+
TwinValidationError,
|
|
49
|
+
TwinConflictError,
|
|
50
|
+
TwinNotFoundError,
|
|
51
|
+
ProcessorNotFoundError,
|
|
52
|
+
AssetValidationError,
|
|
53
|
+
AssetConflictError,
|
|
54
|
+
AssetNotFoundError,
|
|
55
|
+
AssetBatchError,
|
|
56
|
+
TwinFileValidationError,
|
|
57
|
+
TwinFileConflictError,
|
|
58
|
+
TwinFileNotFoundError
|
|
59
|
+
} from './errors';
|
|
60
|
+
import { Twin } from './types/Twin';
|
|
61
|
+
import { TwinGeometryInfo } from './types/TwinGeometryInfo';
|
|
62
|
+
|
|
63
|
+
export interface ITwinClient {
|
|
64
|
+
listTwins(
|
|
65
|
+
relatedTo?: string,
|
|
66
|
+
page?: string,
|
|
67
|
+
limit?: number,
|
|
68
|
+
signal?: AbortSignal
|
|
69
|
+
): Promise<PaginatedResponse<ListTwinItem>>;
|
|
70
|
+
|
|
71
|
+
listAllTwins(relatedTo?: string, signal?: AbortSignal): Promise<ListTwinItem[]>;
|
|
72
|
+
|
|
73
|
+
getTwin(twin: string | { id: string; version: number }, signal?: AbortSignal): Promise<TwinGet>;
|
|
74
|
+
|
|
75
|
+
listTwinVersions(
|
|
76
|
+
id: string,
|
|
77
|
+
options?: {
|
|
78
|
+
includeDeleted?: boolean;
|
|
79
|
+
page?: string;
|
|
80
|
+
limit?: number;
|
|
81
|
+
},
|
|
82
|
+
signal?: AbortSignal
|
|
83
|
+
): Promise<PaginatedResponse<ListTwinItem>>;
|
|
84
|
+
|
|
85
|
+
downloadTwinContent(
|
|
86
|
+
twinId: string,
|
|
87
|
+
twinVersion: number,
|
|
88
|
+
content: TwinContentResponse,
|
|
89
|
+
signal?: AbortSignal
|
|
90
|
+
): Promise<ArrayBuffer>;
|
|
91
|
+
|
|
92
|
+
loadTwin(
|
|
93
|
+
twin: string | { id: string; version: number },
|
|
94
|
+
signal?: AbortSignal
|
|
95
|
+
): Promise<Twin<T8Project, T8Product<T8EntityType>, T8EntityType, T8Unit>>;
|
|
96
|
+
|
|
97
|
+
loadTwinWithFactories<TProject, TProduct, TEntityType, TUnit>(
|
|
98
|
+
twin: string | { id: string; version: number },
|
|
99
|
+
projectFactory: (dto: T8ClientProjectDTO<TUnit>) => TProject,
|
|
100
|
+
productFactory: (dto: T8ClientProductDTO<TUnit>) => TProduct,
|
|
101
|
+
entityTypeFactory: (dto: T8ClientEntityTypeDTO<TUnit>) => TEntityType,
|
|
102
|
+
unitFactory: (dto: T8ClientUnitDTO, dataModel: T8ClientModelDTO) => TUnit,
|
|
103
|
+
connectProject: (dto: T8ClientProjectConnectionsDTO<TProduct>, project: TProject) => void,
|
|
104
|
+
connectProduct: (dto: T8ClientProductConnectionsDTO<TProduct, TEntityType>, product: TProduct) => void,
|
|
105
|
+
signal?: AbortSignal
|
|
106
|
+
): Promise<Twin<TProject, TProduct, TEntityType, TUnit>>;
|
|
107
|
+
|
|
108
|
+
loadGeometryInfo(
|
|
109
|
+
twin: string | { id: string; version: number },
|
|
110
|
+
signal?: AbortSignal
|
|
111
|
+
): Promise<TwinGeometryInfo<T8GIMeshDescriptor, T8GIStyle, T8GIRegion>>;
|
|
112
|
+
|
|
113
|
+
loadGeometryInfoWithFactories<TMeshDescriptor, TStyle, TRegion>(
|
|
114
|
+
twin: string | { id: string; version: number },
|
|
115
|
+
meshDescriptorFactory: (dto: T8GIMeshDescriptorDTO) => TMeshDescriptor,
|
|
116
|
+
styleFactory: (dto: T8GIStyleDTO) => TStyle,
|
|
117
|
+
regionFactory: (dto: T8GIRegionDTO) => TRegion,
|
|
118
|
+
signal?: AbortSignal
|
|
119
|
+
): Promise<TwinGeometryInfo<TMeshDescriptor, TStyle, TRegion>>;
|
|
120
|
+
|
|
121
|
+
createOrUpdateTwin(twinId: string, twin: TwinPut, signal?: AbortSignal): Promise<TwinGet>;
|
|
122
|
+
updateTwin(twinId: string, twin: TwinPut, signal?: AbortSignal): Promise<TwinGet>;
|
|
123
|
+
|
|
124
|
+
restoreTwin(twinId: string, etag: string, signal?: AbortSignal): Promise<TwinGet>;
|
|
125
|
+
|
|
126
|
+
getTwinVersionContent(
|
|
127
|
+
twinId: string,
|
|
128
|
+
twinVersion: number,
|
|
129
|
+
signal?: AbortSignal
|
|
130
|
+
): Promise<TwinVersionContentResponse>;
|
|
131
|
+
getTwinContent(twinId: string, signal?: AbortSignal): Promise<TwinVersionContentResponse>;
|
|
132
|
+
|
|
133
|
+
getTwinDiagnostics(twinId: string, signal?: AbortSignal): Promise<TwinDiagnosticsResponse>;
|
|
134
|
+
getTwinVersionDiagnostics(
|
|
135
|
+
twinId: string,
|
|
136
|
+
twinVersion: number,
|
|
137
|
+
signal?: AbortSignal
|
|
138
|
+
): Promise<TwinDiagnosticsResponse>;
|
|
139
|
+
|
|
140
|
+
getTwinVersionEvents(
|
|
141
|
+
twinId: string,
|
|
142
|
+
twinVersion: number,
|
|
143
|
+
offset?: number,
|
|
144
|
+
limit?: number,
|
|
145
|
+
signal?: AbortSignal
|
|
146
|
+
): Promise<PaginatedResponse<TwinProcessorEvent>>;
|
|
147
|
+
|
|
148
|
+
getProcessorEvents(
|
|
149
|
+
twinId: string,
|
|
150
|
+
twinVersion: number,
|
|
151
|
+
processorId: string,
|
|
152
|
+
offset?: number,
|
|
153
|
+
limit?: number,
|
|
154
|
+
signal?: AbortSignal
|
|
155
|
+
): Promise<PaginatedResponse<TwinProcessorEvent>>;
|
|
156
|
+
|
|
157
|
+
listAssets(
|
|
158
|
+
twinId: string,
|
|
159
|
+
offset?: number,
|
|
160
|
+
limit?: number,
|
|
161
|
+
options?: { includeDeleted?: boolean },
|
|
162
|
+
signal?: AbortSignal
|
|
163
|
+
): Promise<PaginatedResponse<AssetListItem>>;
|
|
164
|
+
|
|
165
|
+
getAsset(twinId: string, assetId: string, signal?: AbortSignal): Promise<AssetGet>;
|
|
166
|
+
|
|
167
|
+
createAssets(twinId: string, assets: AssetPost[], signal?: AbortSignal): Promise<AssetGet[]>;
|
|
168
|
+
|
|
169
|
+
updateAsset(twinId: string, assetId: string, asset: AssetPut, signal?: AbortSignal): Promise<AssetGet>;
|
|
170
|
+
|
|
171
|
+
deleteAsset(twinId: string, assetId: string, etag: string, signal?: AbortSignal): Promise<AssetGet>;
|
|
172
|
+
|
|
173
|
+
deleteTwin(twinId: string, etag: string, signal?: AbortSignal): Promise<TwinGet>;
|
|
174
|
+
|
|
175
|
+
listTwinFiles(
|
|
176
|
+
twinId: string,
|
|
177
|
+
options?: TwinFileListOptions,
|
|
178
|
+
signal?: AbortSignal
|
|
179
|
+
): Promise<PaginatedResponse<TwinFileGet>>;
|
|
180
|
+
getTwinFile(twinId: string, fileId: string, signal?: AbortSignal): Promise<TwinFileGet>;
|
|
181
|
+
|
|
182
|
+
createOrUpdateTwinFile(
|
|
183
|
+
twinId: string,
|
|
184
|
+
fileId: string,
|
|
185
|
+
twinFile: TwinFilePut,
|
|
186
|
+
signal?: AbortSignal
|
|
187
|
+
): Promise<TwinFileGet>;
|
|
188
|
+
deleteTwinFile(
|
|
189
|
+
twinId: string,
|
|
190
|
+
fileId: string,
|
|
191
|
+
deleteRequest: TwinFileDelete,
|
|
192
|
+
signal?: AbortSignal
|
|
193
|
+
): Promise<TwinFileGet>;
|
|
194
|
+
|
|
195
|
+
restoreTwinFile(twinId: string, fileId: string, etag: string, signal?: AbortSignal): Promise<TwinFileGet>;
|
|
196
|
+
getTwinFileContent(twinId: string, fileId: string, signal?: AbortSignal): Promise<Blob>;
|
|
197
|
+
|
|
198
|
+
getTwinFileVersion(twinId: string, fileId: string, version: number, signal?: AbortSignal): Promise<TwinFileGet>;
|
|
199
|
+
listTwinFileVersions(
|
|
200
|
+
twinId: string,
|
|
201
|
+
fileId: string,
|
|
202
|
+
options?: TwinFileVersionListOptions,
|
|
203
|
+
signal?: AbortSignal
|
|
204
|
+
): Promise<PaginatedResponse<TwinFileGet>>;
|
|
205
|
+
|
|
206
|
+
getTwinFileVersionContent(twinId: string, fileId: string, version: number, signal?: AbortSignal): Promise<Blob>;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export class TwinClient implements ITwinClient {
|
|
210
|
+
private readonly _baseUrl: string;
|
|
211
|
+
|
|
212
|
+
constructor(
|
|
213
|
+
baseUrl: string | URL,
|
|
214
|
+
private readonly _httpClient: TwinfinityHttpClient
|
|
215
|
+
) {
|
|
216
|
+
this._baseUrl = `${baseUrl}/_ps/twins`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async listTwins(
|
|
220
|
+
relatedTo?: string,
|
|
221
|
+
page: string | undefined = undefined,
|
|
222
|
+
limit = 100,
|
|
223
|
+
signal?: AbortSignal
|
|
224
|
+
): Promise<PaginatedResponse<ListTwinItem>> {
|
|
225
|
+
const params = new URLSearchParams();
|
|
226
|
+
if (relatedTo) params.set('relatedTo', relatedTo);
|
|
227
|
+
if (page !== undefined) params.set('page', page);
|
|
228
|
+
if (limit !== 100) params.set('limit', limit.toString());
|
|
229
|
+
|
|
230
|
+
const url = params.toString() ? `${this._baseUrl}?${params}` : this._baseUrl;
|
|
231
|
+
|
|
232
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
233
|
+
headers: { 'Content-Type': 'application/json' },
|
|
234
|
+
signal
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (response.ok) {
|
|
238
|
+
return (await response.json()) as PaginatedResponse<ListTwinItem>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (response.status === 400) {
|
|
242
|
+
const errorBody = await response.json();
|
|
243
|
+
throw new TwinValidationError(`Twin listing validation failed: ${errorBody.detail}`, errorBody);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const errorText = await response.text();
|
|
247
|
+
throw new Error(`Failed to list twins: ${response.status} ${response.statusText} - ${errorText}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async listAllTwins(relatedTo?: string, signal?: AbortSignal): Promise<ListTwinItem[]> {
|
|
251
|
+
const allTwins: ListTwinItem[] = [];
|
|
252
|
+
let page: string | undefined = undefined;
|
|
253
|
+
const limit = 100;
|
|
254
|
+
const maxPages = 1000;
|
|
255
|
+
|
|
256
|
+
for (let pageCount = 0; pageCount < maxPages; pageCount++) {
|
|
257
|
+
const response = await this.listTwins(relatedTo, page, limit, signal);
|
|
258
|
+
allTwins.push(...response.data);
|
|
259
|
+
|
|
260
|
+
if (response.data.length < limit) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
page = response.pagination.nextPage;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return allTwins;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async getTwin(twin: string | { id: string; version: number }, signal?: AbortSignal): Promise<TwinGet> {
|
|
271
|
+
const twinId = typeof twin === 'string' ? twin : twin.id;
|
|
272
|
+
const specifiedVersion = typeof twin === 'string' ? undefined : twin.version;
|
|
273
|
+
const response = await this._httpClient.fetch(
|
|
274
|
+
HttpMethod.Get,
|
|
275
|
+
`${this._baseUrl}/${twinId}${specifiedVersion !== undefined ? `/versions/${specifiedVersion}` : ''}`,
|
|
276
|
+
{
|
|
277
|
+
headers: { 'Content-Type': 'application/json' },
|
|
278
|
+
signal
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
if (response.ok) {
|
|
283
|
+
return (await response.json()) as TwinGet;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (response.status === 404) {
|
|
287
|
+
throw new TwinNotFoundError(`Twin ${twinId} not found`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const errorText = await response.text();
|
|
291
|
+
throw new Error(`Failed to get twin: ${response.status} ${response.statusText} - ${errorText}`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async listTwinVersions(
|
|
295
|
+
id: string,
|
|
296
|
+
options?: {
|
|
297
|
+
includeDeleted?: boolean;
|
|
298
|
+
page: string | undefined;
|
|
299
|
+
limit?: number;
|
|
300
|
+
},
|
|
301
|
+
signal?: AbortSignal
|
|
302
|
+
): Promise<PaginatedResponse<ListTwinItem>> {
|
|
303
|
+
const params = new URLSearchParams();
|
|
304
|
+
if (options?.page !== undefined) params.set('page', options.page);
|
|
305
|
+
if (options?.limit !== undefined && options.limit !== 100) params.set('limit', options.limit.toString());
|
|
306
|
+
if (options?.includeDeleted) params.set('includeDeleted', 'true');
|
|
307
|
+
|
|
308
|
+
const url = `${this._baseUrl}/${id}/versions${params.toString() ? `?${params}` : ''}`;
|
|
309
|
+
|
|
310
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
311
|
+
headers: { 'Content-Type': 'application/json' },
|
|
312
|
+
signal
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (response.ok) {
|
|
316
|
+
return (await response.json()) as PaginatedResponse<ListTwinItem>;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (response.status === 400) {
|
|
320
|
+
const errorBody = await response.json();
|
|
321
|
+
throw new TwinValidationError(`Twin listing validation failed: ${errorBody.detail}`, errorBody);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const errorText = await response.text();
|
|
325
|
+
throw new Error(`Failed to list twins: ${response.status} ${response.statusText} - ${errorText}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async downloadTwinContent(
|
|
329
|
+
twinId: string,
|
|
330
|
+
twinVersion: number,
|
|
331
|
+
content: TwinContentResponse,
|
|
332
|
+
signal?: AbortSignal
|
|
333
|
+
): Promise<ArrayBuffer> {
|
|
334
|
+
const response = await this._httpClient.fetch(
|
|
335
|
+
HttpMethod.Get,
|
|
336
|
+
`${this._baseUrl}/${twinId}/versions/${twinVersion}/contents/${content.id}/content`,
|
|
337
|
+
{ signal }
|
|
338
|
+
);
|
|
339
|
+
if (response.ok) {
|
|
340
|
+
const blob = await response.blob();
|
|
341
|
+
return await blob.arrayBuffer();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
throw new Error(`Failed to download twin content: ${response.status} ${response.statusText}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private async loadTwinVersion<TProject, TProduct, TEntityType, TUnit>(
|
|
348
|
+
twinId: string,
|
|
349
|
+
twinVersion: number,
|
|
350
|
+
projectFactory: (dto: T8ClientProjectDTO<TUnit>) => TProject,
|
|
351
|
+
productFactory: (dto: T8ClientProductDTO<TUnit>) => TProduct,
|
|
352
|
+
entityTypeFactory: (dto: T8ClientEntityTypeDTO<TUnit>) => TEntityType,
|
|
353
|
+
unitFactory: (dto: T8ClientUnitDTO, dataModel: T8ClientModelDTO) => TUnit,
|
|
354
|
+
connectProject: (dto: T8ClientProjectConnectionsDTO<TProduct>, project: TProject) => void,
|
|
355
|
+
connectProduct: (dto: T8ClientProductConnectionsDTO<TProduct, TEntityType>, product: TProduct) => void,
|
|
356
|
+
signal?: AbortSignal
|
|
357
|
+
): Promise<T8ClientModel<TProject, TProduct, TEntityType, TUnit> | undefined> {
|
|
358
|
+
const twinContent = await this.getTwinVersionContent(twinId, twinVersion, signal);
|
|
359
|
+
|
|
360
|
+
const t8cmItem = twinContent.content.find((item) => item.format === 't8cm');
|
|
361
|
+
|
|
362
|
+
if (t8cmItem === undefined) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const t8cmExtensions = twinContent.content.filter((item) => item.format === 't8cm-ext');
|
|
367
|
+
|
|
368
|
+
const data = await Promise.all(
|
|
369
|
+
[this.downloadTwinContent(twinId, twinVersion, t8cmItem, signal)].concat(
|
|
370
|
+
t8cmExtensions.map((ext) => this.downloadTwinContent(twinId, twinVersion, ext, signal))
|
|
371
|
+
)
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
return readT8ClientModelWithFactories<TProject, TProduct, TEntityType, TUnit>(
|
|
375
|
+
data,
|
|
376
|
+
projectFactory,
|
|
377
|
+
productFactory,
|
|
378
|
+
entityTypeFactory,
|
|
379
|
+
unitFactory,
|
|
380
|
+
connectProject,
|
|
381
|
+
connectProduct
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
loadTwin(
|
|
386
|
+
twin: string | { id: string; version: number },
|
|
387
|
+
signal?: AbortSignal
|
|
388
|
+
): Promise<Twin<T8Project, T8Product<T8EntityType>, T8EntityType, T8Unit>> {
|
|
389
|
+
return this.loadTwinWithFactories(
|
|
390
|
+
twin,
|
|
391
|
+
T8Project.create<T8Product<T8EntityType>, T8Unit>,
|
|
392
|
+
T8Product.create<T8EntityType, T8Unit>,
|
|
393
|
+
T8EntityType.create,
|
|
394
|
+
T8Unit.create,
|
|
395
|
+
T8Project.connect,
|
|
396
|
+
T8Product.connect,
|
|
397
|
+
signal
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async loadTwinWithFactories<TProject, TProduct, TEntityType, TUnit>(
|
|
402
|
+
twin: string | { id: string; version: number },
|
|
403
|
+
projectFactory: (dto: T8ClientProjectDTO<TUnit>) => TProject,
|
|
404
|
+
productFactory: (dto: T8ClientProductDTO<TUnit>) => TProduct,
|
|
405
|
+
entityTypeFactory: (dto: T8ClientEntityTypeDTO<TUnit>) => TEntityType,
|
|
406
|
+
unitFactory: (dto: T8ClientUnitDTO, dataModel: T8ClientModelDTO) => TUnit,
|
|
407
|
+
connectProject: (dto: T8ClientProjectConnectionsDTO<TProduct>, project: TProject) => void,
|
|
408
|
+
connectProduct: (dto: T8ClientProductConnectionsDTO<TProduct, TEntityType>, product: TProduct) => void,
|
|
409
|
+
signal?: AbortSignal
|
|
410
|
+
): Promise<Twin<TProject, TProduct, TEntityType, TUnit>> {
|
|
411
|
+
let page: string | undefined = undefined;
|
|
412
|
+
const twinId = typeof twin === 'string' ? twin : twin.id;
|
|
413
|
+
const specifiedVersion = typeof twin === 'string' ? undefined : twin.version;
|
|
414
|
+
if (specifiedVersion !== undefined) {
|
|
415
|
+
return {
|
|
416
|
+
id: twinId,
|
|
417
|
+
version: specifiedVersion,
|
|
418
|
+
model: await this.loadTwinVersion(
|
|
419
|
+
twinId,
|
|
420
|
+
specifiedVersion,
|
|
421
|
+
projectFactory,
|
|
422
|
+
productFactory,
|
|
423
|
+
entityTypeFactory,
|
|
424
|
+
unitFactory,
|
|
425
|
+
connectProject,
|
|
426
|
+
connectProduct,
|
|
427
|
+
signal
|
|
428
|
+
)
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const maxPages = 100;
|
|
433
|
+
for (let pageCount = 0; pageCount < maxPages; pageCount++) {
|
|
434
|
+
const versions = await this.listTwinVersions(twinId, { page }, signal);
|
|
435
|
+
if (versions.data.length === 0) {
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
for (const versionItem of versions.data) {
|
|
439
|
+
const loadedModel = await this.loadTwinVersion(
|
|
440
|
+
twinId,
|
|
441
|
+
versionItem.version,
|
|
442
|
+
projectFactory,
|
|
443
|
+
productFactory,
|
|
444
|
+
entityTypeFactory,
|
|
445
|
+
unitFactory,
|
|
446
|
+
connectProject,
|
|
447
|
+
connectProduct,
|
|
448
|
+
signal
|
|
449
|
+
);
|
|
450
|
+
if (loadedModel !== undefined) {
|
|
451
|
+
return {
|
|
452
|
+
id: twinId,
|
|
453
|
+
version: versionItem.version,
|
|
454
|
+
model: loadedModel
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
page = versions.pagination.nextPage;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
throw new Error(`Twin ${twinId} does not have a client model`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private async loadGeometryInfoVersion<TMeshDescriptor, TStyle, TRegion>(
|
|
465
|
+
twinId: string,
|
|
466
|
+
twinVersion: number,
|
|
467
|
+
meshDescriptorFactory: (dto: T8GIMeshDescriptorDTO) => TMeshDescriptor,
|
|
468
|
+
styleFactory: (dto: T8GIStyleDTO) => TStyle,
|
|
469
|
+
regionFactory: (dto: T8GIRegionDTO) => TRegion,
|
|
470
|
+
signal?: AbortSignal
|
|
471
|
+
): Promise<T8GIModelGeneric<TMeshDescriptor, TStyle, TRegion> | undefined> {
|
|
472
|
+
const twinContent = await this.getTwinVersionContent(twinId, twinVersion, signal);
|
|
473
|
+
|
|
474
|
+
const t8giItem = twinContent.content.find((item) => item.format === 't8gi');
|
|
475
|
+
|
|
476
|
+
if (t8giItem === undefined) {
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const data = await this.downloadTwinContent(twinId, twinVersion, t8giItem, signal);
|
|
481
|
+
|
|
482
|
+
return readT8GIModelWithFactories(data, meshDescriptorFactory, styleFactory, regionFactory);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
loadGeometryInfo(
|
|
486
|
+
twin: string | { id: string; version: number },
|
|
487
|
+
signal?: AbortSignal
|
|
488
|
+
): Promise<TwinGeometryInfo<T8GIMeshDescriptor, T8GIStyle, T8GIRegion>> {
|
|
489
|
+
return this.loadGeometryInfoWithFactories(
|
|
490
|
+
twin,
|
|
491
|
+
T8GIMeshDescriptor.create,
|
|
492
|
+
T8GIStyle.create,
|
|
493
|
+
T8GIRegion.create,
|
|
494
|
+
signal
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async loadGeometryInfoWithFactories<TMeshDescriptor, TStyle, TRegion>(
|
|
499
|
+
twin: string | { id: string; version: number },
|
|
500
|
+
meshDescriptorFactory: (dto: T8GIMeshDescriptorDTO) => TMeshDescriptor,
|
|
501
|
+
styleFactory: (dto: T8GIStyleDTO) => TStyle,
|
|
502
|
+
regionFactory: (dto: T8GIRegionDTO) => TRegion,
|
|
503
|
+
signal?: AbortSignal
|
|
504
|
+
): Promise<TwinGeometryInfo<TMeshDescriptor, TStyle, TRegion>> {
|
|
505
|
+
let page: string | undefined = undefined;
|
|
506
|
+
const twinId = typeof twin === 'string' ? twin : twin.id;
|
|
507
|
+
const specifiedVersion = typeof twin === 'string' ? undefined : twin.version;
|
|
508
|
+
|
|
509
|
+
if (specifiedVersion !== undefined) {
|
|
510
|
+
return {
|
|
511
|
+
id: twinId,
|
|
512
|
+
version: specifiedVersion,
|
|
513
|
+
model: await this.loadGeometryInfoVersion(
|
|
514
|
+
twinId,
|
|
515
|
+
specifiedVersion,
|
|
516
|
+
meshDescriptorFactory,
|
|
517
|
+
styleFactory,
|
|
518
|
+
regionFactory,
|
|
519
|
+
signal
|
|
520
|
+
)
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const maxPages = 100;
|
|
525
|
+
for (let pageCount = 0; pageCount < maxPages; pageCount++) {
|
|
526
|
+
const versions = await this.listTwinVersions(twinId, { page }, signal);
|
|
527
|
+
if (versions.data.length === 0) {
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
for (const versionItem of versions.data) {
|
|
531
|
+
const model = await this.loadGeometryInfoVersion(
|
|
532
|
+
twinId,
|
|
533
|
+
versionItem.version,
|
|
534
|
+
meshDescriptorFactory,
|
|
535
|
+
styleFactory,
|
|
536
|
+
regionFactory,
|
|
537
|
+
signal
|
|
538
|
+
);
|
|
539
|
+
if (model !== undefined) {
|
|
540
|
+
return {
|
|
541
|
+
id: twinId,
|
|
542
|
+
version: versionItem.version,
|
|
543
|
+
model
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
page = versions.pagination.nextPage;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
throw new Error(`Twin ${twinId} does not have geometry info`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async createOrUpdateTwin(twinId: string, twin: TwinPut, signal?: AbortSignal): Promise<TwinGet> {
|
|
554
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, `${this._baseUrl}/${twinId}`, {
|
|
555
|
+
headers: { 'Content-Type': 'application/json' },
|
|
556
|
+
body: JSON.stringify(twin),
|
|
557
|
+
signal
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
if (response.ok) {
|
|
561
|
+
return (await response.json()) as TwinGet;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (response.status === 400) {
|
|
565
|
+
const errorBody = await response.json();
|
|
566
|
+
throw new TwinValidationError(`Twin validation failed: ${errorBody.detail}`, errorBody);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (response.status === 409) {
|
|
570
|
+
const conflictedTwin = (await response.json()) as TwinGet;
|
|
571
|
+
throw new TwinConflictError('Twin has been modified by another user', conflictedTwin);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const errorText = await response.text();
|
|
575
|
+
throw new Error(`Failed to create/update twin: ${response.status} ${response.statusText} - ${errorText}`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async updateTwin(twinId: string, twin: TwinPut, signal?: AbortSignal): Promise<TwinGet> {
|
|
579
|
+
return this.createOrUpdateTwin(twinId, twin, signal);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async deleteTwin(twinId: string, etag: string, signal?: AbortSignal): Promise<TwinGet> {
|
|
583
|
+
const response = await this._httpClient.fetch(HttpMethod.Delete, `${this._baseUrl}/${twinId}`, {
|
|
584
|
+
headers: { 'Content-Type': 'application/json' },
|
|
585
|
+
body: JSON.stringify({ etag }),
|
|
586
|
+
signal
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
if (response.ok) {
|
|
590
|
+
return (await response.json()) as TwinGet;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (response.status === 404) {
|
|
594
|
+
throw new TwinNotFoundError(`Twin ${twinId} not found`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (response.status === 409) {
|
|
598
|
+
const conflictedTwin = (await response.json()) as TwinGet;
|
|
599
|
+
throw new TwinConflictError('Twin has been modified by another user', conflictedTwin);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const errorText = await response.text();
|
|
603
|
+
throw new Error(`Failed to delete twin: ${response.status} ${response.statusText} - ${errorText}`);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async restoreTwin(twinId: string, etag: string, signal?: AbortSignal): Promise<TwinGet> {
|
|
607
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, `${this._baseUrl}/${twinId}/restore`, {
|
|
608
|
+
headers: { 'Content-Type': 'application/json' },
|
|
609
|
+
body: JSON.stringify({ etag }),
|
|
610
|
+
signal
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
if (response.ok) {
|
|
614
|
+
return (await response.json()) as TwinGet;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (response.status === 404) {
|
|
618
|
+
throw new TwinNotFoundError(`Twin ${twinId} not found`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (response.status === 409) {
|
|
622
|
+
const conflictedTwin = (await response.json()) as TwinGet;
|
|
623
|
+
throw new TwinConflictError('Twin has been modified by another user', conflictedTwin);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const errorText = await response.text();
|
|
627
|
+
throw new Error(`Failed to restore twin: ${response.status} ${response.statusText} - ${errorText}`);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async getTwinVersionContent(
|
|
631
|
+
twinId: string,
|
|
632
|
+
twinVersion: number,
|
|
633
|
+
signal?: AbortSignal
|
|
634
|
+
): Promise<TwinVersionContentResponse> {
|
|
635
|
+
const response = await this._httpClient.fetch(
|
|
636
|
+
HttpMethod.Get,
|
|
637
|
+
`${this._baseUrl}/${twinId}/versions/${twinVersion}/contents`,
|
|
638
|
+
{ headers: { 'Content-Type': 'application/json' }, signal }
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
if (response.ok) {
|
|
642
|
+
return (await response.json()) as TwinVersionContentResponse;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (response.status === 404) {
|
|
646
|
+
throw new TwinNotFoundError(`Twin ${twinId} version ${twinVersion} not found`);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const errorText = await response.text();
|
|
650
|
+
throw new Error(`Failed to get twin version content: ${response.status} ${response.statusText} - ${errorText}`);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async getTwinContent(twinId: string, signal?: AbortSignal): Promise<TwinVersionContentResponse> {
|
|
654
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/contents`, {
|
|
655
|
+
headers: { 'Content-Type': 'application/json' },
|
|
656
|
+
signal
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
if (response.ok) {
|
|
660
|
+
return (await response.json()) as TwinVersionContentResponse;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (response.status === 404) {
|
|
664
|
+
throw new TwinNotFoundError(`Twin ${twinId} not found`);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const errorText = await response.text();
|
|
668
|
+
throw new Error(`Failed to get twin content: ${response.status} ${response.statusText} - ${errorText}`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async getTwinDiagnostics(twinId: string, signal?: AbortSignal): Promise<TwinDiagnosticsResponse> {
|
|
672
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/diagnostics`, {
|
|
673
|
+
headers: { 'Content-Type': 'application/json' },
|
|
674
|
+
signal
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
if (response.ok) {
|
|
678
|
+
return (await response.json()) as TwinDiagnosticsResponse;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (response.status === 404) {
|
|
682
|
+
throw new TwinNotFoundError(`Twin ${twinId} not found`);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const errorText = await response.text();
|
|
686
|
+
throw new Error(`Failed to get twin diagnostics: ${response.status} ${response.statusText} - ${errorText}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async getTwinVersionDiagnostics(
|
|
690
|
+
twinId: string,
|
|
691
|
+
twinVersion: number,
|
|
692
|
+
signal?: AbortSignal
|
|
693
|
+
): Promise<TwinDiagnosticsResponse> {
|
|
694
|
+
const response = await this._httpClient.fetch(
|
|
695
|
+
HttpMethod.Get,
|
|
696
|
+
`${this._baseUrl}/${twinId}/versions/${twinVersion}/diagnostics`,
|
|
697
|
+
{ headers: { 'Content-Type': 'application/json' }, signal }
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
if (response.ok) {
|
|
701
|
+
return (await response.json()) as TwinDiagnosticsResponse;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (response.status === 404) {
|
|
705
|
+
throw new TwinNotFoundError(`Twin ${twinId} version ${twinVersion} not found`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const errorText = await response.text();
|
|
709
|
+
throw new Error(
|
|
710
|
+
`Failed to get twin version diagnostics: ${response.status} ${response.statusText} - ${errorText}`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async getTwinVersionEvents(
|
|
715
|
+
twinId: string,
|
|
716
|
+
twinVersion: number,
|
|
717
|
+
offset = 0,
|
|
718
|
+
limit = 100,
|
|
719
|
+
signal?: AbortSignal
|
|
720
|
+
): Promise<PaginatedResponse<TwinProcessorEvent>> {
|
|
721
|
+
const url = `${this._baseUrl}/${twinId}/versions/${twinVersion}/events?offset=${offset}&limit=${limit}`;
|
|
722
|
+
|
|
723
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
724
|
+
headers: { 'Content-Type': 'application/json' },
|
|
725
|
+
signal
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
if (response.ok) {
|
|
729
|
+
return (await response.json()) as PaginatedResponse<TwinProcessorEvent>;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (response.status === 400) {
|
|
733
|
+
const errorBody = await response.json();
|
|
734
|
+
throw new TwinValidationError(`Twin events validation failed: ${errorBody.detail}`, errorBody);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (response.status === 404) {
|
|
738
|
+
throw new TwinNotFoundError(`Twin ${twinId} version ${twinVersion} not found`);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const errorText = await response.text();
|
|
742
|
+
throw new Error(`Failed to get twin version events: ${response.status} ${response.statusText} - ${errorText}`);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async getProcessorEvents(
|
|
746
|
+
twinId: string,
|
|
747
|
+
twinVersion: number,
|
|
748
|
+
processorId: string,
|
|
749
|
+
offset = 0,
|
|
750
|
+
limit = 100,
|
|
751
|
+
signal?: AbortSignal
|
|
752
|
+
): Promise<PaginatedResponse<TwinProcessorEvent>> {
|
|
753
|
+
const url = `${this._baseUrl}/${twinId}/versions/${twinVersion}/processors/${processorId}/events?offset=${offset}&limit=${limit}`;
|
|
754
|
+
|
|
755
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
756
|
+
headers: { 'Content-Type': 'application/json' },
|
|
757
|
+
signal
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
if (response.ok) {
|
|
761
|
+
return (await response.json()) as PaginatedResponse<TwinProcessorEvent>;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (response.status === 400) {
|
|
765
|
+
const errorBody = await response.json();
|
|
766
|
+
throw new TwinValidationError(`Processor events validation failed: ${errorBody.detail}`, errorBody);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (response.status === 404) {
|
|
770
|
+
throw new ProcessorNotFoundError(
|
|
771
|
+
`Processor ${processorId} not found for twin ${twinId} version ${twinVersion}`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const errorText = await response.text();
|
|
776
|
+
throw new Error(`Failed to get processor events: ${response.status} ${response.statusText} - ${errorText}`);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async listAssets(
|
|
780
|
+
twinId: string,
|
|
781
|
+
offset = 0,
|
|
782
|
+
limit = 100,
|
|
783
|
+
options?: { includeDeleted?: boolean },
|
|
784
|
+
signal?: AbortSignal
|
|
785
|
+
): Promise<PaginatedResponse<AssetListItem>> {
|
|
786
|
+
const includeDeleted = options?.includeDeleted ?? false;
|
|
787
|
+
const params = `offset=${offset}&limit=${limit}${includeDeleted ? '&includeDeleted=true' : ''}`;
|
|
788
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/assets?${params}`, {
|
|
789
|
+
headers: { 'Content-Type': 'application/json' },
|
|
790
|
+
signal
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
if (!response.ok) {
|
|
794
|
+
const errorText = await response.text();
|
|
795
|
+
throw new Error(`Failed to list assets: ${response.status} ${response.statusText} - ${errorText}`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return (await response.json()) as PaginatedResponse<AssetListItem>;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
async getAsset(twinId: string, assetId: string, signal?: AbortSignal): Promise<AssetGet> {
|
|
802
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/assets/${assetId}`, {
|
|
803
|
+
headers: { 'Content-Type': 'application/json' },
|
|
804
|
+
signal
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
if (response.ok) {
|
|
808
|
+
return (await response.json()) as AssetGet;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (response.status === 404) {
|
|
812
|
+
throw new AssetNotFoundError(`Asset ${assetId} not found`);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const errorText = await response.text();
|
|
816
|
+
throw new Error(`Failed to get asset: ${response.status} ${response.statusText} - ${errorText}`);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
async createAssets(twinId: string, assets: AssetPost[], signal?: AbortSignal): Promise<AssetGet[]> {
|
|
820
|
+
const response = await this._httpClient.fetch(HttpMethod.Post, `${this._baseUrl}/${twinId}/assets`, {
|
|
821
|
+
headers: { 'Content-Type': 'application/json' },
|
|
822
|
+
body: JSON.stringify(assets),
|
|
823
|
+
signal
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
if (response.status === 201) {
|
|
827
|
+
return (await response.json()) as AssetGet[];
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (response.status === 400) {
|
|
831
|
+
const errorText = await response.text();
|
|
832
|
+
throw new AssetValidationError(`Asset validation failed: ${errorText}`);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const errorText = await response.text();
|
|
836
|
+
throw new Error(`Failed to create assets: ${response.status} ${response.statusText} - ${errorText}`);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
async updateAsset(twinId: string, assetId: string, asset: AssetPut, signal?: AbortSignal): Promise<AssetGet> {
|
|
840
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, `${this._baseUrl}/${twinId}/assets/${assetId}`, {
|
|
841
|
+
headers: { 'Content-Type': 'application/json' },
|
|
842
|
+
body: JSON.stringify(asset),
|
|
843
|
+
signal
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
if (response.ok) {
|
|
847
|
+
return (await response.json()) as AssetGet;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (response.status === 409) {
|
|
851
|
+
const conflictedAsset = (await response.json()) as AssetGet;
|
|
852
|
+
throw new AssetConflictError('Asset has been modified by another user', conflictedAsset);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (response.status === 400) {
|
|
856
|
+
const errorText = await response.text();
|
|
857
|
+
throw new AssetValidationError(`Asset validation failed: ${errorText}`);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const errorText = await response.text();
|
|
861
|
+
throw new Error(`Failed to update asset: ${response.status} ${response.statusText} - ${errorText}`);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
async deleteAsset(twinId: string, assetId: string, etag: string, signal?: AbortSignal): Promise<AssetGet> {
|
|
865
|
+
const response = await this._httpClient.fetch(
|
|
866
|
+
HttpMethod.Delete,
|
|
867
|
+
`${this._baseUrl}/${twinId}/assets/${assetId}`,
|
|
868
|
+
{
|
|
869
|
+
headers: { 'Content-Type': 'application/json' },
|
|
870
|
+
body: JSON.stringify({ etag }),
|
|
871
|
+
signal
|
|
872
|
+
}
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
if (response.ok) {
|
|
876
|
+
return (await response.json()) as AssetGet;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (response.status === 409) {
|
|
880
|
+
const conflictedAsset = (await response.json()) as AssetGet;
|
|
881
|
+
throw new AssetConflictError('Asset has been modified by another user', conflictedAsset);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (response.status === 404) {
|
|
885
|
+
throw new AssetNotFoundError(`Asset ${assetId} not found`);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const errorText = await response.text();
|
|
889
|
+
throw new Error(`Failed to delete asset: ${response.status} ${response.statusText} - ${errorText}`);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Restores a soft-deleted asset.
|
|
894
|
+
*
|
|
895
|
+
* @param twinId - The twin ID
|
|
896
|
+
* @param assetId - The asset ID to restore
|
|
897
|
+
* @param etag - The current etag of the deleted asset
|
|
898
|
+
* @returns The restored asset
|
|
899
|
+
* @throws {AssetConflictError} When the asset has been modified (409)
|
|
900
|
+
* @throws {AssetNotFoundError} When the asset does not exist (404)
|
|
901
|
+
*/
|
|
902
|
+
async restoreAsset(twinId: string, assetId: string, etag: string): Promise<AssetGet> {
|
|
903
|
+
const response = await this._httpClient.fetch(
|
|
904
|
+
HttpMethod.Put,
|
|
905
|
+
`${this._baseUrl}/${twinId}/assets/${assetId}/restore`,
|
|
906
|
+
{
|
|
907
|
+
headers: { 'Content-Type': 'application/json' },
|
|
908
|
+
body: JSON.stringify({ etag })
|
|
909
|
+
}
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
if (response.ok) {
|
|
913
|
+
return (await response.json()) as AssetGet;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (response.status === 409) {
|
|
917
|
+
const conflictedAsset = (await response.json()) as AssetGet;
|
|
918
|
+
throw new AssetConflictError('Asset has been modified by another user', conflictedAsset);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (response.status === 404) {
|
|
922
|
+
throw new AssetNotFoundError(`Asset ${assetId} not found`);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const errorText = await response.text();
|
|
926
|
+
throw new Error(`Failed to restore asset: ${response.status} ${response.statusText} - ${errorText}`);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Executes a batch of asset operations (upsert, delete, restore) atomically.
|
|
931
|
+
* All operations succeed or all are rolled back.
|
|
932
|
+
*
|
|
933
|
+
* @param twinId - The twin ID
|
|
934
|
+
* @param operations - Array of batch operations (max 1000)
|
|
935
|
+
* @returns Batch response with results for each operation
|
|
936
|
+
* @throws {AssetValidationError} When request validation fails (400) or insufficient permissions (403)
|
|
937
|
+
* @throws {AssetBatchError} When an operation conflicts (409) or asset not found (404) — the response contains per-item results with reverted statuses
|
|
938
|
+
*/
|
|
939
|
+
async batchAssets(twinId: string, operations: AssetBatchOperation[]): Promise<AssetBatchResponse> {
|
|
940
|
+
const response = await this._httpClient.fetch(HttpMethod.Post, `${this._baseUrl}/${twinId}/assets/batch`, {
|
|
941
|
+
headers: { 'Content-Type': 'application/json' },
|
|
942
|
+
body: JSON.stringify({ operations })
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
if (response.ok) {
|
|
946
|
+
return (await response.json()) as AssetBatchResponse;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (response.status === 400) {
|
|
950
|
+
const errorText = await response.text();
|
|
951
|
+
throw new AssetValidationError(`Asset batch validation failed: ${errorText}`);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (response.status === 403) {
|
|
955
|
+
const errorText = await response.text();
|
|
956
|
+
throw new AssetValidationError(`Asset batch forbidden: ${errorText}`);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (response.status === 404 || response.status === 409) {
|
|
960
|
+
const batchResponse = (await response.json()) as AssetBatchResponse;
|
|
961
|
+
throw new AssetBatchError(`Asset batch operation failed with status ${response.status}`, batchResponse);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const errorText = await response.text();
|
|
965
|
+
throw new Error(`Failed to execute asset batch: ${response.status} ${response.statusText} - ${errorText}`);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Retrieves multiple assets by their IDs in a single request.
|
|
970
|
+
*
|
|
971
|
+
* @param twinId - The twin ID
|
|
972
|
+
* @param ids - Array of asset IDs to retrieve (max 1000)
|
|
973
|
+
* @returns Batch response with summary and per-item results
|
|
974
|
+
* @throws {AssetValidationError} When request validation fails (400)
|
|
975
|
+
*/
|
|
976
|
+
async batchGetAssets(twinId: string, ids: string[]): Promise<AssetBatchGetResponse> {
|
|
977
|
+
const response = await this._httpClient.fetch(HttpMethod.Post, `${this._baseUrl}/${twinId}/assets/batch-get`, {
|
|
978
|
+
headers: { 'Content-Type': 'application/json' },
|
|
979
|
+
body: JSON.stringify({ ids })
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
if (response.ok) {
|
|
983
|
+
return (await response.json()) as AssetBatchGetResponse;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (response.status === 400) {
|
|
987
|
+
const errorText = await response.text();
|
|
988
|
+
throw new AssetValidationError(`Asset batch-get validation failed: ${errorText}`);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const errorText = await response.text();
|
|
992
|
+
throw new Error(`Failed to batch-get assets: ${response.status} ${response.statusText} - ${errorText}`);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
async listTwinFiles(
|
|
996
|
+
twinId: string,
|
|
997
|
+
options: TwinFileListOptions = {},
|
|
998
|
+
signal?: AbortSignal
|
|
999
|
+
): Promise<PaginatedResponse<TwinFileGet>> {
|
|
1000
|
+
const { includeDeleted = false, checkedIn = false, version, page = undefined, limit = 100 } = options;
|
|
1001
|
+
|
|
1002
|
+
if (version !== undefined && !checkedIn) {
|
|
1003
|
+
throw new Error('The "version" option can only be used when "checkedIn" is true.');
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const params = new URLSearchParams();
|
|
1007
|
+
if (includeDeleted) params.set('includeDeleted', 'true');
|
|
1008
|
+
if (checkedIn) params.set('checkedIn', 'true');
|
|
1009
|
+
if (version !== undefined) params.set('version', version.toString());
|
|
1010
|
+
if (page !== undefined) params.set('page', page);
|
|
1011
|
+
if (limit !== 100) params.set('limit', limit.toString());
|
|
1012
|
+
|
|
1013
|
+
const url = params.toString()
|
|
1014
|
+
? `${this._baseUrl}/${twinId}/files?${params}`
|
|
1015
|
+
: `${this._baseUrl}/${twinId}/files`;
|
|
1016
|
+
|
|
1017
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, url, {
|
|
1018
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1019
|
+
signal
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
if (response.ok) {
|
|
1023
|
+
return (await response.json()) as PaginatedResponse<TwinFileGet>;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (response.status === 400) {
|
|
1027
|
+
const errorBody = await response.json();
|
|
1028
|
+
throw new TwinFileValidationError(`Twin file listing validation failed: ${errorBody.detail}`, errorBody);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const errorText = await response.text();
|
|
1032
|
+
throw new Error(`Failed to list twin files: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async getTwinFile(twinId: string, fileId: string, signal?: AbortSignal): Promise<TwinFileGet> {
|
|
1036
|
+
const response = await this._httpClient.fetch(HttpMethod.Get, `${this._baseUrl}/${twinId}/files/${fileId}`, {
|
|
1037
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1038
|
+
signal
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
if (response.ok) {
|
|
1042
|
+
return (await response.json()) as TwinFileGet;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (response.status === 404) {
|
|
1046
|
+
throw new TwinFileNotFoundError(`Twin file ${fileId} not found`);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (response.status === 400) {
|
|
1050
|
+
const errorText = await response.text();
|
|
1051
|
+
throw new TwinFileValidationError(`Twin file validation failed: ${errorText}`);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
const errorText = await response.text();
|
|
1055
|
+
throw new Error(`Failed to get twin file: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
async createOrUpdateTwinFile(
|
|
1059
|
+
twinId: string,
|
|
1060
|
+
fileId: string,
|
|
1061
|
+
twinFile: TwinFilePut,
|
|
1062
|
+
signal?: AbortSignal
|
|
1063
|
+
): Promise<TwinFileGet> {
|
|
1064
|
+
const response = await this._httpClient.fetch(HttpMethod.Put, `${this._baseUrl}/${twinId}/files/${fileId}`, {
|
|
1065
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1066
|
+
body: JSON.stringify(twinFile),
|
|
1067
|
+
signal
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
if (response.ok || response.status === 201) {
|
|
1071
|
+
return (await response.json()) as TwinFileGet;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (response.status === 400) {
|
|
1075
|
+
const errorBody = await response.json();
|
|
1076
|
+
throw new TwinFileValidationError(`Twin file validation failed`, errorBody);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
if (response.status === 409) {
|
|
1080
|
+
const conflictedFile = (await response.json()) as TwinFileGet;
|
|
1081
|
+
throw new TwinFileConflictError('Twin file has been modified by another user', conflictedFile);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const errorText = await response.text();
|
|
1085
|
+
throw new Error(`Failed to create/update twin file: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
async deleteTwinFile(
|
|
1089
|
+
twinId: string,
|
|
1090
|
+
fileId: string,
|
|
1091
|
+
deleteRequest: TwinFileDelete,
|
|
1092
|
+
signal?: AbortSignal
|
|
1093
|
+
): Promise<TwinFileGet> {
|
|
1094
|
+
const response = await this._httpClient.fetch(HttpMethod.Delete, `${this._baseUrl}/${twinId}/files/${fileId}`, {
|
|
1095
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1096
|
+
body: JSON.stringify(deleteRequest),
|
|
1097
|
+
signal
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
if (response.ok) {
|
|
1101
|
+
return (await response.json()) as TwinFileGet;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (response.status === 404) {
|
|
1105
|
+
throw new TwinFileNotFoundError(`Twin file ${fileId} not found`);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (response.status === 409) {
|
|
1109
|
+
const conflictedFile = (await response.json()) as TwinFileGet;
|
|
1110
|
+
throw new TwinFileConflictError('Twin file has been modified by another user', conflictedFile);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (response.status === 400) {
|
|
1114
|
+
const errorText = await response.text();
|
|
1115
|
+
throw new TwinFileValidationError(`Twin file validation failed: ${errorText}`);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const errorText = await response.text();
|
|
1119
|
+
throw new Error(`Failed to delete twin file: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
async restoreTwinFile(twinId: string, fileId: string, etag: string, signal?: AbortSignal): Promise<TwinFileGet> {
|
|
1123
|
+
const response = await this._httpClient.fetch(
|
|
1124
|
+
HttpMethod.Put,
|
|
1125
|
+
`${this._baseUrl}/${twinId}/files/${fileId}/restore`,
|
|
1126
|
+
{
|
|
1127
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1128
|
+
body: JSON.stringify({ etag }),
|
|
1129
|
+
signal
|
|
1130
|
+
}
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
if (response.ok) {
|
|
1134
|
+
return (await response.json()) as TwinFileGet;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
if (response.status === 404) {
|
|
1138
|
+
throw new TwinFileNotFoundError(`Twin file ${fileId} not found`);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
if (response.status === 409) {
|
|
1142
|
+
const conflictedFile = (await response.json()) as TwinFileGet;
|
|
1143
|
+
throw new TwinFileConflictError('Twin file has been modified by another user', conflictedFile);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (response.status === 400) {
|
|
1147
|
+
const errorText = await response.text();
|
|
1148
|
+
throw new TwinFileValidationError(`Twin file validation failed: ${errorText}`);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const errorText = await response.text();
|
|
1152
|
+
throw new Error(`Failed to restore twin file: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async getTwinFileContent(twinId: string, fileId: string, signal?: AbortSignal): Promise<Blob> {
|
|
1156
|
+
const response = await this._httpClient.fetch(
|
|
1157
|
+
HttpMethod.Get,
|
|
1158
|
+
`${this._baseUrl}/${twinId}/files/${fileId}/content`,
|
|
1159
|
+
{ signal }
|
|
1160
|
+
);
|
|
1161
|
+
|
|
1162
|
+
if (response.ok) {
|
|
1163
|
+
return await response.blob();
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (response.status === 404) {
|
|
1167
|
+
throw new TwinFileNotFoundError(`Twin file ${fileId} not found`);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const errorText = await response.text();
|
|
1171
|
+
throw new Error(`Failed to get twin file content: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
async getTwinFileVersion(
|
|
1175
|
+
twinId: string,
|
|
1176
|
+
fileId: string,
|
|
1177
|
+
version: number,
|
|
1178
|
+
signal?: AbortSignal
|
|
1179
|
+
): Promise<TwinFileGet> {
|
|
1180
|
+
const response = await this._httpClient.fetch(
|
|
1181
|
+
HttpMethod.Get,
|
|
1182
|
+
`${this._baseUrl}/${twinId}/files/${fileId}/versions/${version}`,
|
|
1183
|
+
{ signal }
|
|
1184
|
+
);
|
|
1185
|
+
|
|
1186
|
+
if (response.ok) {
|
|
1187
|
+
return (await response.json()) as TwinFileGet;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
if (response.status === 404) {
|
|
1191
|
+
throw new TwinFileNotFoundError(`Twin file ${fileId} version ${version} not found`);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const errorText = await response.text();
|
|
1195
|
+
throw new Error(`Failed to get twin file version: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
async listTwinFileVersions(
|
|
1199
|
+
twinId: string,
|
|
1200
|
+
fileId: string,
|
|
1201
|
+
options?: TwinFileVersionListOptions,
|
|
1202
|
+
signal?: AbortSignal
|
|
1203
|
+
): Promise<PaginatedResponse<TwinFileGet>> {
|
|
1204
|
+
const params = new URLSearchParams();
|
|
1205
|
+
if (options?.includeDeleted !== undefined) params.append('includeDeleted', String(options.includeDeleted));
|
|
1206
|
+
if (options?.offset !== undefined) params.append('offset', String(options.offset));
|
|
1207
|
+
if (options?.limit !== undefined) params.append('limit', String(options.limit));
|
|
1208
|
+
|
|
1209
|
+
const response = await this._httpClient.fetch(
|
|
1210
|
+
HttpMethod.Get,
|
|
1211
|
+
`${this._baseUrl}/${twinId}/files/${fileId}/versions?${params.toString()}`,
|
|
1212
|
+
{ signal }
|
|
1213
|
+
);
|
|
1214
|
+
|
|
1215
|
+
if (response.ok) {
|
|
1216
|
+
return (await response.json()) as PaginatedResponse<TwinFileGet>;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (response.status === 400) {
|
|
1220
|
+
const errorBody = await response.json();
|
|
1221
|
+
throw new Error(`Bad request: ${errorBody.detail || response.statusText}`);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const errorText = await response.text();
|
|
1225
|
+
throw new Error(`Failed to list twin file versions: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
async getTwinFileVersionContent(
|
|
1229
|
+
twinId: string,
|
|
1230
|
+
fileId: string,
|
|
1231
|
+
version: number,
|
|
1232
|
+
signal?: AbortSignal
|
|
1233
|
+
): Promise<Blob> {
|
|
1234
|
+
const response = await this._httpClient.fetch(
|
|
1235
|
+
HttpMethod.Get,
|
|
1236
|
+
`${this._baseUrl}/${twinId}/files/${fileId}/versions/${version}/content`,
|
|
1237
|
+
{ signal }
|
|
1238
|
+
);
|
|
1239
|
+
|
|
1240
|
+
if (response.ok) {
|
|
1241
|
+
return await response.blob();
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
if (response.status === 404) {
|
|
1245
|
+
throw new TwinFileNotFoundError(`Twin file ${fileId} version ${version} content not found`);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
const errorText = await response.text();
|
|
1249
|
+
throw new Error(
|
|
1250
|
+
`Failed to get twin file version content: ${response.status} ${response.statusText} - ${errorText}`
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
async createOrUpdateTwinRaw(twinId: string, twin: unknown, signal?: AbortSignal): Promise<Response> {
|
|
1255
|
+
return await this._httpClient.fetch(HttpMethod.Put, `${this._baseUrl}/${twinId}`, {
|
|
1256
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1257
|
+
body: JSON.stringify(twin),
|
|
1258
|
+
signal
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
async createOrUpdateTwinFileRaw(
|
|
1263
|
+
twinId: string,
|
|
1264
|
+
fileId: string,
|
|
1265
|
+
twinFile: unknown,
|
|
1266
|
+
signal?: AbortSignal
|
|
1267
|
+
): Promise<Response> {
|
|
1268
|
+
return await this._httpClient.fetch(HttpMethod.Put, `${this._baseUrl}/${twinId}/files/${fileId}`, {
|
|
1269
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1270
|
+
body: JSON.stringify(twinFile),
|
|
1271
|
+
signal
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
}
|