@twinfinity/twin 6.1.1 → 6.1.2-ci.31342

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.
Files changed (99) hide show
  1. package/README.md +63 -2
  2. package/dist/BuildInfo.js +3 -3
  3. package/dist/TwinClient.d.ts +312 -9
  4. package/dist/TwinClient.d.ts.map +1 -1
  5. package/dist/TwinClient.js +321 -6
  6. package/dist/TwinClient.js.map +1 -1
  7. package/dist/errors.d.ts +120 -5
  8. package/dist/errors.d.ts.map +1 -1
  9. package/dist/errors.js +116 -2
  10. package/dist/errors.js.map +1 -1
  11. package/dist/tsconfig.tsbuildinfo +1 -1
  12. package/dist/types/generated.d.ts +95 -33
  13. package/dist/types/generated.d.ts.map +1 -1
  14. package/dist/types/twin-api.d.ts +54 -1
  15. package/dist/types/twin-api.d.ts.map +1 -1
  16. package/dist/types/twin-permissions-api.d.ts +226 -6
  17. package/dist/types/twin-permissions-api.d.ts.map +1 -1
  18. package/docs/assets/hierarchy.js +1 -1
  19. package/docs/assets/navigation.js +1 -1
  20. package/docs/assets/search.js +1 -1
  21. package/docs/classes/AssetAttachmentConflictError.html +1 -1
  22. package/docs/classes/AssetAttachmentNotFoundError.html +1 -1
  23. package/docs/classes/AssetBatchError.html +1 -1
  24. package/docs/classes/AssetCatalogTooLargeError.html +1 -1
  25. package/docs/classes/AssetCategoryConflictError.html +1 -1
  26. package/docs/classes/AssetCategoryForbiddenError.html +1 -1
  27. package/docs/classes/AssetCategoryNotFoundError.html +1 -1
  28. package/docs/classes/AssetCategoryValidationError.html +1 -1
  29. package/docs/classes/AssetConflictError.html +1 -1
  30. package/docs/classes/AssetForbiddenError.html +1 -1
  31. package/docs/classes/AssetNotFoundError.html +1 -1
  32. package/docs/classes/AssetPermissionsConflictError.html +16 -0
  33. package/docs/classes/AssetTypeConflictError.html +1 -1
  34. package/docs/classes/AssetTypeForbiddenError.html +1 -1
  35. package/docs/classes/AssetTypeNotFoundError.html +1 -1
  36. package/docs/classes/AssetTypeValidationError.html +1 -1
  37. package/docs/classes/AssetValidationError.html +1 -1
  38. package/docs/classes/ProcessorNotFoundError.html +1 -1
  39. package/docs/classes/TwinClient.html +251 -73
  40. package/docs/classes/TwinConflictError.html +1 -1
  41. package/docs/classes/TwinFileConflictError.html +1 -1
  42. package/docs/classes/TwinFileNotFoundError.html +1 -1
  43. package/docs/classes/TwinFileValidationError.html +1 -1
  44. package/docs/classes/TwinNotFoundError.html +4 -2
  45. package/docs/classes/TwinPermissionsConflictError.html +19 -0
  46. package/docs/classes/TwinPermissionsForbiddenError.html +12 -0
  47. package/docs/classes/TwinPrincipalNotFoundError.html +5 -0
  48. package/docs/classes/TwinTemporarilyUnavailableError.html +10 -0
  49. package/docs/classes/TwinUnauthenticatedError.html +1 -1
  50. package/docs/classes/TwinValidationError.html +1 -1
  51. package/docs/hierarchy.html +1 -1
  52. package/docs/index.html +56 -4
  53. package/docs/interfaces/AssetCatalogTooLargeErrorBody.html +2 -2
  54. package/docs/interfaces/AssetGroupGet.html +9 -0
  55. package/docs/interfaces/AssetGroupsGet.html +3 -0
  56. package/docs/interfaces/AssetPermissionsConflictErrorBody.html +12 -0
  57. package/docs/interfaces/AssetPermissionsPut.html +9 -0
  58. package/docs/interfaces/AssetPrincipalPermissionsGet.html +23 -0
  59. package/docs/interfaces/Coordinates.html +6 -0
  60. package/docs/interfaces/EntityCollection.html +7 -2
  61. package/docs/interfaces/EntityRepresentation.html +12 -0
  62. package/docs/interfaces/Facility.html +8 -3
  63. package/docs/interfaces/FacilityPart.html +5 -3
  64. package/docs/interfaces/ITwinClient.html +251 -73
  65. package/docs/interfaces/InheritedPermissions.html +8 -0
  66. package/docs/interfaces/ListTwinItem.html +18 -2
  67. package/docs/interfaces/Location.html +4 -1
  68. package/docs/interfaces/MyPermissions.html +2 -2
  69. package/docs/interfaces/MyTwinPermissions.html +5 -0
  70. package/docs/interfaces/Site.html +11 -4
  71. package/docs/interfaces/Space.html +6 -3
  72. package/docs/interfaces/TwinErrorBody.html +2 -2
  73. package/docs/interfaces/TwinGet.html +29 -11
  74. package/docs/interfaces/TwinGroupGet.html +10 -0
  75. package/docs/interfaces/TwinGroupsGet.html +3 -0
  76. package/docs/interfaces/TwinPermissionsConflictErrorBody.html +8 -0
  77. package/docs/interfaces/TwinPermissionsPut.html +11 -0
  78. package/docs/interfaces/TwinPrincipalPermissionsGet.html +34 -0
  79. package/docs/interfaces/TwinPut.html +8 -10
  80. package/docs/interfaces/TwinReference.html +4 -0
  81. package/docs/interfaces/TwinSpecification.html +7 -9
  82. package/docs/media/twin-management.md +197 -0
  83. package/docs/modules.html +1 -1
  84. package/docs/types/SinglePropertyValueType.html +1 -1
  85. package/docs/types/TwinBoundCapabilityName.html +8 -0
  86. package/docs/types/TwinCapability.html +11 -5
  87. package/docs/types/TwinCapabilityName.html +8 -0
  88. package/docs/types/TwinDiagnosticCode.html +1 -1
  89. package/docs/types/TwinErrorCause.html +1 -1
  90. package/docs/types/TwinErrorItemKind.html +1 -1
  91. package/docs/types/TwinPermissionsConflictCause.html +6 -0
  92. package/docs/types/TwinScopedCapability.html +9 -0
  93. package/docs/types/TwinScopedCapabilityName.html +8 -0
  94. package/package.json +3 -3
  95. package/src/TwinClient.ts +776 -15
  96. package/src/errors.ts +191 -4
  97. package/src/types/generated.ts +105 -35
  98. package/src/types/twin-api.ts +55 -1
  99. package/src/types/twin-permissions-api.ts +243 -6
