@twinfinity/twin 6.0.2-ci.29168 → 6.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/BuildInfo.js +2 -2
  2. package/dist/TwinClient.d.ts +68 -0
  3. package/dist/TwinClient.d.ts.map +1 -1
  4. package/dist/TwinClient.js +152 -1
  5. package/dist/TwinClient.js.map +1 -1
  6. package/dist/errors.d.ts +9 -1
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +14 -0
  9. package/dist/errors.js.map +1 -1
  10. package/dist/tsconfig.tsbuildinfo +1 -1
  11. package/dist/types/asset-attachment-api.d.ts +51 -0
  12. package/dist/types/asset-attachment-api.d.ts.map +1 -0
  13. package/dist/types/asset-attachment-api.js +8 -0
  14. package/dist/types/asset-attachment-api.js.map +1 -0
  15. package/dist/types/index.d.ts +1 -0
  16. package/dist/types/index.d.ts.map +1 -1
  17. package/dist/types/index.js +1 -0
  18. package/dist/types/index.js.map +1 -1
  19. package/docs/assets/hierarchy.js +1 -1
  20. package/docs/assets/navigation.js +1 -1
  21. package/docs/assets/search.js +1 -1
  22. package/docs/classes/AssetAttachmentConflictError.html +3 -0
  23. package/docs/classes/AssetAttachmentNotFoundError.html +2 -0
  24. package/docs/classes/AssetBatchError.html +1 -1
  25. package/docs/classes/AssetCatalogTooLargeError.html +1 -1
  26. package/docs/classes/AssetCategoryConflictError.html +1 -1
  27. package/docs/classes/AssetCategoryForbiddenError.html +1 -1
  28. package/docs/classes/AssetCategoryNotFoundError.html +1 -1
  29. package/docs/classes/AssetCategoryValidationError.html +1 -1
  30. package/docs/classes/AssetConflictError.html +1 -1
  31. package/docs/classes/AssetForbiddenError.html +1 -1
  32. package/docs/classes/AssetNotFoundError.html +1 -1
  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 +102 -69
  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 +1 -1
  45. package/docs/classes/TwinUnauthenticatedError.html +1 -1
  46. package/docs/classes/TwinValidationError.html +1 -1
  47. package/docs/hierarchy.html +1 -1
  48. package/docs/interfaces/ApiCreateUploadSessionResponse.html +7 -0
  49. package/docs/interfaces/AssetAttachmentPut.html +6 -0
  50. package/docs/interfaces/AssetCatalogTooLargeErrorBody.html +1 -1
  51. package/docs/interfaces/AttachmentGet.html +20 -0
  52. package/docs/interfaces/AttachmentPost.html +4 -0
  53. package/docs/interfaces/ITwinClient.html +100 -67
  54. package/docs/interfaces/TwinErrorBody.html +1 -1
  55. package/docs/interfaces/TwinSpecification.html +1 -1
  56. package/docs/interfaces/UploadStatusGet.html +3 -0
  57. package/docs/modules.html +1 -1
  58. package/docs/types/TwinErrorCause.html +1 -1
  59. package/docs/types/UploadStatus.html +1 -0
  60. package/package.json +3 -3
  61. package/src/TwinClient.ts +324 -0
  62. package/src/errors.ts +19 -0
  63. package/src/types/asset-attachment-api.ts +56 -0
  64. package/src/types/index.ts +1 -0
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>TwinErrorCause | @twinfinity/twin</title><meta name="description" content="Documentation for @twinfinity/twin"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@twinfinity/twin</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">TwinErrorCause</a></li></ul><h1>Type Alias TwinErrorCause</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">TwinErrorCause</span><span class="tsd-signature-symbol">:</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;NotAllowedToEditDeletedItem&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;NotAllowedToDeleteItem&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;ImmutableField&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;ReferencedItemNotFound&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;ReferencedItemDeleted&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;FileNotOwnedByTwin&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DuplicateFileReference&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;MultipleVersionsOfSameFileOnSameNode&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;LimitOutOfRange&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;OffsetOutOfRange&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;IdMismatchBetweenUrlAndBody&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;BatchSizeEmpty&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;BatchSizeExceedsMaximum&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InvalidParameterCombination&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DocumentChangeNotFound&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DeletePermissionRequired&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;CannotEditDeletedFile&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;CannotMoveFileBetweenTwins&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;ContainerDoesNotExist&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DuplicateContainerIds&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InsufficientPermissionsOnContainer&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DuplicateId&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;MetadataTooLarge&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InvalidTransform&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InvalidFilePatches&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;MetadataValidationFailed&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;MetadataSchemaInvalid&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;AssetTypeNotFound&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;AssetCategoryAndTypeRequired&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;StringIdInUse&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;CatalogTooLarge&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">string</span> <span class="tsd-signature-symbol">&amp;</span> <span class="tsd-signature-symbol">{}</span></div><div class="tsd-comment tsd-typography"><p>Closed union of <code>cause</code> values the Twin service emits on a 400 response. Mirrors
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>TwinErrorCause | @twinfinity/twin</title><meta name="description" content="Documentation for @twinfinity/twin"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@twinfinity/twin</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">TwinErrorCause</a></li></ul><h1>Type Alias TwinErrorCause</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">TwinErrorCause</span><span class="tsd-signature-symbol">:</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;NotAllowedToEditDeletedItem&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;NotAllowedToDeleteItem&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;ImmutableField&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;ReferencedItemNotFound&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;ReferencedItemDeleted&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;FileNotOwnedByTwin&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DuplicateFileReference&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;MultipleVersionsOfSameFileOnSameNode&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;LimitOutOfRange&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;OffsetOutOfRange&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;IdMismatchBetweenUrlAndBody&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;BatchSizeEmpty&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;BatchSizeExceedsMaximum&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InvalidParameterCombination&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DocumentChangeNotFound&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DeletePermissionRequired&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;CannotEditDeletedFile&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;CannotMoveFileBetweenTwins&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;ContainerDoesNotExist&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DuplicateContainerIds&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InsufficientPermissionsOnContainer&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;DuplicateId&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;MetadataTooLarge&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InvalidTransform&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InvalidFilePatches&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;MetadataValidationFailed&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;MetadataSchemaInvalid&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;AssetTypeNotFound&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;AssetCategoryAndTypeRequired&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;StringIdInUse&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;CatalogTooLarge&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;EtagRequired&quot;</span><br/>    <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">string</span> <span class="tsd-signature-symbol">&amp;</span> <span class="tsd-signature-symbol">{}</span></div><div class="tsd-comment tsd-typography"><p>Closed union of <code>cause</code> values the Twin service emits on a 400 response. Mirrors
2
2
  <code>TwinBadRequestCause</code> in <code>backend/services/Twinfinity.Twin/Controllers/Model/TwinStatus.cs</code>.
