@twinfinity/twin 6.1.0 → 6.1.2-ci.30738

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 (97) 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 +93 -31
  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/TwinBoundCapabilityName.html +8 -0
  85. package/docs/types/TwinCapability.html +11 -5
  86. package/docs/types/TwinCapabilityName.html +8 -0
  87. package/docs/types/TwinErrorCause.html +1 -1
  88. package/docs/types/TwinErrorItemKind.html +1 -1
  89. package/docs/types/TwinPermissionsConflictCause.html +6 -0
  90. package/docs/types/TwinScopedCapability.html +9 -0
  91. package/docs/types/TwinScopedCapabilityName.html +8 -0
  92. package/package.json +3 -3
  93. package/src/TwinClient.ts +776 -15
  94. package/src/errors.ts +191 -4
  95. package/src/types/generated.ts +93 -33
  96. package/src/types/twin-api.ts +55 -1
  97. 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
+ }
@@ -153,10 +153,6 @@ export interface TwinSpecification {
153
153
  * Spaces that are not part of any facility
154
154
  */
155
155
  aggregatedSpaces?: string[];
156
- /**
157
- * Ids of Twins that are part of this Twin
158
- */
159
- aggregatedTwins?: string[];
160
156
  elementQuantities?: ElementQuantities[];
161
157
  /**
162
158
  * Collection of entities that are part of the twin
@@ -170,17 +166,15 @@ export interface TwinSpecification {
170
166
  * Unique identifier for the twin
171
167
  */
172
168
  id?: string;
169
+ propertySets?: PropertySet[];
173
170
  /**
174
- * Twin location in WGS84
175
- */
176
- location?: Location;
177
- /**
178
- * Name of the Twin
171
+ * Id of the entity that is the root (top) node of the Twin's entity tree. Must reference a
172
+ * site or facility present in the EntityCollection.
179
173
  */
180
- name: string;
181
- propertySets?: PropertySet[];
174
+ rootEntity: string;
182
175
  /**
183
- * The site of the Twin
176
+ * Legacy. The site of the Twin. Deprecated: use rootEntity, which is authoritative for the
177
+ * root of the Twin's entity tree.
184
178
  */
185
179
  site?: string;
186
180
  }