package/src/errors.ts CHANGED
@@ -1,4 +1,13 @@
1
- import type { TwinGet, AssetGet, TwinFileGet, AssetBatchResponse, AssetCategoryGet, AssetTypeGet } from './types';
1
+ import type {
2
+ TwinGet,
3
+ AssetGet,
4
+ TwinFileGet,
5
+ AssetBatchResponse,
6
+ AssetCategoryGet,
7
+ AssetTypeGet,
8
+ TwinPrincipalPermissionsGet,
9
+ AssetPrincipalPermissionsGet
10
+ } from './types';
2
11
  import type { AttachmentGet } from './types/asset-attachment-api';
3
12
 
4
13
  /**
@@ -43,6 +52,10 @@ export type TwinErrorCause =
43
52
  | 'StringIdInUse'
44
53
  | 'CatalogTooLarge'
45
54
  | 'EtagRequired'
55
+ | 'InvalidPaginationToken'
56
+ | 'SystemPrincipalImmutable'
57
+ | 'ManagePermissionsNotGrantableToAllAuthenticated'
58
+ | 'ValidationFailed'
46
59
  // Anything the server emits that this client doesn't know about yet.
47
60
  // (string & {}) keeps the autocomplete on the listed values above.
48
61
  // eslint-disable-next-line @typescript-eslint/ban-types
@@ -60,6 +73,10 @@ export type TwinErrorItemKind =
60
73
  | 'DocumentChange'
61
74
  | 'AssetCategory'
62
75
  | 'AssetType'
76
+ | 'Attachment'
77
+ | 'Document'
78
+ | 'Principal'
79
+ | 'Request'
63
80
  // eslint-disable-next-line @typescript-eslint/ban-types
64
81
  | (string & {});
65
82
 
@@ -68,7 +85,9 @@ export interface TwinErrorBody {
68
85
  cause: TwinErrorCause;
69
86
  item?: {
70
87
  kind: TwinErrorItemKind;
71
- id: string;
88
+ // Absent when the reference names something that has no id — a `Request` kind
89
+ // carries only `field`, the request member that failed validation.
90
+ id?: string;
72
91
  version?: number;
73
92
  field?: string;
74
93
  };
@@ -87,8 +106,12 @@ export interface TwinErrorBody {
87
106
  * that switch on known causes will fall through to a default arm.
88
107
  */