3
3
  The trailing <code>(string &amp; {})</code> keeps autocomplete on the known values while still
4
4
  accepting unknown causes at the type level — a forward-compat pattern: a new
@@ -0,0 +1 @@
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>UploadStatus | @twinfinity/twin</title><meta name="description" content="Documentation for @twinfinity/twin"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@twinfinity/twin</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">UploadStatus</a></li></ul><h1>Type Alias UploadStatus</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">UploadStatus</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">&quot;NotFound&quot;</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;InProgress&quot;</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;Completed&quot;</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;Cancelled&quot;</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">&quot;Failed&quot;</span></div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@twinfinity/twin</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinfinity/twin",
3
- "version": "6.0.2-ci.29168",
3
+ "version": "6.0.2",
4
4
  "description": "Twinfinity Twin Service client",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -39,8 +39,8 @@
39
39
  "docs"
40
40
  ],
41
41
  "peerDependencies": {
42
- "@twinfinity/authentication": "6.0.2-ci.29168",
43
- "@twinfinity/t8-client-model": "6.0.2-ci.29168-beta"
42
+ "@twinfinity/authentication": "6.0.2",
43
+ "@twinfinity/t8-client-model": "6.0.2-beta"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@vitest/coverage-v8": "^3.2.3",
package/src/TwinClient.ts CHANGED
@@ -56,6 +56,8 @@ import {
56
56
  AssetForbiddenError,
57
57
  AssetConflictError,
58
58
  AssetNotFoundError,
59
+ AssetAttachmentConflictError,
60
+ AssetAttachmentNotFoundError,
59
61
  AssetBatchError,
60
62
  TwinFileValidationError,
61
63
  TwinFileConflictError,
@@ -80,6 +82,13 @@ import type {
80
82
  AssetTypeGet,
81
83
  AssetTypePut
82
84
  } from './types/asset-types-api';
85
+ import type {
86
+ AttachmentGet,
87
+ AttachmentPost,
88
+ AssetAttachmentPut,
89
+ ApiCreateUploadSessionResponse,
90
+ UploadStatusGet
91
+ } from './types/asset-attachment-api';
83
92
  import type { MyPermissions } from './types/twin-permissions-api';
84
93
  import { Twin } from './types/Twin';
85
94
  import { TwinGeometryInfo } from './types/TwinGeometryInfo';
@@ -319,6 +328,102 @@ export interface ITwinClient {
319
328
  */
320
329
  batchGetAssets(twinId: string, ids: string[], signal?: AbortSignal): Promise<AssetBatchGetResponse>;
321
330
 
331
+ /** Lists attachments for a single asset. */
332
+ listAssetAttachments(
333
+ twinId: string,
334
+ assetId: string,
335
+ options?: { includeDeleted?: boolean; offset?: number; limit?: number; page?: string },
336
+ signal?: AbortSignal
337
+ ): Promise<PaginatedResponse<AttachmentGet>>;
338
+
339
+ /**
340
+ * Downloads an attachment's blob as a `Response`. The caller decides
341
+ * whether to consume it as text, ArrayBuffer, or Blob.
342
+ * @throws {AssetAttachmentNotFoundError} When the attachment does not exist (404).
343
+ */
344
+ getAssetAttachmentBlob(
345
+ twinId: string,
346
+ assetId: string,
347
+ attachmentId: string,
348
+ signal?: AbortSignal
349
+ ): Promise<Response>;
350
+
351
+ /**
352
+ * Creates an upload session for a new attachment. The actual file bytes
353
+ * are uploaded out-of-band to {@link ApiCreateUploadSessionResponse.uploadUrl};
354
+ * use {@link uploadAssetAttachmentFile} for the canonical chunked-upload flow.
355
+ * The attachment row is materialized server-side when the blob lands.
356
+ * @throws {AssetAttachmentConflictError} When an attachment already exists
357
+ * for the given id (409); retries must use a new attachmentId.
358
+ */
359
+ createAssetAttachmentUploadSession(
360
+ twinId: string,
361
+ assetId: string,
362
+ attachmentId: string,
363
+ body: AttachmentPost,
364
+ signal?: AbortSignal
365
+ ): Promise<ApiCreateUploadSessionResponse>;
366
+
367
+ /**
368
+ * Uploads a `File` / `Blob` to an existing upload session in one or more
369
+ * chunks, then resolves the upload token so the caller can poll
370
+ * {@link getAssetAttachmentUploadStatus}.
371
+ */
372
+ uploadAssetAttachmentFile(
373
+ session: ApiCreateUploadSessionResponse,
374
+ file: Blob,
375
+ signal?: AbortSignal
376
+ ): Promise<{ token: string }>;
377
+
378
+ /** Polls the in-memory upload-session cache for an attachment that's being uploaded. */
379
+ getAssetAttachmentUploadStatus(
380
+ twinId: string,
381
+ assetId: string,
382
+ token: string,
383
+ signal?: AbortSignal
384
+ ): Promise<UploadStatusGet>;
385
+
386
+ /**
387
+ * Updates an attachment's description / soft-delete flag. `documentId`
388
+ * is immutable — rebinding is rejected.
389
+ * @throws {AssetAttachmentConflictError} On etag mismatch (409). The
390
+ * error's `currentAttachment` carries the server's current state.
391
+ * @throws {AssetAttachmentNotFoundError} When the attachment does not exist (404).
392
+ */
393
+ putAssetAttachment(
394
+ twinId: string,
395
+ assetId: string,
396
+ attachmentId: string,
397
+ body: AssetAttachmentPut,
398
+ signal?: AbortSignal
399
+ ): Promise<AttachmentGet>;
400
+
401
+ /**
402
+ * Soft-deletes an attachment.
403
+ * @throws {AssetAttachmentConflictError} On etag mismatch (409).
404
+ * @throws {AssetAttachmentNotFoundError} When the attachment does not exist (404).
405
+ */
406
+ deleteAssetAttachment(
407
+ twinId: string,
408
+ assetId: string,
409
+ attachmentId: string,
410
+ etag: string,
411
+ signal?: AbortSignal
412
+ ): Promise<AttachmentGet>;
413
+
414
+ /**
415
+ * Restores a soft-deleted attachment.
416
+ * @throws {AssetAttachmentConflictError} On etag mismatch (409).
417
+ * @throws {AssetAttachmentNotFoundError} When the attachment does not exist (404).
418
+ */
419
+ restoreAssetAttachment(
420
+ twinId: string,
421
+ assetId: string,
422
+ attachmentId: string,
423
+ etag: string,
424
+ signal?: AbortSignal
425
+ ): Promise<AttachmentGet>;
426
+
322
427
  /**
323
428
  * @throws {TwinNotFoundError} When the twin does not exist (404).
324
429
  * @throws {TwinConflictError} On etag mismatch (409). The error's `currentTwin` contains the server's current state.
@@ -1376,6 +1481,225 @@ export class TwinClient implements ITwinClient {
1376
1481
  throw new Error(`Failed to batch-get assets: ${response.status} ${response.statusText} - ${errorText}`);
1377
1482
  }
1378
1483
 
1484
+ // ────────── Asset attachments ──────────
1485
+
1486
+ async listAssetAttachments(
1487
+ twinId: string,
1488
+ assetId: string,
1489
+ options?: { includeDeleted?: boolean; offset?: number; limit?: number; page?: string },
1490
+ signal?: AbortSignal
1491
+ ): Promise<PaginatedResponse<AttachmentGet>> {
1492
+ const params = new URLSearchParams();
1493
+ if (options?.includeDeleted) params.set('includeDeleted', 'true');
1494
+ if (options?.offset !== undefined) params.set('offset', String(options.offset));
1495
+ if (options?.limit !== undefined) params.set('limit', String(options.limit));
1496
+ if (options?.page) params.set('page', options.page);
1497
+
1498
+ const url = `${this._baseUrl}/${twinId}/assets/${assetId}/attachments${params.toString() ? `?${params}` : ''}`;
1499
+ const response = await this._httpClient.fetch(HttpMethod.Get, url, {
1500
+ headers: { Accept: 'application/json' },
1501
+ signal
1502
+ });
1503
+
1504
+ if (response.ok) return (await response.json()) as PaginatedResponse<AttachmentGet>;
1505
+ if (response.status === 404) throw new AssetNotFoundError(`Asset ${assetId} not found`);
1506
+ if (response.status === 400) {
1507
+ const errorBody = await readErrorBody(response);
1508
+ throw new AssetValidationError(`Attachment listing failed: ${errorBody.detail}`, errorBody);
1509
+ }
1510
+ const errorText = await response.text();
1511
+ throw new Error(`Failed to list attachments: ${response.status} ${response.statusText} - ${errorText}`);
1512
+ }
1513
+
1514
+ async getAssetAttachmentBlob(
1515
+ twinId: string,
1516
+ assetId: string,
1517
+ attachmentId: string,
1518
+ signal?: AbortSignal
1519
+ ): Promise<Response> {
1520
+ const response = await this._httpClient.fetch(
1521
+ HttpMethod.Get,
1522
+ `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}`,
1523
+ { signal }
1524
+ );
1525
+ if (response.ok) return response;
1526
+ if (response.status === 404) throw new AssetAttachmentNotFoundError(`Attachment ${attachmentId} not found`);
1527
+ const errorText = await response.text();
1528
+ throw new Error(`Failed to fetch attachment blob: ${response.status} ${response.statusText} - ${errorText}`);
1529
+ }
1530
+
1531
+ async createAssetAttachmentUploadSession(
1532
+ twinId: string,
1533
+ assetId: string,
1534
+ attachmentId: string,
1535
+ body: AttachmentPost,
1536
+ signal?: AbortSignal
1537
+ ): Promise<ApiCreateUploadSessionResponse> {
1538
+ const response = await this._httpClient.fetch(
1539
+ HttpMethod.Post,
1540
+ `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}/createAttachmentUploadSession`,
1541
+ {
1542
+ headers: { 'Content-Type': 'application/json' },
1543
+ body: JSON.stringify(body),
1544
+ signal
1545
+ }
1546
+ );
1547
+ if (response.ok) return (await response.json()) as ApiCreateUploadSessionResponse;
1548
+ if (response.status === 409) {
1549
+ const conflicted = (await response.json()) as AttachmentGet;
1550
+ throw new AssetAttachmentConflictError(
1551
+ `Attachment ${attachmentId} already exists; pick a new id for retries`,
1552
+ conflicted
1553
+ );
1554
+ }
1555
+ if (response.status === 404) throw new AssetNotFoundError(`Asset ${assetId} not found`);
1556
+ if (response.status === 400) {
1557
+ const errorBody = await readErrorBody(response);
1558
+ throw new AssetValidationError(`Upload session creation failed: ${errorBody.detail}`, errorBody);
1559
+ }
1560
+ const errorText = await response.text();
1561
+ throw new Error(`Failed to create upload session: ${response.status} ${response.statusText} - ${errorText}`);
1562
+ }
1563
+
1564
+ async uploadAssetAttachmentFile(
1565
+ session: ApiCreateUploadSessionResponse,
1566
+ file: Blob,
1567
+ signal?: AbortSignal
1568
+ ): Promise<{ token: string }> {
1569
+ // The token is encoded as a query param on the upload URL; we lift it
1570
+ // out for the caller so they can poll uploadStatus without re-parsing.
1571
+ const tokenMatch = session.uploadUrl.match(/token=([^&]+)/);
1572
+ if (!tokenMatch) {
1573
+ throw new Error(`Upload session URL is missing a token query parameter: ${session.uploadUrl}`);
1574
+ }
1575
+ const token = tokenMatch[1];
1576
+
1577
+ const chunkSize = Math.max(1, session.maxChunkSize || file.size);
1578
+ const total = file.size;
1579
+ let offset = 0;
1580
+
1581
+ // Even an empty file needs at least one PUT so the upload session
1582
+ // gets a finalize signal. uploadChunks() in the integration tests
1583
+ // unconditionally PUTs at least one chunk too.
1584
+ do {
1585
+ const end = Math.min(offset + chunkSize, total);
1586
+ const chunk = file.slice(offset, end);
1587
+ const lastByte = Math.max(0, end - 1);
1588
+ const response = await this._httpClient.fetch(HttpMethod.Put, session.uploadUrl, {
1589
+ headers: { 'Content-Range': `bytes ${offset}-${lastByte}/${total}` },
1590
+ body: chunk,
1591
+ signal
1592
+ });
1593
+ if (!response.ok) {
1594
+ const errorText = await response.text();
1595
+ throw new Error(
1596
+ `Upload chunk failed at offset ${offset}: ${response.status} ${response.statusText} - ${errorText}`
1597
+ );
1598
+ }
1599
+ offset = end;
1600
+ } while (offset < total);
1601
+
1602
+ return { token };
1603
+ }
1604
+
1605
+ async getAssetAttachmentUploadStatus(
1606
+ twinId: string,
1607
+ assetId: string,
1608
+ token: string,
1609
+ signal?: AbortSignal
1610
+ ): Promise<UploadStatusGet> {
1611
+ const response = await this._httpClient.fetch(
1612
+ HttpMethod.Get,
1613
+ `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/uploadStatus/${encodeURIComponent(token)}`,
1614
+ { headers: { Accept: 'application/json' }, signal }
1615
+ );
1616
+ if (response.ok) return (await response.json()) as UploadStatusGet;
1617
+ if (response.status === 404) return { status: 'NotFound' };
1618
+ const errorText = await response.text();
1619
+ throw new Error(`Failed to get upload status: ${response.status} ${response.statusText} - ${errorText}`);
1620
+ }
1621
+
1622
+ async putAssetAttachment(
1623
+ twinId: string,
1624
+ assetId: string,
1625
+ attachmentId: string,
1626
+ body: AssetAttachmentPut,
1627
+ signal?: AbortSignal
1628
+ ): Promise<AttachmentGet> {
1629
+ const response = await this._httpClient.fetch(
1630
+ HttpMethod.Put,
1631
+ `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}`,
1632
+ {
1633
+ headers: { 'Content-Type': 'application/json' },
1634
+ body: JSON.stringify(body),
1635
+ signal
1636
+ }
1637
+ );
1638
+ if (response.ok) return (await response.json()) as AttachmentGet;
1639
+ if (response.status === 409) {
1640
+ const conflicted = (await response.json()) as AttachmentGet;
1641
+ throw new AssetAttachmentConflictError('Attachment has been modified by another user', conflicted);
1642
+ }
1643
+ if (response.status === 404) throw new AssetAttachmentNotFoundError(`Attachment ${attachmentId} not found`);
1644
+ if (response.status === 400) {
1645
+ const errorBody = await readErrorBody(response);
1646
+ throw new AssetValidationError(`Attachment update failed: ${errorBody.detail}`, errorBody);
1647
+ }
1648
+ const errorText = await response.text();
1649
+ throw new Error(`Failed to update attachment: ${response.status} ${response.statusText} - ${errorText}`);
1650
+ }
1651
+
1652
+ async deleteAssetAttachment(
1653
+ twinId: string,
1654
+ assetId: string,
1655
+ attachmentId: string,
1656
+ etag: string,
1657
+ signal?: AbortSignal
1658
+ ): Promise<AttachmentGet> {
1659
+ const response = await this._httpClient.fetch(
1660
+ HttpMethod.Delete,
1661
+ `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}`,
1662
+ {
1663
+ headers: { 'If-Match': etag },
1664
+ signal
1665
+ }
1666
+ );
1667
+ if (response.ok) return (await response.json()) as AttachmentGet;
1668
+ if (response.status === 409) {
1669
+ const conflicted = (await response.json()) as AttachmentGet;
1670
+ throw new AssetAttachmentConflictError('Attachment has been modified by another user', conflicted);
1671
+ }
1672
+ if (response.status === 404) throw new AssetAttachmentNotFoundError(`Attachment ${attachmentId} not found`);
1673
+ const errorText = await response.text();
1674
+ throw new Error(`Failed to delete attachment: ${response.status} ${response.statusText} - ${errorText}`);
1675
+ }
1676
+
1677
+ async restoreAssetAttachment(
1678
+ twinId: string,
1679
+ assetId: string,
1680
+ attachmentId: string,
1681
+ etag: string,
1682
+ signal?: AbortSignal
1683
+ ): Promise<AttachmentGet> {
1684
+ const response = await this._httpClient.fetch(
1685
+ HttpMethod.Put,
1686
+ `${this._baseUrl}/${twinId}/assets/${assetId}/attachments/${attachmentId}/restore`,
1687
+ {
1688
+ headers: { 'Content-Type': 'application/json' },
1689
+ body: JSON.stringify({ etag }),
1690
+ signal
1691
+ }
1692
+ );
1693
+ if (response.ok) return (await response.json()) as AttachmentGet;
1694
+ if (response.status === 409) {
1695
+ const conflicted = (await response.json()) as AttachmentGet;
1696
+ throw new AssetAttachmentConflictError('Attachment has been modified by another user', conflicted);
1697
+ }
1698
+ if (response.status === 404) throw new AssetAttachmentNotFoundError(`Attachment ${attachmentId} not found`);
1699
+ const errorText = await response.text();
1700
+ throw new Error(`Failed to restore attachment: ${response.status} ${response.statusText} - ${errorText}`);
1701
+ }
1702
+
1379
1703
  async listTwinFiles(
1380
1704
  twinId: string,
1381
1705
  options: TwinFileListOptions = {},
package/src/errors.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { TwinGet, AssetGet, TwinFileGet, AssetBatchResponse, AssetCategoryGet, AssetTypeGet } from './types';
2
+ import type { AttachmentGet } from './types/asset-attachment-api';
2
3
 
3
4
  /**
4
5
  * Closed union of `cause` values the Twin service emits on a 400 response. Mirrors
@@ -41,6 +42,7 @@ export type TwinErrorCause =
41
42
  | 'AssetCategoryAndTypeRequired'
42
43
  | 'StringIdInUse'
43
44
  | 'CatalogTooLarge'
45
+ | 'EtagRequired'
44
46
  // Anything the server emits that this client doesn't know about yet.
45
47
  // (string & {}) keeps the autocomplete on the listed values above.
46
48
  // eslint-disable-next-line @typescript-eslint/ban-types
@@ -166,6 +168,23 @@ export class AssetNotFoundError extends Error {
166
168
  }
167
169
  }
168
170
 
171
+ export class AssetAttachmentConflictError extends Error {
172
+ public readonly currentAttachment: AttachmentGet;
173
+
174
+ constructor(message: string, currentAttachment: AttachmentGet) {
175
+ super(message);
176
+ this.name = 'AssetAttachmentConflictError';
177
+ this.currentAttachment = currentAttachment;
178
+ }
179
+ }
180
+
181
+ export class AssetAttachmentNotFoundError extends Error {
182
+ constructor(message: string) {
183
+ super(message);
184
+ this.name = 'AssetAttachmentNotFoundError';
185
+ }
186
+ }
187
+
169
188
  export class AssetBatchError extends Error {
170
189
  public readonly response: AssetBatchResponse;
171
190
 
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Wire types for asset attachments. Mirrors the C# DTOs in
3
+ * `Twinfinity.Twin.Controllers.Model.Attachment`. The (asset, document)
4
+ * pair is fixed at upload-finalize time; the only way to replace a file
5
+ * is to delete the attachment and create a new one with a fresh upload.
6
+ */
7
+
8
+ export interface AttachmentGet {
9
+ twinId: string;
10
+ assetId: string;
11
+ id: string;
12
+ documentId: string;
13
+ createdUtc: string;
14
+ modifiedUtc: string;
15
+ createdBy: string;
16
+ modifiedBy: string;
17
+ etag: string;
18
+ /** `[REDACTED]` on soft-deleted rows. */
19
+ fileName: string;
20
+ /** `[REDACTED]` on soft-deleted rows. */
21
+ description: string | null;
22
+ /** Present and `true` on soft-deleted rows; omitted from the wire payload otherwise. */
23
+ isDeleted?: boolean;
24
+ }
25
+
26
+ /** Body for `POST .../createAttachmentUploadSession`. */
27
+ export interface AttachmentPost {
28
+ fileName: string;
29
+ description?: string | null;
30
+ }
31
+
32
+ /**
33
+ * Body for `PUT .../{attachmentId}` (metadata-only update). Soft-delete and
34
+ * restore go through `DELETE` / `PUT /restore`, not this endpoint.
35
+ */
36
+ export interface AssetAttachmentPut {
37
+ documentId: string;
38
+ description?: string | null;
39
+ etag: string;
40
+ }
41
+
42
+ /** Response from `createAttachmentUploadSession`. */
43
+ export interface ApiCreateUploadSessionResponse {
44
+ /** Maximum bytes per `PUT` chunk. */
45
+ maxChunkSize: number;
46
+ /** URL the client uploads the file to. The token is encoded as a query parameter. */
47
+ uploadUrl: string;
48
+ expiresUtc: string;
49
+ }
50
+
51
+ /** Polling shape returned by `uploadStatus/{token}`. */
52
+ export interface UploadStatusGet {
53
+ status: UploadStatus;
54
+ }
55
+
56
+ export type UploadStatus = 'NotFound' | 'InProgress' | 'Completed' | 'Cancelled' | 'Failed';
@@ -3,6 +3,7 @@ export * from './twin-api';
3
3
  export * from './asset-api';
4
4
  export * from './asset-batch-api';
5
5
  export * from './asset-types-api';
6
+ export * from './asset-attachment-api';
6
7
  export * from './twin-permissions-api';
7
8
  export * from './twin-file-api';
8
9
  export * from './generated';