diff --git a/Directory.Packages.props b/Directory.Packages.props
index 86c51244..6bf24322 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,6 +8,8 @@
+
+
@@ -16,7 +18,6 @@
-
@@ -28,7 +29,9 @@
-
+
+
+
@@ -39,4 +42,4 @@
-
+
\ No newline at end of file
diff --git a/src/Speckle.Objects/packages.lock.json b/src/Speckle.Objects/packages.lock.json
index 17abca99..2c2e04f1 100644
--- a/src/Speckle.Objects/packages.lock.json
+++ b/src/Speckle.Objects/packages.lock.json
@@ -130,6 +130,19 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.1",
+ "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
+ },
+ "Newtonsoft.Json.Bson": {
+ "type": "Transitive",
+ "resolved": "1.0.2",
+ "contentHash": "QYFyxhaABwmq3p/21VrZNYvCg3DaEoN/wUuw5nmfAf0X3HLjgupwhkEWdgfb9nvGAUIv3osmZoD3kKl4jxEmYQ==",
+ "dependencies": {
+ "Newtonsoft.Json": "12.0.1"
+ }
+ },
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -162,8 +175,8 @@
},
"System.Buffers": {
"type": "Transitive",
- "resolved": "4.4.0",
- "contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw=="
+ "resolved": "4.6.1",
+ "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw=="
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
@@ -172,18 +185,18 @@
},
"System.Memory": {
"type": "Transitive",
- "resolved": "4.5.3",
- "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==",
+ "resolved": "4.6.3",
+ "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==",
"dependencies": {
- "System.Buffers": "4.4.0",
- "System.Numerics.Vectors": "4.4.0",
- "System.Runtime.CompilerServices.Unsafe": "4.5.2"
+ "System.Buffers": "4.6.1",
+ "System.Numerics.Vectors": "4.6.1",
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
- "resolved": "4.4.0",
- "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
+ "resolved": "4.6.1",
+ "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q=="
},
"System.Reactive": {
"type": "Transitive",
@@ -205,8 +218,8 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
- "resolved": "4.5.3",
- "contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw=="
+ "resolved": "6.1.2",
+ "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Runtime.InteropServices.WindowsRuntime": {
"type": "Transitive",
@@ -216,26 +229,40 @@
"System.Runtime": "4.3.0"
}
},
+ "System.Text.Encodings.Web": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "cVAka0o1rJJ5/De0pjNs7jcaZk5hUGf1HGzUyVmE2MEB1Vf0h/8qsWxImk1zjitCbeD2Avaq2P2+usdvqgbeVQ==",
+ "dependencies": {
+ "System.Buffers": "4.6.1",
+ "System.Memory": "4.6.3",
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
+ }
+ },
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
- "resolved": "4.5.4",
- "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
+ "resolved": "4.6.3",
+ "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"speckle.sdk": {
"type": "Project",
"dependencies": {
"GraphQL.Client": "[6.0.0, )",
- "Microsoft.Bcl.AsyncInterfaces": "[5.0.0, )",
+ "Microsoft.AspNet.WebApi.Client": "[6.0.0, )",
+ "Microsoft.Bcl.AsyncInterfaces": "[10.0.1, )",
"Microsoft.CSharp": "[4.7.0, )",
"Microsoft.Data.Sqlite": "[7.0.5, )",
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
- "Speckle.Sdk.Dependencies": "[1.0.0, )"
+ "Speckle.Sdk.Dependencies": "[1.0.0, )",
+ "System.IO.Pipelines": "[10.0.1, )",
+ "System.Net.Http.Json": "[10.0.1, )",
+ "System.Threading.Channels": "[10.0.1, )"
}
},
"speckle.sdk.dependencies": {
@@ -252,13 +279,25 @@
"System.Reactive": "5.0.0"
}
},
+ "Microsoft.AspNet.WebApi.Client": {
+ "type": "CentralTransitive",
+ "requested": "[6.0.0, )",
+ "resolved": "6.0.0",
+ "contentHash": "zXeWP03dTo67AoDHUzR+/urck0KFssdCKOC+dq7Nv1V2YbFh/nIg09L0/3wSvyRONEdwxGB/ssEGmPNIIhAcAw==",
+ "dependencies": {
+ "Newtonsoft.Json": "13.0.1",
+ "Newtonsoft.Json.Bson": "1.0.2",
+ "System.Memory": "4.5.5",
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
- "requested": "[5.0.0, )",
- "resolved": "5.0.0",
- "contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
- "System.Threading.Tasks.Extensions": "4.5.4"
+ "System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.CSharp": {
@@ -306,6 +345,54 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
+ },
+ "System.IO.Pipelines": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "26LbFXHKd7PmRnWlkjnYgmjd5B6HYVG+1MpTO25BdxTJnx6D0O16JPAC/S4YBqjtt4YpfGj1QO/Ss6SPMGEGQw==",
+ "dependencies": {
+ "System.Buffers": "4.6.1",
+ "System.Memory": "4.6.3",
+ "System.Threading.Tasks.Extensions": "4.6.3"
+ }
+ },
+ "System.Net.Http.Json": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "XGOWt78ccgO9esyNlCemlS2b8JZPrH85pk/MvdHJxp6KwwUY/GnDaw2fPpJa7lgotiTkWnXnhip+ULNKaP7a8A==",
+ "dependencies": {
+ "System.Buffers": "4.6.1",
+ "System.Memory": "4.6.3",
+ "System.Text.Json": "10.0.1",
+ "System.Threading.Tasks.Extensions": "4.6.3"
+ }
+ },
+ "System.Text.Json": {
+ "type": "CentralTransitive",
+ "requested": "[8.0.5, )",
+ "resolved": "10.0.1",
+ "contentHash": "EsgwDgU1PFqhrFA9l5n+RBu76wFhNGCEwu8ITrBNhjPP3MxLyklroU5GIF8o6JYpYg6T4KD/VICfMdgPAvNp5g==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "10.0.1",
+ "System.Buffers": "4.6.1",
+ "System.IO.Pipelines": "10.0.1",
+ "System.Memory": "4.6.3",
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2",
+ "System.Text.Encodings.Web": "10.0.1",
+ "System.Threading.Tasks.Extensions": "4.6.3"
+ }
+ },
+ "System.Threading.Channels": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "10.0.1",
+ "System.Threading.Tasks.Extensions": "4.6.3"
+ }
}
},
"net8.0": {
@@ -418,6 +505,19 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.1",
+ "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
+ },
+ "Newtonsoft.Json.Bson": {
+ "type": "Transitive",
+ "resolved": "1.0.2",
+ "contentHash": "QYFyxhaABwmq3p/21VrZNYvCg3DaEoN/wUuw5nmfAf0X3HLjgupwhkEWdgfb9nvGAUIv3osmZoD3kKl4jxEmYQ==",
+ "dependencies": {
+ "Newtonsoft.Json": "12.0.1"
+ }
+ },
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -455,8 +555,8 @@
},
"System.Memory": {
"type": "Transitive",
- "resolved": "4.5.3",
- "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
+ "resolved": "4.5.5",
+ "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
"System.Reactive": {
"type": "Transitive",
@@ -468,16 +568,30 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
+ "System.Text.Encodings.Web": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "cVAka0o1rJJ5/De0pjNs7jcaZk5hUGf1HGzUyVmE2MEB1Vf0h/8qsWxImk1zjitCbeD2Avaq2P2+usdvqgbeVQ=="
+ },
+ "System.Threading.Tasks.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.5.4",
+ "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg=="
+ },
"speckle.sdk": {
"type": "Project",
"dependencies": {
"GraphQL.Client": "[6.0.0, )",
+ "Microsoft.AspNet.WebApi.Client": "[6.0.0, )",
"Microsoft.Data.Sqlite": "[7.0.5, )",
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
- "Speckle.Sdk.Dependencies": "[1.0.0, )"
+ "Speckle.Sdk.Dependencies": "[1.0.0, )",
+ "System.IO.Pipelines": "[10.0.1, )",
+ "System.Net.Http.Json": "[10.0.1, )",
+ "System.Threading.Channels": "[10.0.1, )"
}
},
"speckle.sdk.dependencies": {
@@ -494,6 +608,18 @@
"System.Reactive": "5.0.0"
}
},
+ "Microsoft.AspNet.WebApi.Client": {
+ "type": "CentralTransitive",
+ "requested": "[6.0.0, )",
+ "resolved": "6.0.0",
+ "contentHash": "zXeWP03dTo67AoDHUzR+/urck0KFssdCKOC+dq7Nv1V2YbFh/nIg09L0/3wSvyRONEdwxGB/ssEGmPNIIhAcAw==",
+ "dependencies": {
+ "Newtonsoft.Json": "13.0.1",
+ "Newtonsoft.Json.Bson": "1.0.2",
+ "System.Memory": "4.5.5",
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
"Microsoft.Data.Sqlite": {
"type": "CentralTransitive",
"requested": "[7.0.5, )",
@@ -533,6 +659,37 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
+ },
+ "System.IO.Pipelines": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "26LbFXHKd7PmRnWlkjnYgmjd5B6HYVG+1MpTO25BdxTJnx6D0O16JPAC/S4YBqjtt4YpfGj1QO/Ss6SPMGEGQw=="
+ },
+ "System.Net.Http.Json": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "XGOWt78ccgO9esyNlCemlS2b8JZPrH85pk/MvdHJxp6KwwUY/GnDaw2fPpJa7lgotiTkWnXnhip+ULNKaP7a8A==",
+ "dependencies": {
+ "System.Text.Json": "10.0.1"
+ }
+ },
+ "System.Text.Json": {
+ "type": "CentralTransitive",
+ "requested": "[8.0.5, )",
+ "resolved": "10.0.1",
+ "contentHash": "EsgwDgU1PFqhrFA9l5n+RBu76wFhNGCEwu8ITrBNhjPP3MxLyklroU5GIF8o6JYpYg6T4KD/VICfMdgPAvNp5g==",
+ "dependencies": {
+ "System.IO.Pipelines": "10.0.1",
+ "System.Text.Encodings.Web": "10.0.1"
+ }
+ },
+ "System.Threading.Channels": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
}
}
}
diff --git a/src/Speckle.Sdk.Dependencies/packages.lock.json b/src/Speckle.Sdk.Dependencies/packages.lock.json
index c664417c..1f256577 100644
--- a/src/Speckle.Sdk.Dependencies/packages.lock.json
+++ b/src/Speckle.Sdk.Dependencies/packages.lock.json
@@ -82,12 +82,12 @@
},
"System.Threading.Channels": {
"type": "Direct",
- "requested": "[9.0.4, )",
- "resolved": "9.0.4",
- "contentHash": "4qBn2H6/aXBpE/Pm3wY5yusY/pEvQz99NlWHrTUji0qCmOdbhhjaALcpmbfW2ksxlPM6i6S+QFLkpOQdyfeKYQ==",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg==",
"dependencies": {
- "Microsoft.Bcl.AsyncInterfaces": "9.0.4",
- "System.Threading.Tasks.Extensions": "4.5.4"
+ "Microsoft.Bcl.AsyncInterfaces": "10.0.1",
+ "System.Threading.Tasks.Extensions": "4.6.3"
}
},
"ILRepack": {
@@ -141,24 +141,24 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
+ "resolved": "6.1.2",
+ "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
- "resolved": "4.5.4",
- "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
+ "resolved": "4.6.3",
+ "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
- "requested": "[5.0.0, )",
- "resolved": "9.0.4",
- "contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
- "System.Threading.Tasks.Extensions": "4.5.4"
+ "System.Threading.Tasks.Extensions": "4.6.3"
}
}
},
@@ -229,9 +229,9 @@
},
"System.Threading.Channels": {
"type": "Direct",
- "requested": "[9.0.4, )",
- "resolved": "9.0.4",
- "contentHash": "4qBn2H6/aXBpE/Pm3wY5yusY/pEvQz99NlWHrTUji0qCmOdbhhjaALcpmbfW2ksxlPM6i6S+QFLkpOQdyfeKYQ=="
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
},
"ILRepack": {
"type": "Transitive",
diff --git a/src/Speckle.Sdk/Models/DynamicBaseMemberType.cs b/src/Speckle.Sdk/Models/DynamicBaseMemberType.cs
index fc114380..b03c2579 100644
--- a/src/Speckle.Sdk/Models/DynamicBaseMemberType.cs
+++ b/src/Speckle.Sdk/Models/DynamicBaseMemberType.cs
@@ -17,7 +17,7 @@ public enum DynamicBaseMemberType
Dynamic = 2,
///
- /// The typed members flagged with attribute.
+ /// The typed members flagged with ObsoleteAttribute attribute.
///
Obsolete = 4,
@@ -27,12 +27,12 @@ public enum DynamicBaseMemberType
SchemaComputed = 16,
///
- /// All the typed members, including ones with attributes.
+ /// All the typed members, including ones with ObsoleteAttribute attributes.
///
InstanceAll = Instance + Obsolete,
///
- /// All the members, including dynamic and instance members flagged with attributes
+ /// All the members, including dynamic and instance members flagged with ObsoleteAttribute attributes
///
All = InstanceAll + Dynamic,
}
diff --git a/src/Speckle.Sdk/Pipelines/ProgressStream.cs b/src/Speckle.Sdk/Pipelines/ProgressStream.cs
new file mode 100644
index 00000000..ed62121c
--- /dev/null
+++ b/src/Speckle.Sdk/Pipelines/ProgressStream.cs
@@ -0,0 +1,97 @@
+namespace Speckle.Sdk.Pipelines;
+
+///
+/// Wraps a stream to report upload progress as bytes are read.
+///
+public sealed class ProgressStream : Stream
+{
+ private readonly Stream _innerStream;
+ private readonly long _totalBytes;
+ private readonly IProgress<(long BytesSent, long TotalBytes)>? _progress;
+ private long _bytesSent;
+
+ public ProgressStream(
+ Stream innerStream,
+ long totalBytes,
+ IProgress<(long BytesSent, long TotalBytes)>? progress = null
+ )
+ {
+ _innerStream = innerStream;
+ _totalBytes = totalBytes;
+ _progress = progress;
+ _bytesSent = 0;
+ }
+
+ public override bool CanRead => _innerStream.CanRead;
+ public override bool CanSeek => _innerStream.CanSeek;
+ public override bool CanWrite => false;
+ public override long Length => _innerStream.Length;
+
+ public override long Position
+ {
+ get => _innerStream.Position;
+ set => _innerStream.Position = value;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ int bytesRead = _innerStream.Read(buffer, offset, count);
+ ReportProgress(bytesRead);
+ return bytesRead;
+ }
+
+ public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ int bytesRead = await _innerStream
+#if NET8_0_OR_GREATER
+ .ReadAsync(buffer.AsMemory(offset, count), cancellationToken)
+#else
+ .ReadAsync(buffer, offset, count, cancellationToken)
+#endif
+ .ConfigureAwait(false);
+ ReportProgress(bytesRead);
+ return bytesRead;
+ }
+
+#if NET8_0_OR_GREATER
+ public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default)
+ {
+ int bytesRead = await _innerStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+ ReportProgress(bytesRead);
+ return bytesRead;
+ }
+#endif
+
+ private void ReportProgress(int bytesRead)
+ {
+ _bytesSent += bytesRead;
+ _progress?.Report((_bytesSent, _totalBytes));
+ }
+
+ public override void Flush() => _innerStream.Flush();
+
+ public override Task FlushAsync(CancellationToken cancellationToken) => _innerStream.FlushAsync(cancellationToken);
+
+ public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
+
+ public override void SetLength(long value) => throw new NotSupportedException();
+
+ public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _innerStream.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+#if NET8_0_OR_GREATER
+ public override async ValueTask DisposeAsync()
+ {
+ await _innerStream.DisposeAsync().ConfigureAwait(false);
+ await base.DisposeAsync().ConfigureAwait(false);
+ }
+#endif
+}
diff --git a/src/Speckle.Sdk/Pipelines/SendPipeline.cs b/src/Speckle.Sdk/Pipelines/SendPipeline.cs
new file mode 100644
index 00000000..4c580163
--- /dev/null
+++ b/src/Speckle.Sdk/Pipelines/SendPipeline.cs
@@ -0,0 +1,59 @@
+using Speckle.Sdk.Credentials;
+using Speckle.Sdk.Models;
+using Speckle.Sdk.Serialisation;
+
+namespace Speckle.Sdk.Pipelines;
+
+public record UploadItem(string Id, Json Json, string SpeckleType, ObjectReference Reference);
+
+public sealed class SendPipeline : IDisposable
+{
+ private readonly CancellationToken _cancellationToken;
+ private readonly Serializer _serializer = new();
+ private readonly Uploader _uploader;
+
+ public SendPipeline(
+ Account account,
+ string projectId,
+ string modelId,
+ string ingestionId,
+ CancellationToken cancellationToken
+ )
+ {
+ _cancellationToken = cancellationToken;
+ _uploader = new Uploader(projectId, modelId, ingestionId, account.serverInfo.url, account.token, cancellationToken);
+ }
+
+ private UploadItem _lastItem;
+
+ public async Task Process(Base @base)
+ {
+ var results = _serializer.Serialize(@base).ToArray();
+ var first = results.First();
+ foreach (var item in results)
+ {
+ // we're not doing fire and forget here so that we get the backpressure from the uploader
+ await _uploader.PushAsync(item).ConfigureAwait(false);
+ }
+
+ // NOTE: this is important to keep track of. When we serialze an object, we get back a list of objects, with the first one being the original root.
+ // In the case of the commit root object, this means the last object is not necessarily the root; we therefore need to manually track its existance here
+ // and ensure it's the last one through in the uploader's stream. See WaitForUpload down below.
+ _lastItem = first;
+ return first.Reference;
+ }
+
+ public async Task WaitForUpload()
+ {
+ await _uploader.PushAsync(_lastItem).ConfigureAwait(false);
+ await _uploader.CompleteAsync().ConfigureAwait(false);
+ }
+
+ public async Task WaitForUploadAndServerProcessing()
+ {
+ // TODO: in some way, wait for the server to process the upload and return the actual new version id
+ return await Task.FromResult("todo").ConfigureAwait(false);
+ }
+
+ public void Dispose() => _uploader.Dispose();
+}
diff --git a/src/Speckle.Sdk/Pipelines/Serializer.cs b/src/Speckle.Sdk/Pipelines/Serializer.cs
new file mode 100644
index 00000000..ed072a50
--- /dev/null
+++ b/src/Speckle.Sdk/Pipelines/Serializer.cs
@@ -0,0 +1,372 @@
+using System.Collections;
+using System.Reflection;
+using Speckle.DoubleNumerics;
+using Speckle.Newtonsoft.Json;
+using Speckle.Sdk.Dependencies;
+using Speckle.Sdk.Helpers;
+using Speckle.Sdk.Models;
+using Speckle.Sdk.Serialisation;
+
+namespace Speckle.Sdk.Pipelines;
+
+///
+/// Another serializer, cleaner and meaner. Provides methods for serializing Speckle objects into a format suitable for upload or storage.
+/// This class handles the conversion of and its derivatives
+/// into serialized JSON structures along with associated metadata, closures, and references.
+/// Any reference objects coming through are being "passed through" serialized - they do not get double encoded.
+///
+public class Serializer
+{
+ private readonly record struct PropertyInfo(string Name, object? Value, bool IsDetachable);
+
+ public IEnumerable Serialize(Base root)
+ {
+ // Special case: if root is already an ObjectReference, serialize it verbatim
+ if (root is ObjectReference existingRef)
+ {
+ var uploadItem = ReferenceToUploadItem(existingRef);
+ yield return uploadItem;
+ yield break;
+ }
+
+ var detachedObjects = new List<(Id, Json, Dictionary, Base, string)>();
+ var rootClosures = new Dictionary();
+
+ var (rootId, rootJson) = SerializeBase(root, false, rootClosures, detachedObjects);
+
+ var rootReference = new ObjectReference
+ {
+ referencedId = rootId.Value,
+ applicationId = root.applicationId,
+ closure = rootClosures.Count > 0 ? rootClosures : null,
+ };
+
+ yield return new UploadItem(rootId.Value, rootJson, root.speckle_type, rootReference);
+
+ foreach (var (id, json, closures, baseObj, speckleType) in detachedObjects)
+ {
+ var reference = new ObjectReference
+ {
+ referencedId = id.Value,
+ applicationId = baseObj.applicationId,
+ closure = closures.Count > 0 ? closures : null,
+ };
+
+ yield return new UploadItem(id.Value, json, speckleType, reference);
+ }
+ }
+
+ private IEnumerable ExtractProperties(Base baseObj)
+ {
+ var typedProperties = baseObj.GetInstanceMembers();
+ foreach (var prop in typedProperties)
+ {
+ if (prop.Name == "id" || prop.Name.StartsWith("__"))
+ {
+ continue;
+ }
+
+ if (prop.IsDefined(typeof(JsonIgnoreAttribute), false))
+ {
+ continue;
+ }
+
+ var value = prop.GetValue(baseObj);
+ var isDetachable = prop.GetCustomAttribute(true)?.Detachable ?? false;
+
+ yield return new PropertyInfo(prop.Name, value, isDetachable);
+ }
+
+ foreach (var propName in baseObj.DynamicPropertyKeys)
+ {
+ if (propName.StartsWith("__"))
+ {
+ continue;
+ }
+
+ var value = baseObj[propName];
+
+#pragma warning disable CA1866
+ var isDetachable = propName.StartsWith("@");
+#pragma warning restore CA1866
+
+ yield return new PropertyInfo(propName, value, isDetachable);
+ }
+ }
+
+ private (Id, Json) SerializeBase(
+ Base baseObj,
+ bool forceDetach,
+ Dictionary closures,
+ List<(Id, Json, Dictionary, Base, string)> detachedObjects
+ )
+ {
+ var childClosures = new Dictionary();
+
+ var sb = Pools.StringBuilders.Get();
+ try
+ {
+ using var stringWriter = new StringWriter(sb);
+ using var jsonWriter = new JsonTextWriter(stringWriter);
+ using var idWriter = new SerializerIdWriter(jsonWriter);
+
+ idWriter.WriteStartObject();
+
+ foreach (var prop in ExtractProperties(baseObj))
+ {
+ idWriter.WritePropertyName(prop.Name);
+ SerializeValue(prop.Value, idWriter, prop.IsDetachable, childClosures, detachedObjects);
+ }
+
+ var (jsonForId, finalWriter) = idWriter.FinishIdWriter();
+ var id = IdGenerator.ComputeId(jsonForId);
+
+ finalWriter.WritePropertyName("id");
+ finalWriter.WriteValue(id.Value);
+
+ baseObj.id = id.Value;
+
+ if ((forceDetach || childClosures.Count > 0) && childClosures.Count > 0)
+ {
+ finalWriter.WritePropertyName("__closure");
+ finalWriter.WriteStartObject();
+ foreach (var kvp in childClosures)
+ {
+ finalWriter.WritePropertyName(kvp.Key);
+ finalWriter.WriteValue(kvp.Value);
+ }
+ finalWriter.WriteEndObject();
+
+ foreach (var kvp in childClosures)
+ {
+ closures[kvp.Key] = closures.TryGetValue(kvp.Key, out var existing) ? existing + kvp.Value : kvp.Value;
+ }
+ }
+
+ finalWriter.WriteEndObject();
+ finalWriter.Flush();
+
+ var json = new Json(stringWriter.ToString());
+ return (id, json);
+ }
+ finally
+ {
+ Pools.StringBuilders.Return(sb);
+ }
+ }
+
+ private void SerializeValue(
+ object? value,
+ JsonWriter writer,
+ bool isDetachable,
+ Dictionary closures,
+ List<(Id, Json, Dictionary, Base, string)> detachedObjects
+ )
+ {
+ if (value == null)
+ {
+ writer.WriteNull();
+ return;
+ }
+
+ switch (value)
+ {
+ case string s:
+ writer.WriteValue(s);
+ return;
+ case int i:
+ writer.WriteValue(i);
+ return;
+ case long l:
+ writer.WriteValue(l);
+ return;
+ case double d:
+ writer.WriteValue(d);
+ return;
+ case float f:
+ writer.WriteValue(f);
+ return;
+ case bool b:
+ writer.WriteValue(b);
+ return;
+ case Enum e:
+ writer.WriteValue((int)(object)e);
+ return;
+ case Guid g:
+ writer.WriteValue(g.ToString());
+ return;
+ case DateTime dt:
+ writer.WriteValue(dt.ToString("o"));
+ return;
+
+ case Matrix4x4 md:
+ writer.WriteStartArray();
+ writer.WriteValue(md.M11);
+ writer.WriteValue(md.M12);
+ writer.WriteValue(md.M13);
+ writer.WriteValue(md.M14);
+ writer.WriteValue(md.M21);
+ writer.WriteValue(md.M22);
+ writer.WriteValue(md.M23);
+ writer.WriteValue(md.M24);
+ writer.WriteValue(md.M31);
+ writer.WriteValue(md.M32);
+ writer.WriteValue(md.M33);
+ writer.WriteValue(md.M34);
+ writer.WriteValue(md.M41);
+ writer.WriteValue(md.M42);
+ writer.WriteValue(md.M43);
+ writer.WriteValue(md.M44);
+ writer.WriteEndArray();
+ return;
+ }
+
+ // Handle ObjectReference before Base (since ObjectReference extends Base)
+ // This prevents double-serialization and properly propagates closures
+ if (value is ObjectReference objRef)
+ {
+ writer.WriteStartObject();
+ writer.WritePropertyName("speckle_type");
+ writer.WriteValue("reference");
+ writer.WritePropertyName("referencedId");
+ writer.WriteValue(objRef.referencedId);
+ writer.WriteEndObject();
+
+ // Propagate closure: add the referenced ID
+ closures[objRef.referencedId] = closures.TryGetValue(objRef.referencedId, out var existing) ? existing + 1 : 1;
+
+ // Propagate nested closures from the ObjectReference.closure dictionary
+ if (objRef.closure != null)
+ {
+ foreach (var kvp in objRef.closure)
+ {
+ closures[kvp.Key] = closures.TryGetValue(kvp.Key, out var existingDepth)
+ ? existingDepth + kvp.Value
+ : kvp.Value;
+ }
+ }
+
+ return;
+ }
+
+ if (value is Base baseObj)
+ {
+ if (isDetachable)
+ {
+ var childClosures = new Dictionary();
+ var (childId, childJson) = SerializeBase(baseObj, true, childClosures, detachedObjects);
+
+ detachedObjects.Add((childId, childJson, childClosures, baseObj, baseObj.speckle_type));
+
+ writer.WriteStartObject();
+ writer.WritePropertyName("speckle_type");
+ writer.WriteValue("reference");
+ writer.WritePropertyName("referencedId");
+ writer.WriteValue(childId.Value);
+ writer.WriteEndObject();
+
+ closures[childId.Value] = closures.TryGetValue(childId.Value, out var existing) ? existing + 1 : 1;
+
+ foreach (var kvp in childClosures)
+ {
+ closures[kvp.Key] = closures.TryGetValue(kvp.Key, out var existingDepth)
+ ? existingDepth + kvp.Value
+ : kvp.Value;
+ }
+ }
+ else
+ {
+ var inlineClosures = new Dictionary();
+ var (_, inlineJson) = SerializeBase(baseObj, false, inlineClosures, detachedObjects);
+
+ writer.WriteRawValue(inlineJson.Value);
+
+ foreach (var kvp in inlineClosures)
+ {
+ closures[kvp.Key] = closures.TryGetValue(kvp.Key, out var existingDepth)
+ ? existingDepth + kvp.Value
+ : kvp.Value;
+ }
+ }
+ return;
+ }
+
+ if (value is IDictionary dict)
+ {
+ writer.WriteStartObject();
+ foreach (DictionaryEntry kvp in dict)
+ {
+ if (kvp.Key is not string key)
+ {
+ throw new ArgumentException("Dictionary keys must be strings");
+ }
+
+ writer.WritePropertyName(key);
+ SerializeValue(kvp.Value, writer, false, closures, detachedObjects);
+ }
+ writer.WriteEndObject();
+ return;
+ }
+
+ if (value is IEnumerable enumerable and not string)
+ {
+ writer.WriteStartArray();
+ foreach (var item in enumerable)
+ {
+ SerializeValue(item, writer, isDetachable, closures, detachedObjects);
+ }
+ writer.WriteEndArray();
+ return;
+ }
+
+ writer.WriteValue(value.ToString());
+ }
+
+ private UploadItem ReferenceToUploadItem(ObjectReference existingRef)
+ {
+ var sb = Pools.StringBuilders.Get();
+ try
+ {
+ using var stringWriter = new StringWriter(sb);
+ using var jsonWriter = new JsonTextWriter(stringWriter);
+
+ jsonWriter.WriteStartObject();
+ jsonWriter.WritePropertyName("speckle_type");
+ jsonWriter.WriteValue("reference");
+ jsonWriter.WritePropertyName("referencedId");
+ jsonWriter.WriteValue(existingRef.referencedId);
+ jsonWriter.WritePropertyName("__closure");
+
+ if (existingRef.closure != null && existingRef.closure.Count > 0)
+ {
+ jsonWriter.WriteStartObject();
+ foreach (var kvp in existingRef.closure)
+ {
+ jsonWriter.WritePropertyName(kvp.Key);
+ jsonWriter.WriteValue(kvp.Value);
+ }
+ jsonWriter.WriteEndObject();
+ }
+ else
+ {
+ jsonWriter.WriteNull();
+ }
+
+ jsonWriter.WriteEndObject();
+ jsonWriter.Flush();
+
+ var refJson = new Json(stringWriter.ToString());
+
+ return new UploadItem(
+ existingRef.referencedId,
+ refJson,
+ existingRef.speckle_type,
+ existingRef // Pass through the original ObjectReference
+ );
+ }
+ finally
+ {
+ Pools.StringBuilders.Return(sb);
+ }
+ }
+}
diff --git a/src/Speckle.Sdk/Pipelines/Uploader.cs b/src/Speckle.Sdk/Pipelines/Uploader.cs
new file mode 100644
index 00000000..ec8133a4
--- /dev/null
+++ b/src/Speckle.Sdk/Pipelines/Uploader.cs
@@ -0,0 +1,175 @@
+using System.IO.Compression;
+using System.Net.Http.Headers;
+using System.Net.Http.Json;
+using System.Threading.Channels;
+
+namespace Speckle.Sdk.Pipelines;
+
+public sealed class Uploader : IDisposable
+{
+ private readonly string _projectId;
+ private readonly string _modelId;
+ private readonly string _ingestionId;
+ private readonly CancellationToken _cancellationToken;
+ private readonly HttpClient _client;
+ private readonly Channel _channel;
+ private readonly Task _sendTask;
+
+ public Uploader(
+ string projectId,
+ string modelId,
+ string ingestionId,
+ string apiEndpoint,
+ string authToken,
+ CancellationToken cancellationToken
+ )
+ {
+ _projectId = projectId;
+ _modelId = modelId;
+ _ingestionId = ingestionId;
+ _cancellationToken = cancellationToken;
+
+ Uri apiBaseUrl = new(new(apiEndpoint), "/api/v1/");
+ _client = new HttpClient { BaseAddress = apiBaseUrl, Timeout = TimeSpan.FromMinutes(30) };
+
+ _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
+
+ _channel = Channel.CreateBounded(
+ new BoundedChannelOptions(1000) { FullMode = BoundedChannelFullMode.Wait }
+ );
+
+ _sendTask = SendLoopAsync();
+ }
+
+ public ValueTask PushAsync(UploadItem item, CancellationToken ct = default) => _channel.Writer.WriteAsync(item, ct);
+
+ public async Task CompleteAsync()
+ {
+ _channel.Writer.Complete();
+ var result = await _sendTask.ConfigureAwait(false);
+ return result.IngestionId;
+ }
+
+ private async Task SendLoopAsync()
+ {
+ // 1. Stream channel to temp file
+ string tempFilePath = Path.GetTempFileName();
+ System.Diagnostics.Debug.WriteLine($"Temp file is at {tempFilePath}");
+ try
+ {
+ long fileSizeBytes;
+ {
+ using var fileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
+ using var gzip = new GZipStream(fileStream, CompressionLevel.Optimal);
+ using var writer = new StreamWriter(gzip);
+ await foreach (var item in _channel.Reader.ReadAllAsync(_cancellationToken).ConfigureAwait(false))
+ {
+ await writer.WriteLineAsync($"{item.Id}\t{item.Json}\t{item.SpeckleType}").ConfigureAwait(false);
+ }
+
+ await writer.FlushAsync().ConfigureAwait(false);
+ await gzip.FlushAsync(_cancellationToken).ConfigureAwait(false);
+ }
+ // fileStream.Flush();
+ // fileStream.Close();
+ fileSizeBytes = new FileInfo(tempFilePath).Length;
+
+ // 2. Request presigned URL
+ var signUri = new Uri($"projects/{_projectId}/models/{_modelId}/uploads/sign", UriKind.Relative);
+
+ var signResponse = await HttpClientExtensions
+ .PostAsJsonAsync(_client, signUri, _cancellationToken)
+ .ConfigureAwait(false);
+ signResponse.EnsureSuccessStatusCode();
+
+ var presignedUpload =
+ await signResponse.Content.ReadFromJsonAsync(_cancellationToken).ConfigureAwait(false)
+ ?? throw new InvalidOperationException("Failed to get presigned upload URL");
+
+ // 3. Upload to S3
+ using var fileStreamUpload = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+ Stream progressStream = fileStreamUpload; // TODO: wrap with progress stream
+
+ using var streamContent = new StreamContent(progressStream);
+ streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
+ streamContent.Headers.ContentLength = fileSizeBytes;
+
+ using var uploadRequest = new HttpRequestMessage(HttpMethod.Put, new Uri(presignedUpload.Url, UriKind.Absolute));
+ uploadRequest.Content = streamContent;
+
+ using var s3Client = new HttpClient(); // NOTE: using a separate client for S3 as we DO NOT NEED THE AUTH HEADER, presigned url's don't work with it.
+ using var uploadResponse = await s3Client
+ .SendAsync(uploadRequest, HttpCompletionOption.ResponseHeadersRead, _cancellationToken)
+ .ConfigureAwait(false);
+
+ uploadResponse.EnsureSuccessStatusCode();
+
+ // 4. Trigger processing
+ var processUri = new Uri($"projects/{_projectId}/models/{_modelId}/uploads/process", UriKind.Relative);
+ var processRequest = new ProcessUploadRequest { key = presignedUpload.Key, ingestionId = _ingestionId };
+
+ var processResponse = await HttpClientExtensions
+ .PostAsJsonAsync(_client, processUri, processRequest, _cancellationToken)
+ .ConfigureAwait(false);
+ processResponse.EnsureSuccessStatusCode();
+
+ var processResult = await processResponse
+ .Content.ReadFromJsonAsync(_cancellationToken)
+ .ConfigureAwait(false);
+
+ if (processResult == null)
+ {
+ throw new InvalidOperationException("Failed to trigger upload processing");
+ }
+
+ return new UploadResult { IngestionId = processResult.ingestionId };
+ }
+ catch (Exception ex) when (!ex.IsFatal())
+ {
+ throw;
+ }
+ finally
+ {
+ // 5. Clean up temp file
+ if (File.Exists(tempFilePath))
+ {
+ try
+ {
+ //File.Delete(tempFilePath);
+ }
+#pragma warning disable CA1031
+ catch
+#pragma warning restore CA1031
+ {
+ // Best effort
+ }
+ }
+ }
+ }
+
+ public void Dispose() => _client.Dispose();
+}
+
+// DTOs
+internal record PresignedUploadResponse
+{
+ public required string Url { get; init; }
+ public required string Key { get; init; }
+}
+
+internal record ProcessUploadRequest
+{
+ public required string key { get; init; }
+ public required string ingestionId { get; init; }
+}
+
+internal record ProcessUploadResponse
+{
+ public required string ingestionId { get; init; }
+}
+
+internal record UploadResult
+{
+ public required string IngestionId { get; init; }
+}
diff --git a/src/Speckle.Sdk/Pipelines/Uploader_Old.cs b/src/Speckle.Sdk/Pipelines/Uploader_Old.cs
new file mode 100644
index 00000000..fa8f73c6
--- /dev/null
+++ b/src/Speckle.Sdk/Pipelines/Uploader_Old.cs
@@ -0,0 +1,83 @@
+// using System.IO.Compression;
+// using System.Net.Http.Headers;
+// using System.Threading.Channels;
+//
+// namespace Speckle.Sdk.Pipeline;
+//
+// public sealed class UploaderOld : IDisposable
+// {
+// private readonly string _projectId;
+// private readonly string _modelId;
+// private readonly HttpClient _client;
+// private readonly Channel _channel;
+// private readonly Task _sendTask;
+//
+// public UploaderOld(string projectId, string modelId, string? authToken, string? apiEndpoint)
+// {
+// _projectId = projectId;
+// _modelId = modelId;
+//
+// Uri apiBaseUrl = !string.IsNullOrEmpty(apiEndpoint)
+// ? new Uri(apiEndpoint)
+// // : new Uri("http://dimitries-macbook-pro.mermaid-emperor.ts.net/api/v1/");
+// : new Uri("http://zog.local:3000/api/v1/");
+// _client = new HttpClient { BaseAddress = apiBaseUrl, Timeout = TimeSpan.FromMinutes(10) };
+//
+// if (authToken != null)
+// {
+// _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
+// }
+//
+// _channel = Channel.CreateBounded(
+// new BoundedChannelOptions(1000) { FullMode = BoundedChannelFullMode.Wait } // if we're not able to write fast enough, we'll block writes
+// );
+//
+// _sendTask = SendLoopAsync(projectId, "test");
+// }
+//
+// public ValueTask PushAsync(UploadItem item, CancellationToken ct = default) => _channel.Writer.WriteAsync(item, ct);
+//
+// public async Task CompleteAsync()
+// {
+// _channel.Writer.Complete();
+// await _sendTask.ConfigureAwait(false);
+// }
+//
+// private async Task SendLoopAsync(string projectId, string modelId)
+// {
+// var content = new PushStreamContent(
+// async (stream, _, _) =>
+// {
+// var gzip = new GZipStream(stream, CompressionLevel.Optimal);
+// var writer = new StreamWriter(gzip); // new StreamWriter(gzip, System.Text.Encoding.UTF8, 20 * 1024 * 1024); // potential lever for controlling memory pressure
+// try
+// {
+// // extra levers for memory pressure in here: we can manually flush every x items or every x bytes
+// await foreach (var item in _channel.Reader.ReadAllAsync().ConfigureAwait(false))
+// {
+// await writer.WriteLineAsync($"{item.Id}\t{item.Json}\t{item.SpeckleType}").ConfigureAwait(false);
+// }
+// }
+// finally
+// {
+// await writer.FlushAsync().ConfigureAwait(false);
+// await gzip.FlushAsync().ConfigureAwait(false);
+// writer.Dispose();
+// gzip.Dispose();
+// }
+// },
+// new MediaTypeHeaderValue("application/x-ndjson")
+// );
+//
+// var uri = new Uri($"projects/{projectId}/models/{modelId}/versions", UriKind.Relative);
+// var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = content };
+// request.Headers.TransferEncodingChunked = true; // NOTE: important for streaming to happen.
+// var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
+// response.EnsureSuccessStatusCode();
+//
+// // Consume the response body to fully complete the request
+// await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+// }
+//
+// public void Dispose() => _client.Dispose();
+// }
diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs b/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs
index 2a88e3db..9c5b02bf 100644
--- a/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs
@@ -2,7 +2,13 @@ using System.Text;
namespace Speckle.Sdk.Serialisation.V2.Send;
-public sealed record BaseItem(Id Id, Json Json, bool NeedsStorage, Dictionary? Closures) : IHasByteSize
+public sealed record BaseItem(
+ Id Id,
+ Json Json,
+ bool NeedsStorage,
+ Dictionary? Closures,
+ bool? IsReference = false
+) : IHasByteSize
{
public int ByteSize { get; } = Encoding.UTF8.GetByteCount(Json.Value);
diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectFlopper.cs b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectFlopper.cs
new file mode 100644
index 00000000..2027a6e3
--- /dev/null
+++ b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectFlopper.cs
@@ -0,0 +1,79 @@
+using System.IO.Compression;
+using System.Net.Http.Headers;
+using System.Threading.Channels;
+
+namespace Speckle.Sdk.Serialisation.V2.Send;
+
+#pragma warning disable CA1001
+public sealed class ObjectFlopper
+#pragma warning restore CA1001
+{
+ private readonly Uri _url;
+ private readonly string _streamId;
+ private readonly HttpClient _client;
+ private readonly Channel _channel;
+ private readonly Task _sendTask;
+
+ public ObjectFlopper(Uri _, string streamId, string? authToken)
+ {
+ _streamId = streamId;
+ _url = new Uri("http://zog.local:3000/api/v1/");
+ _client = new HttpClient { BaseAddress = _url, Timeout = TimeSpan.FromMinutes(10) };
+
+ if (authToken != null)
+ {
+ _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
+ }
+
+ _channel = Channel.CreateBounded(
+ new BoundedChannelOptions(1000) { FullMode = BoundedChannelFullMode.Wait }
+ );
+
+ _sendTask = SendLoopAsync(streamId, "test");
+ }
+
+ public ValueTask PushAsync(BaseItem item, CancellationToken ct = default) => _channel.Writer.WriteAsync(item, ct);
+
+ public async Task CompleteAsync()
+ {
+ _channel.Writer.Complete();
+ await _sendTask.ConfigureAwait(false);
+ }
+
+ private async Task SendLoopAsync(string projectId, string modelId)
+ {
+ var content = new PushStreamContent(
+ async (stream, _, _) =>
+ {
+ var gzip = new GZipStream(stream, CompressionLevel.Optimal);
+ var writer = new StreamWriter(gzip); //new StreamWriter(gzip, System.Text.Encoding.UTF8, 20 * 1024 * 1024);
+ try
+ {
+ await foreach (var item in _channel.Reader.ReadAllAsync().ConfigureAwait(false))
+ {
+ await writer.WriteLineAsync($"{item.Id}\t{item.Json}").ConfigureAwait(false);
+ }
+ }
+ finally
+ {
+ await writer.FlushAsync().ConfigureAwait(false);
+ await gzip.FlushAsync().ConfigureAwait(false);
+ writer.Dispose();
+ gzip.Dispose();
+ }
+ },
+ new MediaTypeHeaderValue("application/x-ndjson")
+ );
+
+ var uri = new Uri($"projects/{projectId}/models/{modelId}/versions", UriKind.Relative);
+ var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = content };
+ request.Headers.TransferEncodingChunked = true; // NOTE: important for streaming to happen.
+ var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+
+ // Consume the response body to fully complete the request
+ await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ }
+
+ public void Dispose() => _client.Dispose();
+}
diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectFlopperGandalf.cs b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectFlopperGandalf.cs
new file mode 100644
index 00000000..abccd3a6
--- /dev/null
+++ b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectFlopperGandalf.cs
@@ -0,0 +1,90 @@
+using System.IO.Compression;
+using System.IO.Pipelines;
+using System.Net.Http.Headers;
+using System.Threading.Channels;
+
+namespace Speckle.Sdk.Serialisation.V2.Send;
+
+#pragma warning disable CA1001
+public sealed class ObjectFlopperGandalf
+#pragma warning restore CA1001
+{
+ private readonly Uri _url;
+ private readonly string _streamId;
+ private readonly HttpClient _client;
+ private readonly Channel _channel;
+ private readonly Task _sendTask;
+
+ public ObjectFlopperGandalf(Uri _, string streamId, string? authToken)
+ {
+ _streamId = streamId;
+ _url = new Uri("http://bender-2.local:3000/api/v1/");
+ _client = new HttpClient { BaseAddress = _url, Timeout = TimeSpan.FromMinutes(10) };
+
+ if (authToken != null)
+ {
+ _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
+ }
+
+ _channel = Channel.CreateBounded(
+ new BoundedChannelOptions(1000) { FullMode = BoundedChannelFullMode.Wait }
+ );
+
+ _sendTask = SendLoopAsync(streamId, "test");
+ }
+
+ public ValueTask PushAsync(BaseItem item, CancellationToken ct = default) => _channel.Writer.WriteAsync(item, ct);
+
+ public async Task CompleteAsync()
+ {
+ _channel.Writer.Complete();
+ await _sendTask.ConfigureAwait(false);
+ }
+
+ private async Task SendLoopAsync(string projectId, string modelId)
+ {
+ var pipe = new Pipe();
+
+ // Start writing to pipe immediately in background
+ var writeTask = Task.Run(async () =>
+ {
+ var gzip = new GZipStream(pipe.Writer.AsStream(), CompressionLevel.Optimal);
+ var writer = new StreamWriter(gzip);
+
+ try
+ {
+ await foreach (var item in _channel.Reader.ReadAllAsync().ConfigureAwait(false))
+ {
+ await writer.WriteLineAsync($"{item.Id}\t{item.Json}").ConfigureAwait(false);
+ await writer.FlushAsync().ConfigureAwait(false);
+ }
+ }
+ finally
+ {
+ await writer.FlushAsync().ConfigureAwait(false);
+ await gzip.FlushAsync().ConfigureAwait(false);
+ writer.Dispose();
+ gzip.Dispose();
+ await pipe.Writer.CompleteAsync().ConfigureAwait(false);
+ }
+ });
+
+ // Start HTTP request immediately, reading from pipe
+ var content = new StreamContent(pipe.Reader.AsStream());
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/x-ndjson");
+ content.Headers.ContentEncoding.Add("gzip");
+
+ var uri = new Uri($"projects/{projectId}/models/{modelId}/objects", UriKind.Relative);
+ var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = content };
+
+ var responseTask = _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
+
+ // Wait for both
+ await Task.WhenAll(writeTask, responseTask).ConfigureAwait(false);
+
+ var response = await responseTask.ConfigureAwait(false);
+ response.EnsureSuccessStatusCode();
+ }
+
+ public void Dispose() => _client.Dispose();
+}
diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs
index 6dac6f6f..29965525 100644
--- a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs
@@ -113,16 +113,6 @@ public sealed class SerializeProcess(
_processSource.Token
);
var findTotalObjectsTask = Task.CompletedTask;
- if (!options.SkipFindTotalObjects)
- {
- ThrowIfFailed();
- findTotalObjectsTask = Task.Factory.StartNew(
- () => TraverseTotal(root),
- _processSource.Token,
- TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness,
- _highest
- );
- }
await Traverse(root).ConfigureAwait(false);
ThrowIfFailed();
@@ -133,6 +123,7 @@ public sealed class SerializeProcess(
ThrowIfFailed();
await WaitForSchedulerCompletion().ConfigureAwait(false);
ThrowIfFailed();
+
return new(root.id.NotNull(), baseSerializer.ObjectReferences.Freeze());
}
catch (OperationCanceledException)
diff --git a/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs b/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs
index b708f4d4..3c56b4af 100644
--- a/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs
+++ b/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs
@@ -35,7 +35,8 @@ public class SerializeProcessFactory(
IServerObjectManager serverObjectManager,
IProgress? progress,
CancellationToken cancellationToken,
- SerializeProcessOptions? options = null
+ SerializeProcessOptions? options = null,
+ ObjectFlopper? objectFlopper = null
) =>
new SerializeProcess(
progress,
diff --git a/src/Speckle.Sdk/Speckle.Sdk.csproj b/src/Speckle.Sdk/Speckle.Sdk.csproj
index 5dbc5924..664d497a 100644
--- a/src/Speckle.Sdk/Speckle.Sdk.csproj
+++ b/src/Speckle.Sdk/Speckle.Sdk.csproj
@@ -23,19 +23,23 @@
+
+
+
+
+
-
diff --git a/src/Speckle.Sdk/packages.lock.json b/src/Speckle.Sdk/packages.lock.json
index 5ea95453..b7195702 100644
--- a/src/Speckle.Sdk/packages.lock.json
+++ b/src/Speckle.Sdk/packages.lock.json
@@ -13,13 +13,25 @@
"System.Reactive": "5.0.0"
}
},
+ "Microsoft.AspNet.WebApi.Client": {
+ "type": "Direct",
+ "requested": "[6.0.0, )",
+ "resolved": "6.0.0",
+ "contentHash": "zXeWP03dTo67AoDHUzR+/urck0KFssdCKOC+dq7Nv1V2YbFh/nIg09L0/3wSvyRONEdwxGB/ssEGmPNIIhAcAw==",
+ "dependencies": {
+ "Newtonsoft.Json": "13.0.1",
+ "Newtonsoft.Json.Bson": "1.0.2",
+ "System.Memory": "4.5.5",
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Direct",
- "requested": "[5.0.0, )",
- "resolved": "5.0.0",
- "contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
- "System.Threading.Tasks.Extensions": "4.5.4"
+ "System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.CSharp": {
@@ -99,6 +111,39 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
+ "System.IO.Pipelines": {
+ "type": "Direct",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "26LbFXHKd7PmRnWlkjnYgmjd5B6HYVG+1MpTO25BdxTJnx6D0O16JPAC/S4YBqjtt4YpfGj1QO/Ss6SPMGEGQw==",
+ "dependencies": {
+ "System.Buffers": "4.6.1",
+ "System.Memory": "4.6.3",
+ "System.Threading.Tasks.Extensions": "4.6.3"
+ }
+ },
+ "System.Net.Http.Json": {
+ "type": "Direct",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "XGOWt78ccgO9esyNlCemlS2b8JZPrH85pk/MvdHJxp6KwwUY/GnDaw2fPpJa7lgotiTkWnXnhip+ULNKaP7a8A==",
+ "dependencies": {
+ "System.Buffers": "4.6.1",
+ "System.Memory": "4.6.3",
+ "System.Text.Json": "10.0.1",
+ "System.Threading.Tasks.Extensions": "4.6.3"
+ }
+ },
+ "System.Threading.Channels": {
+ "type": "Direct",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "10.0.1",
+ "System.Threading.Tasks.Extensions": "4.6.3"
+ }
+ },
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -196,6 +241,19 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.1",
+ "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
+ },
+ "Newtonsoft.Json.Bson": {
+ "type": "Transitive",
+ "resolved": "1.0.2",
+ "contentHash": "QYFyxhaABwmq3p/21VrZNYvCg3DaEoN/wUuw5nmfAf0X3HLjgupwhkEWdgfb9nvGAUIv3osmZoD3kKl4jxEmYQ==",
+ "dependencies": {
+ "Newtonsoft.Json": "12.0.1"
+ }
+ },
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -228,8 +286,8 @@
},
"System.Buffers": {
"type": "Transitive",
- "resolved": "4.4.0",
- "contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw=="
+ "resolved": "4.6.1",
+ "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw=="
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
@@ -238,18 +296,18 @@
},
"System.Memory": {
"type": "Transitive",
- "resolved": "4.5.3",
- "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==",
+ "resolved": "4.6.3",
+ "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==",
"dependencies": {
- "System.Buffers": "4.4.0",
- "System.Numerics.Vectors": "4.4.0",
- "System.Runtime.CompilerServices.Unsafe": "4.5.2"
+ "System.Buffers": "4.6.1",
+ "System.Numerics.Vectors": "4.6.1",
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
- "resolved": "4.4.0",
- "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
+ "resolved": "4.6.1",
+ "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q=="
},
"System.Reactive": {
"type": "Transitive",
@@ -271,8 +329,8 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
- "resolved": "4.5.3",
- "contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw=="
+ "resolved": "6.1.2",
+ "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Runtime.InteropServices.WindowsRuntime": {
"type": "Transitive",
@@ -282,16 +340,75 @@
"System.Runtime": "4.3.0"
}
},
+ "System.Text.Encodings.Web": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "cVAka0o1rJJ5/De0pjNs7jcaZk5hUGf1HGzUyVmE2MEB1Vf0h/8qsWxImk1zjitCbeD2Avaq2P2+usdvqgbeVQ==",
+ "dependencies": {
+ "System.Buffers": "4.6.1",
+ "System.Memory": "4.6.3",
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
+ }
+ },
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
- "resolved": "4.5.4",
- "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
+ "resolved": "4.6.3",
+ "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2"
+ }
+ },
+ "speckle.connectors.common": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
+ "Speckle.Connectors.Logging": "[1.0.0, )",
+ "Speckle.Converters.Common": "[1.0.0, )",
+ "Speckle.Objects": "[1.0.0, )"
+ }
+ },
+ "speckle.connectors.logging": {
+ "type": "Project"
+ },
+ "speckle.converters.common": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
+ "Speckle.Objects": "[1.0.0, )"
+ }
+ },
+ "speckle.objects": {
+ "type": "Project",
+ "dependencies": {
+ "Speckle.Sdk": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
"type": "Project"
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "CentralTransitive",
+ "requested": "[2.2.0, )",
+ "resolved": "2.2.0",
+ "contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
+ }
+ },
+ "System.Text.Json": {
+ "type": "CentralTransitive",
+ "requested": "[8.0.5, )",
+ "resolved": "10.0.1",
+ "contentHash": "EsgwDgU1PFqhrFA9l5n+RBu76wFhNGCEwu8ITrBNhjPP3MxLyklroU5GIF8o6JYpYg6T4KD/VICfMdgPAvNp5g==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "10.0.1",
+ "System.Buffers": "4.6.1",
+ "System.IO.Pipelines": "10.0.1",
+ "System.Memory": "4.6.3",
+ "System.Runtime.CompilerServices.Unsafe": "6.1.2",
+ "System.Text.Encodings.Web": "10.0.1",
+ "System.Threading.Tasks.Extensions": "4.6.3"
+ }
}
},
"net8.0": {
@@ -306,6 +423,18 @@
"System.Reactive": "5.0.0"
}
},
+ "Microsoft.AspNet.WebApi.Client": {
+ "type": "Direct",
+ "requested": "[6.0.0, )",
+ "resolved": "6.0.0",
+ "contentHash": "zXeWP03dTo67AoDHUzR+/urck0KFssdCKOC+dq7Nv1V2YbFh/nIg09L0/3wSvyRONEdwxGB/ssEGmPNIIhAcAw==",
+ "dependencies": {
+ "Newtonsoft.Json": "13.0.1",
+ "Newtonsoft.Json.Bson": "1.0.2",
+ "System.Memory": "4.5.5",
+ "System.Threading.Tasks.Extensions": "4.5.4"
+ }
+ },
"Microsoft.Data.Sqlite": {
"type": "Direct",
"requested": "[7.0.5, )",
@@ -368,6 +497,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
+ "System.IO.Pipelines": {
+ "type": "Direct",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "26LbFXHKd7PmRnWlkjnYgmjd5B6HYVG+1MpTO25BdxTJnx6D0O16JPAC/S4YBqjtt4YpfGj1QO/Ss6SPMGEGQw=="
+ },
+ "System.Net.Http.Json": {
+ "type": "Direct",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "XGOWt78ccgO9esyNlCemlS2b8JZPrH85pk/MvdHJxp6KwwUY/GnDaw2fPpJa7lgotiTkWnXnhip+ULNKaP7a8A==",
+ "dependencies": {
+ "System.Text.Json": "10.0.1"
+ }
+ },
+ "System.Threading.Channels": {
+ "type": "Direct",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
+ },
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
@@ -455,6 +605,19 @@
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.1",
+ "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
+ },
+ "Newtonsoft.Json.Bson": {
+ "type": "Transitive",
+ "resolved": "1.0.2",
+ "contentHash": "QYFyxhaABwmq3p/21VrZNYvCg3DaEoN/wUuw5nmfAf0X3HLjgupwhkEWdgfb9nvGAUIv3osmZoD3kKl4jxEmYQ==",
+ "dependencies": {
+ "Newtonsoft.Json": "12.0.1"
+ }
+ },
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -492,8 +655,8 @@
},
"System.Memory": {
"type": "Transitive",
- "resolved": "4.5.3",
- "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
+ "resolved": "4.5.5",
+ "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
"System.Reactive": {
"type": "Transitive",
@@ -505,8 +668,62 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
+ "System.Text.Encodings.Web": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "cVAka0o1rJJ5/De0pjNs7jcaZk5hUGf1HGzUyVmE2MEB1Vf0h/8qsWxImk1zjitCbeD2Avaq2P2+usdvqgbeVQ=="
+ },
+ "System.Threading.Tasks.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.5.4",
+ "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg=="
+ },
+ "speckle.connectors.common": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
+ "Speckle.Connectors.Logging": "[1.0.0, )",
+ "Speckle.Converters.Common": "[1.0.0, )",
+ "Speckle.Objects": "[1.0.0, )"
+ }
+ },
+ "speckle.connectors.logging": {
+ "type": "Project"
+ },
+ "speckle.converters.common": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
+ "Speckle.Objects": "[1.0.0, )"
+ }
+ },
+ "speckle.objects": {
+ "type": "Project",
+ "dependencies": {
+ "Speckle.Sdk": "[1.0.0, )"
+ }
+ },
"speckle.sdk.dependencies": {
"type": "Project"
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "CentralTransitive",
+ "requested": "[2.2.0, )",
+ "resolved": "2.2.0",
+ "contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
+ }
+ },
+ "System.Text.Json": {
+ "type": "CentralTransitive",
+ "requested": "[8.0.5, )",
+ "resolved": "10.0.1",
+ "contentHash": "EsgwDgU1PFqhrFA9l5n+RBu76wFhNGCEwu8ITrBNhjPP3MxLyklroU5GIF8o6JYpYg6T4KD/VICfMdgPAvNp5g==",
+ "dependencies": {
+ "System.IO.Pipelines": "10.0.1",
+ "System.Text.Encodings.Web": "10.0.1"
+ }
}
}
}