@@ -228,6 +222,11 @@ export interface EntityCollection {
228
222
  * Facility part entities in the Twin
229
223
  */
230
224
  facilityParts?: { [key: string]: FacilityPart };
225
+ /**
226
+ * Representation relations declaring that a twin entity is represented by an entity in a
227
+ * model file, keyed by relation id
228
+ */
229
+ representations?: { [key: string]: EntityRepresentation };
231
230
  /**
232
231
  * Site entities in the Twin
233
232
  */
@@ -236,6 +235,10 @@ export interface EntityCollection {
236
235
  * Space entities in the Twin
237
236
  */
238
237
  spaces?: { [key: string]: Space };
238
+ /**
239
+ * Twin reference entities in the Twin
240
+ */
241
+ twinReferences?: { [key: string]: TwinReference };
239
242
  [property: string]: any;
240
243
  }
241
244
 
@@ -259,11 +262,18 @@ export interface Facility {
259
262
  * Type of facility (e.g. IfcBuilding)
260
263
  */
261
264
  class: FacilityClass;
265
+ description?: string;
262
266
  elementQuantities?: ElementQuantities[];
263
267
  /**
264
268
  * Unique identifier for the building
265
269
  */
266
270
  id: string;
271
+ /**
272
+ * Location of the facility in WGS84. When the facility is the twin's root entity, this is
273
+ * the twin's location.
274
+ */
275
+ location?: Location;
276
+ longName?: string;
267
277
  name?: string;
268
278
  propertySets?: PropertySet[];
269
279
  }
@@ -275,6 +285,31 @@ export interface Facility {
275
285
  */
276
286
  export type FacilityClass = 'IfcBridge' | 'IfcBuilding' | 'IfcMarineFacility' | 'IfcRailway' | 'IfcRoad';
277
287
 
288
+ /**
289
+ * Location of the facility in WGS84. When the facility is the twin's root entity, this is
290
+ * the twin's location.
291
+ *
292
+ * A location in WGS84
293
+ *
294
+ * Location of the site in WGS84. When the site is the twin's root entity, this is the
295
+ * twin's location.
296
+ */
297
+ export interface Location {
298
+ /**
299
+ * Metres above sea level
300
+ */
301
+ elevation?: number;
302
+ epsg: EpsgType;
303
+ latitude: number;
304
+ longitude: number;
305
+ /**
306
+ * Vector pointing to the true north
307
+ */
308
+ trueNorth?: number[];
309
+ }
310
+
311
+ export type EpsgType = '4326';
312
+
278
313
  export interface PropertySet {
279
314
  name: string;
280
315
  /**
@@ -431,11 +466,13 @@ export interface FacilityPart {
431
466
  * Type of facility part (e.g. IfcBuildingStorey)
432
467
  */
433
468
  class: FacilityPartClass;
469
+ description?: string;
434
470
  elementQuantities?: ElementQuantities[];
435
471
  /**
436
472
  * Unique identifier for the facility part
437
473
  */
438
474
  id: string;
475
+ longName?: string;
439
476
  name?: string;
440
477
  propertySets?: PropertySet[];
441
478
  }
@@ -452,6 +489,26 @@ export type FacilityPartClass =
452
489
  | 'IfcRailwayPart'
453
490
  | 'IfcRoadPart';
454
491
 
492
+ /**
493
+ * An objectified relation declaring that a twin entity is represented by another entity.
494
+ * Modelled as a relation (not an inline attribute) so it stays addressable and extensible,
495
+ * mirroring how twinReferences objectifies twin-to-twin relations.
496
+ */
497
+ export interface EntityRepresentation {
498
+ /**
499
+ * Reference to the entity that is represented, e.g. twin~twin:{twinId}.entity:{entityId}
500
+ * for an entity in a twin. Must name an entity: the shared grammar also admits a bare
501
+ * domain (a scope, not an entity), which the service refuses here
502
+ */
503
+ relatingEntity: string;
504
+ /**
505
+ * Reference to the entity that provides this entity's representation, e.g.
506
+ * twin~twin:{twinId}.file:{fileId}/entities/{ifcGid} for an IFC product within one of the
507
+ * twin's model files. Must name an entity, as relatingEntity must
508
+ */
509
+ representedBy: string;
510
+ }
511
+
455
512
  /**
456
513
  * A site is a defined area of land, possibly covered with water, upon which constructions
457
514
  * are located
@@ -469,15 +526,26 @@ export interface Site {
469
526
  * Spaces that are not part of any facility
470
527
  */
471
528
  aggregatedSpaces?: string[];
529
+ /**
530
+ * Twin references nested directly under this site
531
+ */
532
+ aggregatedTwinReferences?: string[];
472
533
  /**
473
534
  * Type of site (i.e. IfcSite)
474
535
  */
475
536
  class?: SiteType;
537
+ description?: string;
476
538
  elementQuantities?: ElementQuantities[];
477
539
  /**
478
540
  * Unique identifier for the site
479
541
  */
480
542
  id?: string;
543
+ /**
544
+ * Location of the site in WGS84. When the site is the twin's root entity, this is the
545
+ * twin's location.
546
+ */
547
+ location?: Location;
548
+ longName?: string;
481
549
  /**
482
550
  * Name of the site
483
551
  */
@@ -507,12 +575,15 @@ export interface Space {
507
575
  * Type of space (e.g. IfcSpace)
508
576
  */
509
577
  class: SpaceType;
578
+ description?: string;
510
579
  elementQuantities?: ElementQuantities[];
511
580
  /**
512
581
  * Unique identifier for the space
513
582
  */
514
583
  id: string;
584
+ longName?: string;
515
585
  name?: string;
586
+ objectType?: string;
516
587
  propertySets?: PropertySet[];
517
588
  }
518
589
 
@@ -521,6 +592,16 @@ export interface Space {
521
592
  */
522
593
  export type SpaceType = 'IfcSpace';
523
594
 
595
+ /**
596
+ * A reference to another Twin nested within this Twin's spatial hierarchy
597
+ */
598
+ export interface TwinReference {
599
+ /**
600
+ * Id of the referenced (nested) Twin
601
+ */
602
+ twin: string;
603
+ }
604
+
524
605
  export interface FilePatchSet {
525
606
  /**
526
607
  * Ordered list of patch operations to apply
@@ -551,24 +632,3 @@ export interface FilePatch {
551
632
  * The patch operation type
552
633
  */
553
634
  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 {