Compare commits

...

15 Commits

Author SHA1 Message Date
Jedd Morgan c517dead03 Internal 2026-03-24 10:24:43 +00:00
Jedd Morgan 2b61ab7d2e fallback mechanism 2026-03-24 10:04:44 +00:00
Jedd Morgan 4b319499c3 Fix mistake 2026-03-23 16:10:44 +00:00
Jedd Morgan d4055c6ff1 Avoid deprecated fields 2026-03-23 16:03:20 +00:00
Jedd Morgan af0fc9f669 Merge pull request #439 from specklesystems/duckdev
.NET Build and Publish / build (push) Has been cancelled
feat(packfile): duckdb packfile api (duckdev -> main)
2026-03-23 11:08:57 +00:00
Jedd Morgan edbc884d74 Clean diff 2026-03-23 10:45:57 +00:00
Jedd Morgan 025d7f70ba Merge branch 'main' into duckdev 2026-03-23 10:41:22 +00:00
Jedd Morgan 70acc06f37 feat(otel): Change sig for remote (#460)
.NET Build and Publish / build (push) Has been cancelled
* Remote changes

* Fix tests

* Fix tests

* ditto
2026-03-16 15:32:33 +00:00
Jedd Morgan a2c99a537a feat(otel): Add proper support for remote spans (#458)
.NET Build and Publish / build (push) Has been cancelled
* Add proper support for remote spans

* Fix tests
2026-03-12 12:52:10 +00:00
Jedd Morgan 906ff9c3ff test: duck tests (#455)
.NET Build and Publish / build (push) Has been cancelled
* lets get some testing action!

* revert url

* we'll try these fixes

* we'll try this, if it doesn't work I'm deleting the test

* try this

* Add optional parent id to traces

* how about now

* and as zero

* Fix build

* fix code cov

* FML subscription tests are so flaky
2026-03-11 16:30:24 +00:00
Jedd Morgan 515d45528d feat(otel): traces for new send pipeline (#457)
* 😃 extra traces

* default
2026-03-11 14:51:58 +00:00
dependabot[bot] abf86eda03 chore(deps): bump docker/login-action from 3 to 4 (#456)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 09:50:34 +00:00
Jedd Morgan f777050c10 change order of tab delimited json (#454)
.NET Build and Publish / build (push) Has been cancelled
2026-03-05 13:40:18 +00:00
Jedd Morgan c4e956cdb4 fix(build): Option 2 - "keep channels il-repacked" (#453)
* Repacked channel

* transitive for sdk

* restore solution

* comment tweaks

* Fix print line
2026-03-05 12:47:56 +00:00
Oğuzhan Koral a662fb54c2 fix: channels reference only for net standard (#450)
.NET Build and Publish / build (push) Has been cancelled
2026-02-27 10:32:16 +00:00
46 changed files with 596 additions and 309 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
- name: 🔐 Login to Github Container Registry
if: ${{ inputs.use-internal-image }}
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: "ghcr.io"
username: ${{ github.actor }}
+5 -1
View File
@@ -1,7 +1,11 @@
name: PR Test
on:
pull_request:
pull_request: {}
push:
branches:
- "main" # Need to run for codecov to compare against the BASE
jobs:
build:
-7
View File
@@ -46,13 +46,6 @@ jobs:
SEMVER: ${{ steps.set-version.outputs.SEMVER }}
FILE_VERSION: ${{ steps.set-version.outputs.FILE_VERSION }}
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
continue-on-error: true
with:
fail_ci_if_error: true
files: tests/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
+2 -1
View File
@@ -15,6 +15,7 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[2.2.0,)" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="[2.2.0,)" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="[9.0.4,)" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="[2.2.0,)" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Newtonsoft.Json.Schema" Version="4.0.1" />
@@ -27,7 +28,7 @@
<PackageVersion Include="Speckle.DoubleNumerics" Version="4.1.0" />
<PackageVersion Include="SimpleExec" Version="12.0.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.1" />
<PackageVersion Include="System.Threading.Channels" Version="9.0.4" />
<PackageVersion Include="Verify.Quibble" Version="2.1.1" />
<PackageVersion Include="Verify.Xunit" Version="29.4.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
+20 -34
View File
@@ -86,14 +86,6 @@
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -250,8 +242,8 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Runtime.InteropServices.WindowsRuntime": {
"type": "Transitive",
@@ -273,10 +265,10 @@
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.objects": {
@@ -295,12 +287,14 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
"type": "Project"
"type": "Project",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "[9.0.4, )"
}
},
"GraphQL.Client": {
"type": "CentralTransitive",
@@ -313,6 +307,15 @@
"System.Reactive": "5.0.0"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.CSharp": {
"type": "CentralTransitive",
"requested": "[4.7.0, )",
@@ -358,16 +361,6 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"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": {
@@ -565,8 +558,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -622,12 +614,6 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
}
}
}
+20 -34
View File
@@ -54,14 +54,6 @@
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -213,8 +205,8 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
"resolved": "4.5.3",
"contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw=="
},
"System.Runtime.InteropServices.WindowsRuntime": {
"type": "Transitive",
@@ -226,10 +218,10 @@
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.sdk": {
@@ -242,12 +234,14 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
"type": "Project"
"type": "Project",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "[9.0.4, )"
}
},
"GraphQL.Client": {
"type": "CentralTransitive",
@@ -260,6 +254,15 @@
"System.Reactive": "5.0.0"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.CSharp": {
"type": "CentralTransitive",
"requested": "[4.7.0, )",
@@ -305,16 +308,6 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"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": {
@@ -486,8 +479,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -543,12 +535,6 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
}
}
}
@@ -5,6 +5,7 @@ public interface ISdkActivity : IDisposable
void SetTag(string key, object? value);
void RecordException(Exception e);
string TraceId { get; }
string SpanId { get; }
void SetStatus(SdkActivityStatusCode code);
void InjectHeaders(Action<string, string> header);
@@ -1,8 +1,20 @@
using System.Runtime.CompilerServices;
using Speckle.Connectors.Logging;
namespace Speckle.Sdk.Logging;
public interface ISdkActivityFactory : IDisposable
{
ISdkActivity? Start(string? name = default, [CallerMemberName] string source = "");
ISdkActivity? Start(
string? name = null,
SdkActivityKind kind = SdkActivityKind.Internal,
[CallerMemberName] string source = ""
);
ISdkActivity? StartRemote(
string traceContext,
SdkActivityKind kind,
string? name = null,
[CallerMemberName] string source = ""
);
}
@@ -0,0 +1,49 @@
using System.Threading.Channels;
namespace Speckle.Sdk.Dependencies;
/// <summary>
/// For various reasons related to our use of ILRepack.FullAuto,
/// we cannot use Channels from the SDK project.
/// We have to keep usage of it inside the Sdk.Dependencies project.
///
/// For the sake of quick development, I've wrapped the <see cref="Channel"/> class here in a type
/// that is safe to use from the SDK project.
///
/// As and when we need more functions, we can add them here.
///
/// And yes... I'm not very happy about the way we've set this up
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class RepackedChannel<T>
{
private readonly Channel<T> _channel;
public RepackedChannel(int capacity, bool singleReader, bool singleWriter)
{
_channel = Channel.CreateBounded<T>(
new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait,
SingleReader = singleReader,
SingleWriter = singleWriter,
}
);
}
public void CompleteWriter() => _channel.Writer.Complete();
public ValueTask WriteAsync(T item, CancellationToken cancellationToken) =>
_channel.Writer.WriteAsync(item, cancellationToken);
public IAsyncEnumerable<T> ReadAllAsync(CancellationToken cancellationToken) =>
_channel.Reader.ReadAllAsync(cancellationToken);
// public async Task ReadAllAsync(Func<T, Task> callback, CancellationToken cancellationToken)
// {
// await foreach (T item in _channel.Reader.ReadAllAsync(cancellationToken))
// {
// await callback.Invoke(item).ConfigureAwait(false);
// }
// }
}
@@ -28,4 +28,36 @@
<PackageReference Include="Open.ChannelExtensions" PrivateAssets="all" />
<PackageReference Include="System.Threading.Channels" PrivateAssets="all" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
<Target Name="BeforeILRepackPrepareBuild" BeforeTargets="ILRepackPrepareBuild">
<ItemGroup>
<!--
We're Being selective about which assemblies we're il-repacking
Avoiding repacling `Microsoft.Bcl.AsyncInterfaces.dll` because we need types like `ValueTask` and `IAsyncEnumerable` to be external
Yes, this does beg the question, why are we using `IlRepack.FullAuto` instead of raw ILRepack. Well the truth is, I'd like to move away from FullAuto
since it's unmaintaned and is lagging behind ILRepack version.
-->
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Numerics.Vectors.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Runtime.CompilerServices.Unsafe.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Memory.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Open.ChannelExtensions.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Threading.Channels.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)System.Collections.Immutable.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Polly.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Polly.Contrib.WaitAndRetry.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Polly.Extensions.Http.dll" />
<_ILRepackIncludeAssemblies_Items Include="$(OutputPath)Microsoft.Extensions.ObjectPool.dll" />
<_ILRepackExcludeAssemblies_Items Include="$(OutputPath)*.dll" Exclude="@(_ILRepackIncludeAssemblies_Items)" />
</ItemGroup>
<Message
Text="These are the packages we are NOT ilrepacking '@(_ILRepackExcludeAssemblies_Items)'"
Importance="high"
/>
<PropertyGroup>
<ILRepackExcludeAssemblies>@(_ILRepackExcludeAssemblies_Items)</ILRepackExcludeAssemblies>
</PropertyGroup>
</Target>
</Project>
@@ -0,0 +1,30 @@
namespace Speckle.Connectors.Logging;
public enum SdkActivityKind
{
/// <summary>
/// Default value.
/// Indicates that the Activity represents an internal operation within an application, as opposed to an operations with remote parents or children.
/// </summary>
Internal = 0,
/// <summary>
/// Server activity represents request incoming from external component.
/// </summary>
Server = 1,
/// <summary>
/// Client activity represents outgoing request to the external component.
/// </summary>
Client = 2,
/// <summary>
/// Producer activity represents output provided to external components.
/// </summary>
Producer = 3,
/// <summary>
/// Consumer activity represents output received from an external component.
/// </summary>
Consumer = 4,
}
+22 -21
View File
@@ -11,6 +11,15 @@
"ILRepack": "2.0.33"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Direct",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
"requested": "[9.0.4, )",
@@ -82,12 +91,12 @@
},
"System.Threading.Channels": {
"type": "Direct",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg==",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "4qBn2H6/aXBpE/Pm3wY5yusY/pEvQz99NlWHrTUji0qCmOdbhhjaALcpmbfW2ksxlPM6i6S+QFLkpOQdyfeKYQ==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "10.0.1",
"System.Threading.Tasks.Extensions": "4.6.3"
"Microsoft.Bcl.AsyncInterfaces": "9.0.4",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"ILRepack": {
@@ -95,14 +104,6 @@
"resolved": "2.0.33",
"contentHash": "xb2h1CsOepoYwdXEPui9VcQglwABQwNf9cccZbf+acarEzF5PUp8Xx71nFXIhOgEdm6wrxAoF6xAxK4m/XFRUQ=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -149,15 +150,15 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
}
},
@@ -228,9 +229,9 @@
},
"System.Threading.Channels": {
"type": "Direct",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "4qBn2H6/aXBpE/Pm3wY5yusY/pEvQz99NlWHrTUji0qCmOdbhhjaALcpmbfW2ksxlPM6i6S+QFLkpOQdyfeKYQ=="
},
"ILRepack": {
"type": "Transitive",
@@ -14,7 +14,8 @@ public record ModelIngestionCreateInput(
string modelId,
string projectId,
string progressMessage,
SourceDataInput sourceData
SourceDataInput sourceData,
int? maxIdleTimeoutSeconds = null
);
public record ModelIngestionUpdateInput(string ingestionId, string projectId, string progressMessage, double? progress);
@@ -1,3 +1,5 @@
using Speckle.Newtonsoft.Json;
namespace Speckle.Sdk.Api.GraphQL.Models;
public class LimitedWorkspace
@@ -6,8 +8,12 @@ public class LimitedWorkspace
public string name { get; init; }
public string? role { get; init; }
public string slug { get; init; }
public string? logo { get; init; }
public string? logoUri { get; init; }
public string? description { get; init; }
[JsonIgnore]
[Obsolete($"Deprecated, use {nameof(logoUri)} instead", true)]
public string? logo { get; init; }
}
public class Workspace : LimitedWorkspace
@@ -16,9 +22,13 @@ public class Workspace : LimitedWorkspace
public DateTime updatedAt { get; init; }
public bool readOnly { get; init; }
public WorkspacePermissionChecks permissions { get; init; }
[JsonIgnore]
[Obsolete("Workspaces no longer have creation state, is always created true", true)]
public WorkspaceCreationState? creationState { get; init; }
}
[Obsolete("Workspaces no longer have creation state, is always created true")]
public sealed class WorkspaceCreationState
{
public bool completed { get; init; }
@@ -264,15 +264,11 @@ public sealed class ActiveUserResource
name
role
slug
logo
logoUrl
createdAt
updatedAt
readOnly
description
creationState
{
completed
}
permissions {
canCreateProject {
authorized
@@ -317,7 +313,7 @@ public sealed class ActiveUserResource
/// <remarks>note this returns a <see cref="LimitedWorkspace"/>, because it may be a workspace the user is not a member of</remarks>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
/// <exception cref="SpeckleException">The ActiveUser could not be found (e.g. the client is not authenticated)</exception>
public async Task<LimitedWorkspace?> GetActiveWorkspace(CancellationToken cancellationToken = default)
private async Task<LimitedWorkspace?> GetActiveWorkspace_Legacy(CancellationToken cancellationToken = default)
{
//language=graphql
const string QUERY = """
@@ -328,7 +324,6 @@ public sealed class ActiveUserResource
name
role
slug
logo
description
}
}
@@ -349,6 +344,47 @@ public sealed class ActiveUserResource
return response.data.data;
}
public async Task<LimitedWorkspace?> GetActiveWorkspace(CancellationToken cancellationToken = default)
{
//language=graphql
const string QUERY = """
query ActiveUser {
data:activeUser {
data:activeWorkspace {
id
name
role
slug
logoUrl
description
}
}
}
""";
var request = new GraphQLRequest { Query = QUERY };
NullableResponse<NullableResponse<LimitedWorkspace?>?> response;
try
{
response = await _client
.ExecuteGraphQLRequest<NullableResponse<NullableResponse<LimitedWorkspace?>?>>(request, cancellationToken)
.ConfigureAwait(false);
}
catch (SpeckleGraphQLInvalidQueryException)
{
//v2.x.x servers do not have a logoUrl property
return await GetActiveWorkspace_Legacy(cancellationToken).ConfigureAwait(false);
}
if (response.data is null)
{
throw new SpeckleException("GraphQL response indicated that the ActiveUser could not be found");
}
return response.data.data;
}
/// <param name="limit">Max number of projects to fetch</param>
/// <param name="cursor">Optional cursor for pagination</param>
/// <param name="filter">Optional filter</param>
@@ -52,7 +52,6 @@ public sealed class OtherUserResource
/// <param name="query">String to search for. Must be at least 3 characters</param>
/// <param name="limit">Max number of users to fetch</param>
/// <param name="cursor">Optional cursor for pagination</param>
/// <param name="archived"></param>
/// <param name="emailOnly"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
@@ -61,26 +60,25 @@ public sealed class OtherUserResource
string query,
int limit = ServerLimits.DEFAULT_PAGINATION_REQUEST,
string? cursor = null,
bool archived = false,
bool emailOnly = false,
CancellationToken cancellationToken = default
)
{
//language=graphql
const string QUERY = """
query UserSearch($query: String!, $limit: Int!, $cursor: String, $archived: Boolean, $emailOnly: Boolean) {
data:userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived, emailOnly: $emailOnly) {
query Users($input: UsersRetrievalInput!) {
data:users(input: $input) {
cursor
items {
id
name
bio
company
avatar
verified
role
}
}
id
name
bio
company
avatar
verified
role
}
}
}
""";
@@ -89,11 +87,13 @@ public sealed class OtherUserResource
Query = QUERY,
Variables = new
{
query,
limit,
cursor,
archived,
emailOnly,
input = new
{
query,
limit,
emailOnly,
cursor,
},
},
};
@@ -76,6 +76,7 @@ public sealed class SubscriptionResource : IDisposable
/// <summary>Subscribe to updates to resource comments/threads. Optionally specify resource ID string to only receive updates regarding comments for those resources</summary>
/// <remarks><inheritdoc cref="CreateUserProjectsUpdatedSubscription"/></remarks>
/// <inheritdoc cref="ISpeckleGraphQLClient.SubscribeTo{T}"/>
[Obsolete("Comments are now issues, and we've not update SDKs with the new subs")]
public Subscription<ProjectCommentsUpdatedMessage> CreateProjectCommentsUpdatedSubscription(
ViewerUpdateTrackingTarget target
)
@@ -28,15 +28,11 @@ public sealed class WorkspaceResource
name
role
slug
logo
logoUrl
createdAt
updatedAt
readOnly
description
creationState
{
completed
}
permissions {
canCreateProject {
authorized
@@ -1,8 +1,12 @@
namespace Speckle.Sdk.Logging;
using Speckle.Connectors.Logging;
namespace Speckle.Sdk.Logging;
public sealed class NullActivityFactory : ISdkActivityFactory
{
public void Dispose() { }
public ISdkActivity? Start(string? name = default, string source = "") => null;
public ISdkActivity? Start(string? name, SdkActivityKind kind, string source) => null;
public ISdkActivity? StartRemote(string traceContext, SdkActivityKind kind, string? name, string source) => null;
}
@@ -7,32 +7,33 @@ namespace Speckle.Sdk.Models;
public enum DynamicBaseMemberType
{
/// <summary>
/// The typed members of the DynamicBase object
/// The typed members of the <see cref="DynamicBase"/> object
/// </summary>
Instance = 1,
/// <summary>
/// The dynamically added members of the DynamicBase object
/// The dynamically added members of the <see cref="DynamicBase"/> object
/// </summary>
Dynamic = 2,
/// <summary>
/// The typed members flagged with ObsoleteAttribute attribute.
/// The typed members flagged with <see cref="ObsoleteAttribute"/> attribute.
/// </summary>
Obsolete = 4,
/// <summary>
/// The typed methods flagged with TODO:
/// Old feature supported in v2 for grasshopper
/// </summary>
[Obsolete("Feature no longer supported")]
SchemaComputed = 16,
/// <summary>
/// All the typed members, including ones with ObsoleteAttribute attributes.
/// All the typed members, including ones with <see cref="ObsoleteAttribute"/> attributes.
/// </summary>
InstanceAll = Instance + Obsolete,
/// <summary>
/// All the members, including dynamic and instance members flagged with ObsoleteAttribute attributes
/// All the members, including dynamic and instance members flagged with <see cref="ObsoleteAttribute"/> attributes
/// </summary>
All = InstanceAll + Dynamic,
}
@@ -14,20 +14,20 @@ public partial interface IIngestionProgressManager : IProgress<CardProgress>;
/// An <see langword="IProgress{IngestionProgressEventArgs}"/> implementation for the entire client side Ingestion progress update reporting
/// Will throttles ingestion progress messages and reports their progress
/// </summary>
/// <remarks>
/// Normally we would pick quite a coarse updateInterval to try and spamming the server (1-5s)
/// </remarks>
[GenerateAutoInterface]
public sealed class IngestionProgressManager(
ILogger<IngestionProgressManager> logger,
IClient speckleClient,
ModelIngestion ingestion,
string projectId,
TimeSpan updateInterval,
CancellationToken cancellationToken
) : IIngestionProgressManager
{
/// <remarks>
/// Normally we would pick quite a coarse throttle window to try and avoid over pressure (1-5s)
/// </remarks>
private Task? _lastUpdate;
public Task? LastUpdate { get; private set; }
private long _lastUpdatedAt;
private readonly object _lock = new();
@@ -48,15 +48,15 @@ public sealed class IngestionProgressManager(
trimmedMessage = value.Status.TrimEnd('.');
_lastUpdate = speckleClient
LastUpdate = speckleClient
.Ingestion.UpdateProgress(
new ModelIngestionUpdateInput(ingestion.id, projectId, trimmedMessage, value.Progress),
new ModelIngestionUpdateInput(ingestion.id, ingestion.projectId, trimmedMessage, value.Progress),
cancellationToken
)
.ContinueWith(
HandleFaultedContinuation,
Continuation,
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default
);
}
@@ -67,7 +67,7 @@ public sealed class IngestionProgressManager(
/// <returns><see langword="true"/> if the update should be ignored, otherwise <see langword="false"/></returns>
private bool ShouldIgnoreProgressUpdate()
{
if (_lastUpdate is not null && !_lastUpdate.IsCompleted)
if (LastUpdate is not null && !LastUpdate.IsCompleted)
{
return true;
}
@@ -76,7 +76,7 @@ public sealed class IngestionProgressManager(
return msSinceLastUpdate < updateInterval;
}
private void HandleFaultedContinuation(Task updateTask)
private void Continuation(Task updateTask)
{
// The progress report failed... could be many reasons.
// For now, we're not letting this fail the Ingestion in any way
@@ -12,11 +12,10 @@ public sealed class IngestionProgressManagerFactory(ILogger<IngestionProgressMan
public IIngestionProgressManager CreateInstance(
IClient speckleClient,
ModelIngestion ingestion,
string projectId,
TimeSpan updateInterval,
CancellationToken cancellationToken
)
{
return new IngestionProgressManager(logger, speckleClient, ingestion, projectId, updateInterval, cancellationToken);
return new IngestionProgressManager(logger, speckleClient, ingestion, updateInterval, cancellationToken);
}
}
@@ -18,9 +18,9 @@ public sealed class RenderedStreamProgress(IProgress<CardProgress> progress) : I
);
}
private static readonly string[] s_suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
private static readonly string[] s_suffixes = ["B", "KB", "MB", "GB", "TB", "PB"];
private static (string suffix, double scaleFactor) GetFileSizeRendering(long value)
internal static (string suffix, double scaleFactor) GetFileSizeRendering(long value)
{
if (value <= 0)
{
+20 -12
View File
@@ -1,40 +1,48 @@
using System.IO.Compression;
using System.Threading.Channels;
using Microsoft.Extensions.Logging;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Pipelines.Send;
[GenerateAutoInterface]
public sealed class DiskStoreFactory(ILogger<DiskStore> logger) : IDiskStoreFactory
public sealed class DiskStoreFactory(ILogger<DiskStore> logger, ISdkActivityFactory activityFactory) : IDiskStoreFactory
{
public DiskStore CreateInstance(CancellationToken cancellationToken) => new(logger, cancellationToken);
public DiskStore CreateInstance(CancellationToken cancellationToken) =>
new(logger, activityFactory, cancellationToken);
}
public sealed class DiskStore
{
private readonly Channel<UploadItem> _channel;
private readonly RepackedChannel<UploadItem> _channel;
private readonly Task<DisposableFile> _writeToDiskTask;
private readonly ILogger<DiskStore> _logger;
private readonly ISdkActivityFactory _activityFactory;
private readonly CancellationToken _cancellationToken;
internal DiskStore(ILogger<DiskStore> logger, CancellationToken cancellationToken)
internal DiskStore(
ILogger<DiskStore> logger,
ISdkActivityFactory activityFactory,
CancellationToken cancellationToken
)
{
_logger = logger;
_activityFactory = activityFactory;
_cancellationToken = cancellationToken;
_channel = Channel.CreateBounded<UploadItem>(
new BoundedChannelOptions(1000) { FullMode = BoundedChannelFullMode.Wait, SingleReader = true }
);
_channel = new RepackedChannel<UploadItem>(1000, true, false);
_writeToDiskTask = Task.Run(WriteFile, cancellationToken);
}
public ValueTask PushAsync(UploadItem item) => _channel.Writer.WriteAsync(item, _cancellationToken);
public async Task PushAsync(UploadItem item) =>
await _channel.WriteAsync(item, _cancellationToken).ConfigureAwait(false);
public async Task<DisposableFile> CompleteAsync()
{
_channel.Writer.Complete();
using var a = _activityFactory.Start("Waiting for DiskStore to complete");
_channel.CompleteWriter();
return await _writeToDiskTask.ConfigureAwait(false);
}
@@ -55,9 +63,9 @@ public sealed class DiskStore
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 foreach (var item in _channel.ReadAllAsync(_cancellationToken).ConfigureAwait(false))
{
await writer.WriteLineAsync($"{item.Id}\t{item.Json}\t{item.SpeckleType}").ConfigureAwait(false);
await writer.WriteLineAsync($"{item.Id}\t{item.SpeckleType}\t{item.Json}").ConfigureAwait(false);
}
#if NET8_0_OR_GREATER
await writer.FlushAsync(_cancellationToken).ConfigureAwait(false);
@@ -70,11 +70,5 @@ public sealed class SendPipeline : IDisposable
await _uploader.Send(fileStreamUpload).ConfigureAwait(false);
}
public async Task<string> 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();
}
+12 -7
View File
@@ -1,16 +1,17 @@
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Logging;
using Speckle.InterfaceGenerator;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Pipelines.Progress;
namespace Speckle.Sdk.Pipelines.Send;
[GenerateAutoInterface]
public sealed class UploaderFactory(ISpeckleHttp httpClientFactory, ILogger<Uploader> logger) : IUploaderFactory
public sealed class UploaderFactory(ISpeckleHttp httpClientFactory, ISdkActivityFactory activityFactory)
: IUploaderFactory
{
public Uploader CreateInstance(
string projectId,
@@ -18,7 +19,7 @@ public sealed class UploaderFactory(ISpeckleHttp httpClientFactory, ILogger<Uplo
Account account,
IProgress<StreamProgressArgs> progress,
CancellationToken cancellationToken
) => new(projectId, ingestionId, logger, httpClientFactory, account, progress, cancellationToken);
) => new(projectId, ingestionId, activityFactory, httpClientFactory, account, progress, cancellationToken);
}
public sealed class Uploader : IDisposable
@@ -28,13 +29,13 @@ public sealed class Uploader : IDisposable
private readonly CancellationToken _cancellationToken;
private readonly HttpClient _speckleClient;
private readonly HttpClient _s3Client;
private readonly ILogger<Uploader> _logger;
private readonly ISdkActivityFactory _activity;
private readonly IProgress<StreamProgressArgs> _progress;
internal Uploader(
string projectId,
string ingestionId,
ILogger<Uploader> logger,
ISdkActivityFactory activity,
ISpeckleHttp httpClientFactory,
Account speckleAccount,
IProgress<StreamProgressArgs> progress,
@@ -43,7 +44,7 @@ public sealed class Uploader : IDisposable
{
_projectId = projectId;
_ingestionId = ingestionId;
_logger = logger;
_activity = activity;
_cancellationToken = cancellationToken;
_progress = progress;
_speckleClient = httpClientFactory.CreateHttpClient(authorizationToken: speckleAccount.token);
@@ -62,6 +63,8 @@ public sealed class Uploader : IDisposable
private async Task<PresignedUploadResponse> GetPresignedUrl()
{
using var a = _activity.Start("Get Presigned Url");
var signUri = new Uri($"projects/{_projectId}/modelingestion/{_ingestionId}/uploads/sign", UriKind.Relative);
using var signResponse = await _speckleClient.PostAsync(signUri, null, _cancellationToken).ConfigureAwait(false);
@@ -80,7 +83,7 @@ public sealed class Uploader : IDisposable
private async Task<string> UploadToS3(Stream fileStream, PresignedUploadResponse presignedUploadResponse)
{
_logger.LogInformation("Uploading file to pre-signed url");
using var a = _activity.Start("Uploading file to pre-signed url");
Stream progressStream = new ProgressStream(fileStream, _progress);
@@ -107,6 +110,8 @@ public sealed class Uploader : IDisposable
private async Task TriggerProcessing(TriggerUploadRequest request)
{
using var a = _activity.Start("Triggering Processing");
Uri processUri = new($"projects/{_projectId}/modelingestion/{_ingestionId}/uploads/process", UriKind.Relative);
string requestBody = JsonConvert.SerializeObject(request);
using var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
-1
View File
@@ -26,7 +26,6 @@
<PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="Speckle.DoubleNumerics" />
<PackageReference Include="Speckle.Newtonsoft.Json" />
<PackageReference Include="System.Threading.Channels" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" OverrideVersion="8.0.0" />
+18 -30
View File
@@ -90,16 +90,6 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"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",
@@ -121,14 +111,6 @@
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -280,8 +262,8 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
"resolved": "4.5.3",
"contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw=="
},
"System.Runtime.InteropServices.WindowsRuntime": {
"type": "Transitive",
@@ -293,14 +275,26 @@
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.sdk.dependencies": {
"type": "Project"
"type": "Project",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "[9.0.4, )"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
}
},
"net8.0": {
@@ -377,12 +371,6 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"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",
@@ -370,8 +370,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -492,12 +491,6 @@
"System.Text.Encodings.Web": "8.0.0"
}
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
},
"Verify.Quibble": {
"type": "CentralTransitive",
"requested": "[2.1.1, )",
@@ -348,8 +348,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -433,12 +432,6 @@
"System.Text.Encodings.Web": "8.0.0"
}
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
},
"Verify.Quibble": {
"type": "CentralTransitive",
"requested": "[2.1.1, )",
@@ -74,11 +74,6 @@
"resolved": "1.17.0",
"contentHash": "8x+HCVTl/HHTGpscH3vMBhV8sknN/muZFw9s3TsI8SA6+c43cOTCi2+jE4KsU8pNLbJ++iF2ZFcpcXHXtDglnw=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "1Am6l4Vpn3/K32daEqZI+FFr96OlZkgwK2LcT3pZ2zWubR5zTPW3/FkO1Rat9kb7oQOa4rxgl9LJHc5tspCWfg=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -342,8 +337,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -386,6 +380,12 @@
"System.Reactive": "5.0.0"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[9.0.4, )",
"resolved": "1.1.0",
"contentHash": "1Am6l4Vpn3/K32daEqZI+FFr96OlZkgwK2LcT3pZ2zWubR5zTPW3/FkO1Rat9kb7oQOa4rxgl9LJHc5tspCWfg=="
},
"Microsoft.Data.Sqlite": {
"type": "CentralTransitive",
"requested": "[7.0.5, )",
@@ -425,12 +425,6 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
}
}
}
@@ -52,7 +52,7 @@ public class ServerObjectManagerTests : MoqTest
http.Setup(x => x.CreateHttpClient(It.IsAny<HttpClientHandler>(), timeout, token)).Returns(httpClient);
var activityFactory = Create<ISdkActivityFactory>();
activityFactory.Setup(x => x.Start(null, "DownloadObjects")).Returns((ISdkActivity?)null);
activityFactory.Setup(x => x.Start(null, default, "DownloadObjects")).Returns((ISdkActivity?)null);
var serverObjectManager = new ServerObjectManager(
http.Object,
@@ -91,7 +91,7 @@ public class ServerObjectManagerTests : MoqTest
http.Setup(x => x.CreateHttpClient(It.IsAny<HttpClientHandler>(), timeout, token)).Returns(httpClient);
var activityFactory = Create<ISdkActivityFactory>();
activityFactory.Setup(x => x.Start(null, "DownloadSingleObject")).Returns((ISdkActivity?)null);
activityFactory.Setup(x => x.Start(null, default, "DownloadSingleObject")).Returns((ISdkActivity?)null);
var serverObjectManager = new ServerObjectManager(
http.Object,
@@ -132,7 +132,7 @@ public class ServerObjectManagerTests : MoqTest
http.Setup(x => x.CreateHttpClient(It.IsAny<HttpClientHandler>(), timeout, token)).Returns(httpClient);
var activityFactory = Create<ISdkActivityFactory>();
activityFactory.Setup(x => x.Start(null, "HasObjects")).Returns((ISdkActivity?)null);
activityFactory.Setup(x => x.Start(null, default, "HasObjects")).Returns((ISdkActivity?)null);
var serverObjectManager = new ServerObjectManager(
http.Object,
@@ -171,7 +171,7 @@ public class ServerObjectManagerTests : MoqTest
http.Setup(x => x.CreateHttpClient(It.IsAny<HttpClientHandler>(), timeout, token)).Returns(httpClient);
var activityFactory = Create<ISdkActivityFactory>();
activityFactory.Setup(x => x.Start(null, "UploadObjects")).Returns((ISdkActivity?)null);
activityFactory.Setup(x => x.Start(null, default, "UploadObjects")).Returns((ISdkActivity?)null);
var serverObjectManager = new ServerObjectManager(
http.Object,
@@ -393,8 +393,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -475,12 +474,6 @@
"System.Text.Encodings.Web": "8.0.0"
}
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
},
"Verify.Quibble": {
"type": "CentralTransitive",
"requested": "[2.1.1, )",
+1 -8
View File
@@ -311,8 +311,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -377,12 +376,6 @@
"dependencies": {
"System.Text.Encodings.Web": "8.0.0"
}
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
}
}
}
@@ -13,7 +13,7 @@ public class SubscriptionResourceTests : IAsyncLifetime
#if DEBUG
private const int WAIT_PERIOD = 3000; // WSL is slow AF, so for local runs, we're being extra generous
#else
private const int WAIT_PERIOD = 400; // For CI runs, a much smaller wait time is acceptable
private const int WAIT_PERIOD = 600; // For CI runs, a much smaller wait time is acceptable
#endif
private const int TIMEOUT = WAIT_PERIOD + WAIT_PERIOD + 600;
private IClient _testUser;
@@ -1,8 +0,0 @@
{
"Type": "AggregateException",
"InnerException": {
"Data": {},
"Message": "FORBIDDEN: Your auth token does not have the required scope: workspace:read.",
"Type": "SpeckleGraphQLForbiddenException"
}
}
@@ -1,8 +0,0 @@
{
"Type": "AggregateException",
"InnerException": {
"Data": {},
"Message": "FORBIDDEN: Your auth token does not have the required scope: workspace:read.",
"Type": "SpeckleGraphQLForbiddenException"
}
}
@@ -21,17 +21,19 @@ public class WorkspaceResourceTests
return testUser;
}
[Fact]
[Fact, Trait("Server", "Internal")]
public async Task TestGetWorkspace()
{
var ex = await Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.Get("non-existent-id"));
await Verify(ex);
Assert.Single(ex.InnerExceptions);
Assert.All(ex.InnerExceptions, item => Assert.IsType<SpeckleGraphQLForbiddenException>(item));
}
[Fact]
public async Task TestGetProjects()
{
var ex = await Assert.ThrowsAsync<AggregateException>(async () => _ = await Sut.GetProjects("non-existent-id"));
await Verify(ex);
Assert.Single(ex.InnerExceptions);
Assert.All(ex.InnerExceptions, item => Assert.IsType<SpeckleGraphQLForbiddenException>(item));
}
}
@@ -0,0 +1,76 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Common;
using Speckle.Sdk.Pipelines.Progress;
namespace Speckle.Sdk.Tests.Integration.Pipelines.Progress;
[Trait("Server", "Internal")]
public class IngestionProgressManagerTests : IAsyncLifetime
{
private IIngestionProgressManagerFactory _factory;
private IClient _client;
private Project _project;
private Model _model;
private ModelIngestion _ingestion;
public async Task InitializeAsync()
{
var serviceProvider = TestServiceSetup.GetServiceProvider();
_factory = serviceProvider.GetRequiredService<IIngestionProgressManagerFactory>();
_client = await Fixtures.SeedUserWithClient();
_project = await _client.Project.Create(new("test", null, default));
_model = await _client.Model.Create(new("test", null, _project.id));
_ingestion = await _client.Ingestion.Create(
new(_model.id, _project.id, "Testing ingestion", new("integrationTests", "0.0.0", null, null))
);
}
[Fact]
public async Task TestProgress_NoThrottle()
{
var sut = _factory.CreateInstance(_client, _ingestion, TimeSpan.Zero, CancellationToken.None);
const string FIRST_MESSAGE = "This is a test 123";
const string SECOND_MESSAGE = "This is another test 321";
// first message (should go through)
sut.Report(new CardProgress(FIRST_MESSAGE, 0.123123123d));
await sut.LastUpdate.NotNull();
var res = await _client.Ingestion.Get(_ingestion.id, _project.id, CancellationToken.None);
Assert.Equal(FIRST_MESSAGE, res.statusData.progressMessage);
// second message (should also go through)
sut.Report(new CardProgress(SECOND_MESSAGE, 0.321321321d));
await sut.LastUpdate.NotNull();
res = await _client.Ingestion.Get(_ingestion.id, _project.id, CancellationToken.None);
Assert.Equal(SECOND_MESSAGE, res.statusData.progressMessage);
}
[Fact]
public async Task TestProgress_WithThrottle()
{
var sut = _factory.CreateInstance(_client, _ingestion, TimeSpan.FromMilliseconds(500), CancellationToken.None);
const string EXPECTED_MESSAGE = "First message should go through 123";
await Task.Delay(TimeSpan.FromMilliseconds(600));
// first message (should go through)
sut.Report(new CardProgress(EXPECTED_MESSAGE, 0.123123123d));
// second message (should be dropped)
sut.Report(new CardProgress("Second message, should be dropped", 0.321321321d));
await sut.LastUpdate.NotNull();
var res = await _client.Ingestion.Get(_ingestion.id, _project.id, CancellationToken.None);
Assert.Equal(EXPECTED_MESSAGE, res.statusData.progressMessage);
}
public Task DisposeAsync()
{
_client.Dispose();
return Task.CompletedTask;
}
}
@@ -16,4 +16,7 @@
<ProjectReference Include="..\..\src\Speckle.Sdk\Speckle.Sdk.csproj" />
<ProjectReference Include="..\Speckle.Sdk.Testing\Speckle.Sdk.Testing.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Pipelines\Send\" />
</ItemGroup>
</Project>
@@ -370,8 +370,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -455,12 +454,6 @@
"System.Text.Encodings.Web": "8.0.0"
}
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
},
"Verify.Quibble": {
"type": "CentralTransitive",
"requested": "[2.1.1, )",
@@ -92,11 +92,6 @@
"resolved": "1.17.0",
"contentHash": "8x+HCVTl/HHTGpscH3vMBhV8sknN/muZFw9s3TsI8SA6+c43cOTCi2+jE4KsU8pNLbJ++iF2ZFcpcXHXtDglnw=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "1Am6l4Vpn3/K32daEqZI+FFr96OlZkgwK2LcT3pZ2zWubR5zTPW3/FkO1Rat9kb7oQOa4rxgl9LJHc5tspCWfg=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -360,8 +355,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -378,6 +372,12 @@
"System.Reactive": "5.0.0"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[9.0.4, )",
"resolved": "1.1.0",
"contentHash": "1Am6l4Vpn3/K32daEqZI+FFr96OlZkgwK2LcT3pZ2zWubR5zTPW3/FkO1Rat9kb7oQOa4rxgl9LJHc5tspCWfg=="
},
"Microsoft.Data.Sqlite": {
"type": "CentralTransitive",
"requested": "[7.0.5, )",
@@ -417,12 +417,6 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
}
}
}
@@ -0,0 +1,21 @@
using Moq;
using Speckle.Sdk.Pipelines.Progress;
namespace Speckle.Sdk.Tests.Unit.Pipelines.Progress;
public class AggregateProgressTests
{
[Fact]
public void Report_InvokesReportOnAllInnerProgresses()
{
var mock1 = new Mock<IProgress<int>>();
var mock2 = new Mock<IProgress<int>>();
const int TEST_VALUE = 42;
var target = new AggregateProgress<int>(mock1.Object, mock2.Object);
target.Report(TEST_VALUE);
mock1.Verify(x => x.Report(TEST_VALUE), Times.Once);
mock2.Verify(x => x.Report(TEST_VALUE), Times.Once);
}
}
@@ -0,0 +1,72 @@
using System.Diagnostics.CodeAnalysis;
using Moq;
using Speckle.Sdk.Pipelines.Progress;
namespace Speckle.Sdk.Tests.Unit.Pipelines.Progress;
[SuppressMessage(
"Performance",
"CA1835:Prefer the \'Memory\'-based overloads for \'ReadAsync\' and \'WriteAsync\'",
Justification = "Need to test it"
)]
public class ProgressStreamTests : IDisposable
{
private readonly Mock<Stream> _innerStreamMock;
private readonly Mock<IProgress<StreamProgressArgs>> _progressMock;
private readonly ProgressStream _sut;
public ProgressStreamTests()
{
// Setup the mocks
_innerStreamMock = new Mock<Stream>();
_innerStreamMock.Setup(s => s.Length).Returns(1024L);
_progressMock = new Mock<IProgress<StreamProgressArgs>>();
// Inject mocks into the System Under Test
_sut = new ProgressStream(_innerStreamMock.Object, _progressMock.Object);
}
[Fact]
public async Task ReadAsync_Should_CallInnerStreamAndReportProgress()
{
// Arrange
var buffer = new byte[10];
_innerStreamMock
.Setup(s => s.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None))
.Returns(Task.FromResult(5));
// Act
await _sut.ReadAsync(buffer, 0, buffer.Length);
// Assert - Inner Stream Read was called
_innerStreamMock.Verify(s => s.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None), Times.Once);
// Assert - Progress Report was called with the correct byte count
_progressMock.Verify(p => p.Report(It.IsAny<StreamProgressArgs>()), Times.Once);
}
[Fact]
public async Task WriteAsync_Should_CallInnerStreamAndReportProgress()
{
// Arrange
var buffer = new byte[10];
_innerStreamMock
.Setup(s => s.WriteAsync(buffer, 0, buffer.Length, CancellationToken.None))
.Returns(Task.FromResult(5));
// Act
await _sut.WriteAsync(buffer, 0, buffer.Length);
// Assert - Inner Stream Write was called
_innerStreamMock.Verify(s => s.WriteAsync(buffer, 0, buffer.Length, CancellationToken.None), Times.Once);
// Assert - Progress Report was called with the correct byte count
_progressMock.Verify(p => p.Report(It.IsAny<StreamProgressArgs>()), Times.Once);
}
public void Dispose()
{
_sut.Dispose();
}
}
@@ -0,0 +1,46 @@
using Speckle.Sdk.Pipelines.Progress;
namespace Speckle.Sdk.Tests.Unit.Pipelines.Progress;
public class RenderedStreamProgressTests
{
[Theory]
[InlineData(1, "B", 1.0)]
[InlineData(1024, "B", 1.0)]
[InlineData(1024 + 1, "KB", 1.0 / 1024)]
[InlineData(1024 * 1024, "KB", 1.0 / 1024)]
[InlineData(1024 * 1024 + 1, "MB", 1.0 / (1024 * 1024))]
[InlineData(1024 * 1024 * 1024, "MB", 1.0 / (1024 * 1024))]
[InlineData(1024 * 1024 * 1024 + 1, "GB", 1.0 / (1024 * 1024 * 1024))]
[InlineData(1024L * 1024L * 1024L * 1024L, "GB", 1.0 / (1024L * 1024L * 1024L))]
public void GetFileSizeRendering_WithPositiveValue_ReturnsCorrectSuffix(
long value,
string expectedSuffix,
double expectedScaleFactor
)
{
var result = RenderedStreamProgress.GetFileSizeRendering(value);
Assert.Equal(expectedSuffix, result.suffix);
Assert.Equal(expectedScaleFactor, result.scaleFactor);
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(-1000)]
public void GetFileSizeRendering_WithNonPositiveValue_ReturnsBytesSuffix(long value)
{
var result = RenderedStreamProgress.GetFileSizeRendering(value);
Assert.Equal("B", result.suffix);
Assert.Equal(1d, result.scaleFactor);
}
[Theory]
[InlineData(long.MaxValue)]
public void GetFileSizeRendering_WithVeryLargeValue_ThrowsArgumentOutOfRangeException(long value)
{
Assert.Throws<ArgumentOutOfRangeException>(() => RenderedStreamProgress.GetFileSizeRendering(value));
}
}
@@ -363,8 +363,7 @@
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )"
}
},
"speckle.sdk.dependencies": {
@@ -442,12 +441,6 @@
"System.Text.Encodings.Web": "8.0.0"
}
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
},
"Verify.Quibble": {
"type": "CentralTransitive",
"requested": "[2.1.1, )",