89
108
  export async function readErrorBody(response: Response): Promise<TwinErrorBody> {
109
+ // Parse a clone, so that a body which turns out not to be JSON can still be read as text below.
110
+ // `json()` consumes the stream: on a real Response, reading the original here would leave the
111
+ // fallback unreachable and every non-JSON diagnostic would degrade to `statusText`.
112
+ const parseFrom = typeof response.clone === 'function' ? response.clone() : response;
90
113
  try {
91
- const parsed = (await response.json()) as TwinErrorBody;
114
+ const parsed = (await parseFrom.json()) as TwinErrorBody;
92
115
  // Defensive shape check — JSON-but-not-our-shape (e.g. an upstream
93
116
  // ProblemDetails payload from a non-Twinfinity component) should still
94
117
  // surface usefully. `detail` is the only field every consumer reads.
@@ -121,9 +144,13 @@ export class TwinConflictError extends Error {
121
144
  }
122
145
 
123
146
  export class TwinNotFoundError extends Error {
124
- constructor(message: string) {
147
+ /** The server's envelope, when there was one — `item.kind` names what was not found. */
148
+ public readonly errorBody?: TwinErrorBody;
149
+
150
+ constructor(message: string, errorBody?: TwinErrorBody) {
125
151
  super(message);
126
152
  this.name = 'TwinNotFoundError';
153
+ this.errorBody = errorBody;
127
154
  }
128
155
  }
129
156
 
@@ -377,3 +404,163 @@ export class TwinUnauthenticatedError extends Error {
377
404
  this.name = 'TwinUnauthenticatedError';
378
405
  }
379
406
  }
407
+
408
+ // ---------- Twin-scoped permissions ----------
409
+
410
+ /**
411
+ * The `cause` values the twin-scoped permission routes emit on a 409. A subset of
412
+ * `TwinConflictCause` in `backend/services/Twinfinity.Twin/Controllers/Model/TwinStatus.cs`:
413
+ * that enum's `GroupIdAlreadyInUse` and `ScopeIdAlreadyInUse` are raised by twin *creation*,
414
+ * which answers through {@link TwinConflictError}, never through this surface. Carries the same
415
+ * forward-compat tail as {@link TwinErrorCause}.
416
+ */
417
+ export type TwinPermissionsConflictCause =
418
+ | 'EtagMismatch'
419
+ | 'EtagRequired'
420
+ | 'ScopesNotProvisioned'
421
+ // eslint-disable-next-line @typescript-eslint/ban-types
422
+ | (string & {});
423
+
424
+ /**
425
+ * The 409 envelope of the twin-scoped permission surface: the standard
426
+ * {@link TwinErrorBody} plus the server's view of the entry the caller lost a race on,
427
+ * whenever the service could report it.
428
+ */
429
+ export interface TwinPermissionsConflictErrorBody extends TwinErrorBody {
430
+ cause: TwinPermissionsConflictCause;
431
+ currentState?: TwinPrincipalPermissionsGet;
432
+ }
433
+
434
+ /**
435
+ * Thrown by the twin permission mutation and listing methods on a 409.
436
+ *
437
+ * The three causes need different handling, which is why they are distinguishable
438
+ * rather than collapsed into one "modified by another user":
439
+ * - `EtagMismatch` — the etag is stale; re-read (or use {@link currentState}) and retry.
440
+ * - `EtagRequired` — the write omitted an etag but the principal already held an entry,
441
+ * so it would have replaced state the caller never read. Retried by reading at all.
442
+ * - `ScopesNotProvisioned` — the twin owns no Twinfinity scope, so there is no permission
443
+ * state to read or write. Nothing provisions on read, so retrying will not help.
444
+ */
445
+ export class TwinPermissionsConflictError extends Error {
446
+ /**
447
+ * Which of the three conflicts this is. Named `conflictCause` rather than `cause` so the
448
+ * standard `Error.cause` — whose consumers expect a wrapped error, not a string — is left
449
+ * alone; the same value is on {@link errorBody}`.cause`.
450
+ */
451
+ public readonly conflictCause: TwinPermissionsConflictCause;
452
+ /** The entry as the server holds it, including the etag to retry with. */
453
+ public readonly currentState?: TwinPrincipalPermissionsGet;
454
+ public readonly errorBody: TwinPermissionsConflictErrorBody;
455
+
456
+ constructor(message: string, errorBody: TwinPermissionsConflictErrorBody) {
457
+ super(message);
458
+ this.name = 'TwinPermissionsConflictError';
459
+ this.conflictCause = errorBody.cause;
460
+ this.currentState = errorBody.currentState;
461
+ this.errorBody = errorBody;
462
+ }
463
+ }
464
+
465
+ /**
466
+ * Thrown on a 403 from the twin-scoped permission surface: the caller can see the twin
467
+ * but does not hold `ManageTwinPermissions` on it. Distinct from
468
+ * {@link TwinNotFoundError}, which is what a twin the caller cannot see at all answers —
469
+ * the surface refuses to confirm such a twin exists.
470
+ */
471
+ export class TwinPermissionsForbiddenError extends Error {
472
+ /**
473
+ * The server's envelope, when there was one. Its presence is load-bearing: a genuine refusal
474
+ * carries `cause: 'InsufficientPermissions'`, while a Twin service that could not evaluate the
475
+ * request at all (a customer-database lookup that threw, a missing twin feature) falls through
476
+ * to the framework's default forbid handler and answers a 403 with no body. Without this a
477
+ * caller cannot tell "you may not" from "we could not tell", and would report the first.
478
+ */
479
+ public readonly errorBody?: TwinErrorBody;
480
+
481
+ constructor(message: string, errorBody?: TwinErrorBody) {
482
+ super(message);
483
+ this.name = 'TwinPermissionsForbiddenError';
484
+ this.errorBody = errorBody;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Thrown when a 404 names the principal rather than the twin — an id that no principal
490
+ * in the tenant carries, or one holding nothing on the twin for a `DELETE` to revoke.
491
+ */
492
+ export class TwinPrincipalNotFoundError extends Error {
493
+ public readonly errorBody?: TwinErrorBody;
494
+
495
+ constructor(message: string, errorBody?: TwinErrorBody) {
496
+ super(message);
497
+ this.name = 'TwinPrincipalNotFoundError';
498
+ this.errorBody = errorBody;
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Thrown on a 503: the Twin service could neither carry out the request nor answer it
504
+ * truthfully. The request is unchanged and safe to retry as-is — unlike
505
+ * {@link TwinPermissionsConflictError}, which never clears on its own.
506
+ */
507
+ export class TwinTemporarilyUnavailableError extends Error {
508
+ public readonly errorBody?: TwinErrorBody;
509
+ /**
510
+ * The `Retry-After` the service sent, in seconds, when it sent one. The Twin service pairs the
511
+ * status with this header deliberately, so that "retrying is worthwhile" does not rest on the
512
+ * status alone — pass it to whatever backoff the caller uses instead of inventing a delay.
513
+ */
514
+ public readonly retryAfterSeconds?: number;
515
+
516
+ constructor(message: string, errorBody?: TwinErrorBody, retryAfterSeconds?: number) {
517
+ super(message);
518
+ this.name = 'TwinTemporarilyUnavailableError';
519
+ this.errorBody = errorBody;
520
+ this.retryAfterSeconds = retryAfterSeconds;
521
+ }
522
+ }
523
+
524
+ // ---------- Asset-domain permissions ----------
525
+
526
+ /**
527
+ * The 409 envelope of the customer-wide asset permission surface. The same shape as
528
+ * {@link TwinPermissionsConflictErrorBody}, but `currentState` is a domain entry rather than a
529
+ * twin's: the two carry different identity fields (`id` versus `principalId`) and different
530
+ * capability vocabularies, so a caller that acts on the state it was handed has to know which it
531
+ * has.
532
+ *
533
+ * `ScopesNotProvisioned` never appears here — the asset management scope is seeded once per
534
+ * customer rather than provisioned per resource.
535
+ */
536
+ export interface AssetPermissionsConflictErrorBody extends TwinErrorBody {
537
+ cause: TwinPermissionsConflictCause;
538
+ currentState?: AssetPrincipalPermissionsGet;
539
+ }
540
+
541
+ /**
542
+ * Thrown by the asset permission mutation methods on a 409, which on this surface is always an
543
+ * etag failure:
544
+ * - `EtagMismatch` — the etag is stale; re-read (or use {@link currentState}) and retry. Expect
545
+ * this routinely: the scope's etag is customer-wide, so any administrator's write invalidates it.
546
+ * - `EtagRequired` — the write omitted an etag but the principal already held capabilities, so it
547
+ * would have replaced state the caller never read.
548
+ */
549
+ export class AssetPermissionsConflictError extends Error {
550
+ /**
551
+ * Which of the two conflicts this is. Named `conflictCause` rather than `cause` so the standard
552
+ * `Error.cause` — whose consumers expect a wrapped error, not a string — is left alone.
553
+ */
554
+ public readonly conflictCause: TwinPermissionsConflictCause;
555
+ /** The entry as the server holds it, including the etag to retry with. */
556
+ public readonly currentState?: AssetPrincipalPermissionsGet;
557
+ public readonly errorBody: AssetPermissionsConflictErrorBody;
558
+
559
+ constructor(message: string, errorBody: AssetPermissionsConflictErrorBody) {
560
+ super(message);
561
+ this.name = 'AssetPermissionsConflictError';
562
+ this.conflictCause = errorBody.cause;
563
+ this.currentState = errorBody.currentState;
564
+ this.errorBody = errorBody;
565
+ }
566
+ }
@@ -143,7 +143,11 @@ export type TwinDiagnosticCode =
143
143
  | 'InvalidStructure'
144
144
  | 'MissingElement'
145
145
  | 'MissingFile'
146
- | 'PatchTargetNotFound';
146
+ | 'PatchTargetNotFound'
147
+ | 'DuplicateGlobalId'
148
+ | 'NonFiniteTransform'
149
+ | 'NonFiniteGeometryItemTransform'
150
+ | 'ArtifactMergeFailed';
147
151
 
148
152
  /**
149
153
  * Specification of a Twinfinity Twin
@@ -153,10 +157,6 @@ export interface TwinSpecification {
153
157
  * Spaces that are not part of any facility
154
158
  */
155
159
  aggregatedSpaces?: string[];
156
- /**
157
- * Ids of Twins that are part of this Twin
158
- */
159
- aggregatedTwins?: string[];
160
160
  elementQuantities?: ElementQuantities[];
161
161
  /**
162
162
  * Collection of entities that are part of the twin
@@ -170,17 +170,15 @@ export interface TwinSpecification {
170
170
  * Unique identifier for the twin
171
171
  */
172
172
  id?: string;
173
+ propertySets?: PropertySet[];
173
174
  /**
174
- * Twin location in WGS84
175
- */
176
- location?: Location;
177
- /**
178
- * Name of the Twin
175
+ * Id of the entity that is the root (top) node of the Twin's entity tree. Must reference a
176
+ * site or facility present in the EntityCollection.
179
177
  */
180
- name: string;
181
- propertySets?: PropertySet[];
178
+ rootEntity: string;
182
179
  /**
183
- * The site of the Twin
180
+ * Legacy. The site of the Twin. Deprecated: use rootEntity, which is authoritative for the
181
+ * root of the Twin's entity tree.
184
182
  */
185
183
  site?: string;
186
184
  }
@@ -228,6 +226,11 @@ export interface EntityCollection {
228
226
  * Facility part entities in the Twin
229
227
  */
230
228
  facilityParts?: { [key: string]: FacilityPart };
229
+ /**
230
+ * Representation relations declaring that a twin entity is represented by an entity in a
231
+ * model file, keyed by relation id
232
+ */
233
+ representations?: { [key: string]: EntityRepresentation };
231
234
  /**
232
235
  * Site entities in the Twin
233
236
  */
@@ -236,6 +239,10 @@ export interface EntityCollection {
236
239
  * Space entities in the Twin
237
240
  */
238
241
  spaces?: { [key: string]: Space };
242
+ /**
243
+ * Twin reference entities in the Twin
244
+ */
245
+ twinReferences?: { [key: string]: TwinReference };
239
246
  [property: string]: any;
240
247
  }
241
248
 
@@ -259,11 +266,18 @@ export interface Facility {
259
266
  * Type of facility (e.g. IfcBuilding)
260
267
  */
261
268
  class: FacilityClass;
269
+ description?: string;
262
270
  elementQuantities?: ElementQuantities[];
263
271
  /**
264
272
  * Unique identifier for the building
265
273
  */
266
274
  id: string;
275
+ /**
276
+ * Location of the facility in WGS84. When the facility is the twin's root entity, this is
277
+ * the twin's location.
278
+ */
279
+ location?: Location;
280
+ longName?: string;
267
281
  name?: string;
268
282
  propertySets?: PropertySet[];
269
283
  }
@@ -275,6 +289,31 @@ export interface Facility {
275
289
  */
276
290
  export type FacilityClass = 'IfcBridge' | 'IfcBuilding' | 'IfcMarineFacility' | 'IfcRailway' | 'IfcRoad';
277
291
 
292
+ /**
293
+ * Location of the facility in WGS84. When the facility is the twin's root entity, this is
294
+ * the twin's location.
295
+ *
296
+ * A location in WGS84
297
+ *
298
+ * Location of the site in WGS84. When the site is the twin's root entity, this is the
299
+ * twin's location.
300
+ */
301
+ export interface Location {
302
+ /**
303
+ * Metres above sea level
304
+ */
305
+ elevation?: number;
306
+ epsg: EpsgType;
307
+ latitude: number;
308
+ longitude: number;
309
+ /**
310
+ * Vector pointing to the true north
311
+ */
312
+ trueNorth?: number[];
313
+ }
314
+
315
+ export type EpsgType = '4326';
316
+
278
317
  export interface PropertySet {
279
318
  name: string;
280
319
  /**
@@ -412,7 +451,13 @@ export type SinglePropertyValueType =
412
451
  | 'IfcVolumeMeasure'
413
452
  | 'IfcVolumetricFlowRateMeasure'
414
453
  | 'IfcWarpingConstantMeasure'
415
- | 'IfcWarpingMomentMeasure';
454
+ | 'IfcWarpingMomentMeasure'
455
+ | 'IfcAreaDensityMeasure'
456
+ | 'IfcContextDependentMeasure'
457
+ | 'IfcCurvatureMeasure'
458
+ | 'IfcSectionalAreaIntegralMeasure'
459
+ | 'IfcShearModulusMeasure'
460
+ | 'IfcSoundPowerLevelMeasure';
416
461
 
417
462
  /**
418
463
  * Distinct part of the facility (e.g. storey or railway part)
@@ -431,11 +476,13 @@ export interface FacilityPart {
431
476
  * Type of facility part (e.g. IfcBuildingStorey)
432
477
  */
433
478
  class: FacilityPartClass;
479
+ description?: string;
434
480
  elementQuantities?: ElementQuantities[];
435
481
  /**
436
482
  * Unique identifier for the facility part
437
483
  */
438
484
  id: string;
485
+ longName?: string;
439
486
  name?: string;
440
487
  propertySets?: PropertySet[];
441
488
  }
@@ -452,6 +499,26 @@ export type FacilityPartClass =
452
499
  | 'IfcRailwayPart'
453
500
  | 'IfcRoadPart';
454
501
 
502
+ /**
503
+ * An objectified relation declaring that a twin entity is represented by another entity.
504
+ * Modelled as a relation (not an inline attribute) so it stays addressable and extensible,
505
+ * mirroring how twinReferences objectifies twin-to-twin relations.
506
+ */
507
+ export interface EntityRepresentation {
508
+ /**
509
+ * Reference to the entity that is represented, e.g. twin~twin:{twinId}.entity:{entityId}
510
+ * for an entity in a twin. Must name an entity: the shared grammar also admits a bare
511
+ * domain (a scope, not an entity), which the service refuses here
512
+ */
513
+ relatingEntity: string;
514
+ /**
515
+ * Reference to the entity that provides this entity's representation, e.g.
516
+ * twin~twin:{twinId}.file:{fileId}/entities/{ifcGid} for an IFC product within one of the
517
+ * twin's model files. Must name an entity, as relatingEntity must
518
+ */
519
+ representedBy: string;
520
+ }
521
+
455
522
  /**
456
523
  * A site is a defined area of land, possibly covered with water, upon which constructions
457
524
  * are located
@@ -469,15 +536,26 @@ export interface Site {
469
536
  * Spaces that are not part of any facility
470
537
  */
471
538
  aggregatedSpaces?: string[];
539
+ /**
540
+ * Twin references nested directly under this site
541
+ */
542
+ aggregatedTwinReferences?: string[];
472
543
  /**
473
544
  * Type of site (i.e. IfcSite)
474
545
  */
475
546
  class?: SiteType;
547
+ description?: string;
476
548
  elementQuantities?: ElementQuantities[];
477
549
  /**
478
550
  * Unique identifier for the site
479
551
  */
480
552
  id?: string;
553
+ /**
554
+ * Location of the site in WGS84. When the site is the twin's root entity, this is the
555
+ * twin's location.
556
+ */
557
+ location?: Location;
558
+ longName?: string;
481
559
  /**
482
560
  * Name of the site
483
561
  */
@@ -507,12 +585,15 @@ export interface Space {
507
585
  * Type of space (e.g. IfcSpace)
508
586
  */
509
587
  class: SpaceType;
588
+ description?: string;
510
589
  elementQuantities?: ElementQuantities[];
511
590
  /**
512
591
  * Unique identifier for the space
513
592
  */
514
593
  id: string;
594
+ longName?: string;
515
595
  name?: string;
596
+ objectType?: string;
516
597
  propertySets?: PropertySet[];
517
598
  }
518
599
 
@@ -521,6 +602,16 @@ export interface Space {
521
602
  */
522
603
  export type SpaceType = 'IfcSpace';
523
604
 
605
+ /**
606
+ * A reference to another Twin nested within this Twin's spatial hierarchy
607
+ */
608
+ export interface TwinReference {
609
+ /**
610
+ * Id of the referenced (nested) Twin
611
+ */
612
+ twin: string;
613
+ }
614
+
524
615
  export interface FilePatchSet {
525
616
  /**
526
617
  * Ordered list of patch operations to apply
@@ -551,24 +642,3 @@ export interface FilePatch {
551
642
  * The patch operation type
552
643
  */
553
644
  export type Op = 'replace' | 'remove';
554
-
555
- /**
556
- * Twin location in WGS84
557
- *
558
- * A location in WGS84
559
- */
560
- export interface Location {
561
- /**
562
- * Metres above sea level
563
- */
564
- elevation?: number;
565
- epsg: EpsgType;
566
- latitude: number;
567
- longitude: number;
568
- /**
569
- * Vector pointing to the true north
570
- */
571
- trueNorth?: number[];
572
- }
573
-
574
- export type EpsgType = '4326';
@@ -1,4 +1,5 @@
1
- import type { PropertySet, ElementQuantities, TwinSpecification, Diagnostics } from './generated';
1
+ import type { PropertySet, ElementQuantities, TwinSpecification, Diagnostics, EpsgType, Location } from './generated';
2
+ import type { TwinBoundCapabilityName } from './twin-permissions-api';
2
3
 
3
4
  export interface TwinFileReference {
4
5
  fileId: string;
@@ -6,6 +7,14 @@ export interface TwinFileReference {
6
7
  twinNodeId?: string;
7
8
  }
8
9
 
10
+ /** WGS84 coordinates of a twin, as returned on the list endpoint (coordinates only — the full
11
+ * Location, including elevation and trueNorth, is available on the single-twin GET). */
12
+ export interface Coordinates {
13
+ epsg: EpsgType;
14
+ latitude: number;
15
+ longitude: number;
16
+ }
17
+
9
18
  export interface ListTwinItem {
10
19
  id: string;
11
20
  name: string;
@@ -20,8 +29,32 @@ export interface ListTwinItem {
20
29
  createdUtc: string;
21
30
  modifiedBy: string;
22
31
  modifiedUtc: string;
32
+ /** Ids of the Twins nested within this Twin, derived by the service from its twin references. */
33
+ aggregatedTwins: string[];
34
+ /** Deduplicated, sorted set of IFC entity classes the twin contains (e.g. IfcBuilding, IfcSpace),
35
+ * computed by the service from the specification's entity collection. */
36
+ classes: string[];
37
+ /** Id of the Twin that references this Twin (its parent), or omitted for a root Twin. */
38
+ parentTwin?: string;
39
+ /** WGS84 coordinates, present only when the twin's specification declares a location. */
40
+ location?: Coordinates;
41
+ /**
42
+ * What the *calling principal* may do with this twin — the whole affordance decision for the
43
+ * row, so a list of a thousand twins needs no probe per row. Never the twin's access-control
44
+ * list: that is `listTwinPermissions(twinId)`, which is administration-gated.
45
+ *
46
+ * Mixes both capability vocabularies, as the twin-scoped probe does. Where each capability was
47
+ * granted is answered by `listTwinPermissions(twinId)`, whose rows split `permissions` from
48
+ * `inheritedPermissions`. Absent on `listTwinVersions`, which does not resolve it.
49
+ */
50
+ effectivePermissions?: TwinBoundCapabilityName[];
23
51
  }
24
52
 
53
+ /**
54
+ * A twin as returned by the service — by `getTwin` and `getTwinVersion`, and by the write methods
55
+ * (`createOrUpdateTwin`, `deleteTwin`, `restoreTwin`), which answer with the same shape. Writing a
56
+ * twin and re-rendering it therefore needs no second fetch.
57
+ */
25
58
  export interface TwinGet extends TwinSpecification {
26
59
  id: string;
27
60
  etag: string;
@@ -34,6 +67,27 @@ export interface TwinGet extends TwinSpecification {
34
67
  modifiedUtc: string;
35
68
  fileReferences: TwinFileReference[];
36
69
  computedPropertySets?: PropertySet[];
70
+ /** Ids of the Twins nested within this Twin, derived by the service from its twin references. */
71
+ aggregatedTwins: string[];
72
+ /** Id of the Twin that references this Twin (its parent), or omitted for a root Twin. */
73
+ parentTwin?: string;
74
+ /** Name of the Twin, derived by the service from the root entity. Read-only; not part of the
75
+ * stored specification. */
76
+ name: string;
77
+ /** Full WGS84 location of the Twin (including elevation and trueNorth), derived by the service
78
+ * from the root entity. Read-only; not part of the stored specification. */
79
+ location?: Location;
80
+ /**
81
+ * Everything the *calling principal* may do with this twin: the capabilities its own scopes
82
+ * confer, unioned with the twin-bound asset ones held customer-wide. Calculated by the service on
83
+ * every read, never stored, and never accepted in a `TwinPut` body.
84
+ *
85
+ * The verdict, without its provenance — a fetched twin says no more than a listing row does.
86
+ * Which capabilities are granted here and which come from elsewhere is answered by
87
+ * `listTwinPermissions(twinId)`, where they are administered. Never the twin's access-control
88
+ * list either: this describes the caller, not every principal.
89
+ */
90
+ effectivePermissions: TwinBoundCapabilityName[];
37
91
  }
38
92
 
39
93
  export interface TwinPut extends TwinSpecification {