From 41337b43a318e531f4be53602dacf19b91d06d19 Mon Sep 17 00:00:00 2001 From: ducksoop Date: Mon, 12 Feb 2024 11:10:42 -0600 Subject: [PATCH 01/10] feat: wip insert, update and delete commands --- QuickbaseNet.Examples/Program.cs | 51 ++-- .../QuickbaseNet.UnitTests.csproj | 1 + .../Tests/QuickbaseClientTests.cs | 234 ++++++++++++++++++ .../Tests/QuickbaseResultTests.cs | 37 +++ QuickbaseNet.UnitTests/Utility/Builder.cs | 130 ++++++++++ QuickbaseNet/Services/QuickbaseClient.cs | 24 +- 6 files changed, 442 insertions(+), 35 deletions(-) create mode 100644 QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs create mode 100644 QuickbaseNet.UnitTests/Tests/QuickbaseResultTests.cs create mode 100644 QuickbaseNet.UnitTests/Utility/Builder.cs diff --git a/QuickbaseNet.Examples/Program.cs b/QuickbaseNet.Examples/Program.cs index cec746d..9870e15 100644 --- a/QuickbaseNet.Examples/Program.cs +++ b/QuickbaseNet.Examples/Program.cs @@ -9,44 +9,47 @@ internal class Program { static async Task Main(string[] args) { - var quickBaseClient = new QuickbaseClient("diamond", Environment.GetEnvironmentVariable("QB_USERTOKEN")); - var query = new QuickbaseQueryBuilder() - .From("bmycek2xq") - .Select(3, 7, 14, 75, 150, 157, 354, 355, 367, 538, 539, 540, 541, 542, 543) - .Where("{'7'.'EX'.'10136'}") - .Build(); + var quickBaseClient = new QuickbaseClient("builderprogram-pcohen", "b4mtg6_p7vz_0_dcmcjr3bdtp4csdtuu6qx3tyzuc"); - string jsonRequest = JsonConvert.SerializeObject(query); + var insertCommand = new QuickbaseCommandBuilder() + .ForTable("bsfhutyzn") + .ReturnFields(1, 2, 3) + .AddNewRecord(record => record + .AddField("6", "Cupcakes") + .AddField("7", "$14")) + .BuildInsertUpdateCommand(); - var result = await quickBaseClient.QueryRecords(query); + var result = await quickBaseClient.InsertRecords(insertCommand); + + // var quickBaseClient = new QuickbaseClient("diamond", Environment.GetEnvironmentVariable("QB_USERTOKEN")); + // var query = new QuickbaseQueryBuilder() + // .From("bsfhutyzn") + // .Select(6, 7, 8, 9, 10, 11, 12, 13) + // .Build(); + // + // string jsonRequest = JsonConvert.SerializeObject(query); + // + // var result = await quickBaseClient.QueryRecords(query); if (result.IsSuccess) { Console.WriteLine("Success!"); Console.WriteLine(JsonConvert.SerializeObject(result.Value, Formatting.Indented)); } - else + else if (result.IsFailure) { Console.WriteLine("Error!"); Console.WriteLine(JsonConvert.SerializeObject(result.QuickbaseError, Formatting.Indented)); } - // var recordBuilder = new QuickbaseCommandBuilder() - // .ForTable("your_table_id") - // .ReturnFields(1, 2, 3) - // .AddRecord(record => record - // .AddField("fieldId1", "Value1") - // .AddField("fieldId2", "Value2")) - // .AddRecord(record => record - // .AddField("fieldId1", "Another Value1") - // .AddField("fieldId2", "Another Value2")); - // - // InsertOrUpdateRecordRequest request = recordBuilder.Build(); - var commandBuilder = new QuickbaseCommandBuilder() - .ForTable("bck7gp3q2") - .WithDeletionCriteria("{6.EX.'hello'}"); + + // InsertOrUpdateRecordRequest request = recordBuilder.Build(); - DeleteRecordRequest deleteRequest = commandBuilder.BuildDeleteCommand(); + // var commandBuilder = new QuickbaseCommandBuilder() + // .ForTable("bck7gp3q2") + // .WithDeletionCriteria("{6.EX.'hello'}"); + // + // DeleteRecordRequest deleteRequest = commandBuilder.BuildDeleteCommand(); } } diff --git a/QuickbaseNet.UnitTests/QuickbaseNet.UnitTests.csproj b/QuickbaseNet.UnitTests/QuickbaseNet.UnitTests.csproj index 23e0299..1618e7c 100644 --- a/QuickbaseNet.UnitTests/QuickbaseNet.UnitTests.csproj +++ b/QuickbaseNet.UnitTests/QuickbaseNet.UnitTests.csproj @@ -10,6 +10,7 @@ + diff --git a/QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs b/QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs new file mode 100644 index 0000000..5484e1a --- /dev/null +++ b/QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs @@ -0,0 +1,234 @@ +using System.Net; +using System.Text.Json; +using Bogus; +using QuickbaseNet.Requests; +using QuickbaseNet.Responses; +using QuickbaseNet.Services; +using QuickbaseNet.UnitTests.Mocks; +using QuickbaseNet.UnitTests.Utility; + +namespace QuickbaseNet.UnitTests.Tests; + +public class QuickbaseClientTests +{ + private readonly MockHttpMessageHandler _mockHandler; + private readonly QuickbaseClient _client; + private const string TestRealm = "testRealm"; + private const string TestToken = "testToken"; + private const string BaseUrl = "http://localhost/"; + + public QuickbaseClientTests() + { + _mockHandler = SetupMockHandlerWithSuccessResponse(); + _client = CreateConfiguredQuickbaseClient(); + } + + [Fact] + public async Task Constructor_ThrowsArgumentNullException_WhenRealmIsNull() + { + // Arrange + var realm = string.Empty; + + // Act + var exception = await Assert.ThrowsAsync(() => Task.FromResult(new QuickbaseClient(realm, TestToken))); + + // Assert + Assert.Equal("realm", exception.ParamName); + } + + [Fact] + public async Task Constructor_ThrowsArgumentNullException_WhenTokenIsNull() + { + // Arrange + var token = string.Empty; + + // Act + var exception = await Assert.ThrowsAsync(() => Task.FromResult(new QuickbaseClient(TestRealm, token))); + + // Assert + Assert.Equal("userToken", exception.ParamName); + } + + [Fact] + public async Task QueryRecords_ReturnsSuccessResponse_WhenCalled() + { + // Arrange + var request = new Builder().Build(); + + // Act + var response = await _client.QueryRecords(request); + + // Assert + Assert.True(response.IsSuccess); + Assert.NotNull(response.Value); + Assert.NotEmpty(response.Value.Data); + Assert.NotEmpty(response.Value.Fields); + Assert.NotNull(response.Value.Metadata); + } + + [Fact] + public async Task QueryRecords_ReturnsErrorResponse_When4xxOccurs() + { + // Arrange + SetupMockHandlerWithErrorResponse(); + var request = new QuickbaseQueryRequest + { + From = "tableId", + Where = "{1.CT.'query'}", + Select = [1, 2, 3] + }; + + // Act + var actualResponse = await _client.QueryRecords(request); + + // Assert + Assert.False(actualResponse.IsSuccess); + Assert.True(actualResponse.IsFailure); + Assert.NotNull(actualResponse.QuickbaseError); + } + + [Fact] + public async Task QueryRecords_ReturnsErrorResponse_When5xxOccurs() + { + // Arrange + _mockHandler.ResponseStatusCode = HttpStatusCode.InternalServerError; + var request = new QuickbaseQueryRequest + { + From = "tableId", + Where = "{1.CT.'query'}", + Select = [1, 2, 3] + }; + + // Act + var actualResponse = await _client.QueryRecords(request); + + // Assert + Assert.False(actualResponse.IsSuccess); + Assert.NotNull(actualResponse.QuickbaseError); + } + + [Fact] + public async Task InsertRecords_ReturnsSuccessResponse_WhenCalled() + { + // Arrange + var request = new Builder().Build(); + + // Act + var response = await _client.InsertRecords(request); + + // Assert + Assert.True(response.IsSuccess); + Assert.NotNull(response.Response); + Assert.Null(response.Error); + } + + [Fact] + public async Task InsertRecords_ReturnsErrorResponse_WhenFails() + { + // Arrange + SetupMockHandlerWithErrorResponse(); + var request = new Builder().Build(); + + // Act + var actualResponse = await _client.InsertRecords(request); + + // Assert + Assert.False(actualResponse.IsSuccess); + Assert.Null(actualResponse.Response); + Assert.NotNull(actualResponse.Error); + } + + [Fact] + public async Task UpdateRecords_ReturnsSuccessResponse_WhenCalled() + { + // Arrange + var request = new Builder().Build(); + + // Act + var response = await _client.UpdateRecords(request); + + // Assert + Assert.True(response.IsSuccess); + Assert.NotNull(response.Response); + Assert.Null(response.Error); + } + + [Fact] + public async Task UpdateRecords_ReturnsErrorResponse_WhenFails() + { + // Arrange + SetupMockHandlerWithErrorResponse(); + var request = new Builder().Build(); + + // Act + var actualResponse = await _client.UpdateRecords(request); + + // Assert + Assert.False(actualResponse.IsSuccess); + Assert.Null(actualResponse.Response); + Assert.NotNull(actualResponse.Error); + } + + #region Helpers + private void SetupMockHandlerWithErrorResponse() + { + var faker = new Faker(); + var errorResponse = new QuickbaseErrorResponse + { + Message = faker.Random.Words(), + Description = faker.Lorem.Paragraph() + }; + + _mockHandler.ResponseContent = JsonSerializer.Serialize(errorResponse); + _mockHandler.ResponseStatusCode = HttpStatusCode.BadRequest; + } + + private MockHttpMessageHandler SetupMockHandlerWithSuccessResponse() + { + var mockJsonResponse = GetMockJsonResponse(); + return new MockHttpMessageHandler + { + ResponseContent = mockJsonResponse, + ResponseStatusCode = HttpStatusCode.OK + }; + } + + private QuickbaseClient CreateConfiguredQuickbaseClient() + { + var httpClient = new HttpClient(_mockHandler) + { + BaseAddress = new Uri(BaseUrl) + }; + return new QuickbaseClient(TestRealm, TestToken) + { + Client = httpClient + }; + } + + private static string GetMockJsonResponse() + { + // JSON response string... + return @"{ + ""data"": [ + { + ""6"": { ""value"": ""Andre Harris"" }, + ""7"": { ""value"": 10 }, + ""8"": { ""value"": ""2019-12-18T08:00:00Z"" } + } + ], + ""fields"": [ + { ""id"": 6, ""label"": ""Full Name"", ""type"": ""text"" }, + { ""id"": 7, ""label"": ""Amount"", ""type"": ""numeric"" }, + { ""id"": 8, ""label"": ""Date time"", ""type"": ""date time"" } + ], + ""metadata"": { + ""totalRecords"": 10, + ""numRecords"": 1, + ""numFields"": 3, + ""skip"": 0 + } + }"; + } + + #endregion +} \ No newline at end of file diff --git a/QuickbaseNet.UnitTests/Tests/QuickbaseResultTests.cs b/QuickbaseNet.UnitTests/Tests/QuickbaseResultTests.cs new file mode 100644 index 0000000..8db8ca8 --- /dev/null +++ b/QuickbaseNet.UnitTests/Tests/QuickbaseResultTests.cs @@ -0,0 +1,37 @@ +using QuickbaseNet.Errors; + +namespace QuickbaseNet.UnitTests.Tests; + +public class QuickbaseResultTests +{ + [Fact] + public void Constructor_ThrowsArgumentException_WhenInvalidErrorForSuccess() + { + // Arrange + var isSuccess = true; + var invalidError = QuickbaseError.ClientError("InvalidError", "Invalid error occurred", "Description"); + + // Act & Assert + Assert.Throws(() => new TestableQuickbaseResult(isSuccess, invalidError)); + } + + [Fact] + public void Constructor_ThrowsArgumentException_WhenInvalidErrorForFailure() + { + // Arrange + var isSuccess = false; + var invalidError = QuickbaseError.None; + + // Act & Assert + Assert.Throws(() => new TestableQuickbaseResult(isSuccess, invalidError)); + } +} + +internal class TestableQuickbaseResult : QuickbaseResult +{ + public TestableQuickbaseResult(bool isSuccess, QuickbaseError quickbaseError) + : base(isSuccess, quickbaseError) + { + // This constructor allows access to the protected internal constructor of QuickbaseResult + } +} diff --git a/QuickbaseNet.UnitTests/Utility/Builder.cs b/QuickbaseNet.UnitTests/Utility/Builder.cs new file mode 100644 index 0000000..ec78d8b --- /dev/null +++ b/QuickbaseNet.UnitTests/Utility/Builder.cs @@ -0,0 +1,130 @@ +namespace QuickbaseNet.UnitTests.Utility; + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; + +using AutoBogus; + +using Bogus; + +[ExcludeFromCodeCoverage] +public class Builder +{ + public T Build() where T : class + { + var binder = new AutoBinder(); + + Faker model = new AutoFaker(binder) + .RuleForType(typeof(uint), rule => rule.Random.UInt(1, int.MaxValue)) + .RuleForType(typeof(uint?), rule => (uint?)rule.Random.UInt(1, int.MaxValue)) + .RuleForType(typeof(int), rule => rule.Random.Int()) + .RuleForType(typeof(int?), rule => (int?)rule.Random.Int()) + .RuleForType(typeof(DateTime), rule => + { + DateTime date = rule.Date.Recent(); + return new DateTime( + date.Year, + date.Month, + date.Day + ); + }) + .RuleForType(typeof(DateTime?), rule => + { + DateTime date = rule.Date.Recent(); + return (DateTime?)new DateTime( + date.Year, + date.Month, + date.Day + ); + }) + .RuleForType(typeof(DateTimeOffset?), rule => + { + DateTimeOffset date = rule.Date.Recent(); + return (DateTimeOffset?)new DateTime( + date.Year, + date.Month, + date.Day + ); + }) + .RuleForType(typeof(byte), rule => rule.Random.Byte()) + .RuleForType(typeof(byte?), rule => (byte?)rule.Random.Byte()) + .RuleForType(typeof(sbyte), rule => rule.Random.SByte()) + .RuleForType(typeof(string), rule => rule.Random.AlphaNumeric(10)) + .RuleForType(typeof(decimal), rule => rule.Finance.Amount(min: 0.01M, max: 99999.99M, decimals: 2)) + .RuleForType(typeof(decimal?), rule => (decimal?)rule.Finance.Amount(min: 0.01M, max: 99999.99M, decimals: 2)) + .RuleForType(typeof(ushort), rule => rule.Random.UShort()) + .RuleForType(typeof(short), rule => rule.Random.Short()) + .RuleForType(typeof(long), rule => rule.Random.Long()) + .RuleForType(typeof(ulong), rule => rule.Random.ULong()) + .RuleForType(typeof(bool), rule => rule.Random.Bool()) + .RuleForType(typeof(bool?), rule => (bool?)rule.Random.Bool()); + + model = AddEnumRules(model, binder); + + //int seed = DateTime.UtcNow.Millisecond; + //return model.UseSeed(seed).Generate(); + return model.Generate(); + } + + /// + /// Adds the ability to generate random valid enum values + /// + /// + /// Bogus currently doesn't appear to have a way to create rules for types that aren't well known at compile time. To generate random values for enum properties + /// we have to find properties on that are enum and get a list of valid values for that enum and pick one of those randomly. + /// + /// Bogus appears to do similar things when creating rules for well known types too, so this is at least similar. + /// + /// This also excludes enum members whose name is "None" when it is the first member of the enum as that is usually not a valid value. + /// This follows the convention for other nubmer types not generating zeroes + /// + private Faker AddEnumRules(Faker faker, IBinder binder) where T : class + { + // find enum properties using the binder + IEnumerable enumProperties = binder.GetMembers(typeof(T)) + .Select(item => item.Value) + .OfType() + .Where(item => item.PropertyType.IsEnum); + + Faker result = faker; + if (enumProperties.Any()) + { + result = result.FinishWith((fk, target) => + { + foreach (PropertyInfo enumProperty in enumProperties) + { + // None = 0 is typically not a valid value, so for the sake of generating sane values it will be skipped + var minIndex = 0; + string[] enumNames = Enum.GetNames(enumProperty.PropertyType); + if (enumNames[0].ToLower() == "none") + { + minIndex = 1; + } + + Array enumValues = Enum.GetValues(enumProperty.PropertyType); + var randomIndex = fk.Random.Int(minIndex, enumValues.Length - 1); + object randomEnumValue = enumValues.GetValue(randomIndex); + + enumProperty.SetValue(target, randomEnumValue); + } + }); + } + + return result; + } + + public IEnumerable Build(int howMany) where T : class + { + var models = new List(); + for (int i = 0; i < howMany; i++) + { + T model = Build(); + models.Add(model); + } + + return models; + } +} \ No newline at end of file diff --git a/QuickbaseNet/Services/QuickbaseClient.cs b/QuickbaseNet/Services/QuickbaseClient.cs index ea7b97e..76d0129 100644 --- a/QuickbaseNet/Services/QuickbaseClient.cs +++ b/QuickbaseNet/Services/QuickbaseClient.cs @@ -18,6 +18,15 @@ public class QuickbaseClient public QuickbaseClient(string realm, string userToken) { + if (string.IsNullOrEmpty(realm)) + { + throw new ArgumentNullException(nameof(realm), "Realm cannot be null or empty"); + } + + if (string.IsNullOrEmpty(userToken)) + { + throw new ArgumentNullException(nameof(userToken), "User token cannot be null or empty"); + } Client.BaseAddress = new Uri(BaseUrl); Client.DefaultRequestHeaders.Add("QB-Realm-Hostname", $"{realm}.quickbase.com"); Client.DefaultRequestHeaders.Add("Authorization", $"QB-USER-TOKEN {userToken}"); @@ -51,18 +60,11 @@ public async Task> QueryRecords(Quickbas return QuickbaseResult.Failure(QuickbaseError.ClientError("QuickbaseNet.ClientError", errorResponse.Message, errorResponse.Description)); } - // Check if its 5xx error - if (response.StatusCode >= System.Net.HttpStatusCode.InternalServerError) - { - return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse.Message, errorResponse.Description)); - } - - // Return generic failure - return QuickbaseResult.Failure(QuickbaseError.Failure("QuickbaseNet.Failure", errorResponse.Message, errorResponse.Description)); + // Its a 5xx error + return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse.Message, errorResponse.Description)); } - internal async Task<(QuickbaseRecordUpdateResponse Response, QuickbaseErrorResponse Error, bool IsSuccess)> - InsertRecords(InsertOrUpdateRecordRequest quickBaseRequest) + public async Task<(QuickbaseRecordUpdateResponse Response, QuickbaseErrorResponse Error, bool IsSuccess)> InsertRecords(InsertOrUpdateRecordRequest quickBaseRequest) { HttpContent content = new StringContent(JsonConvert.SerializeObject(quickBaseRequest), Encoding.UTF8, "application/json"); @@ -78,7 +80,7 @@ public async Task> QueryRecords(Quickbas return (null, JsonConvert.DeserializeObject(errorResponse), false); } - internal async Task<(QuickbaseRecordUpdateResponse Response, QuickbaseErrorResponse Error, bool IsSuccess)> + public async Task<(QuickbaseRecordUpdateResponse Response, QuickbaseErrorResponse Error, bool IsSuccess)> UpdateRecords(InsertOrUpdateRecordRequest quickBaseRequest) { HttpContent content = new StringContent(JsonConvert.SerializeObject(quickBaseRequest), Encoding.UTF8, "application/json"); From e984a534b48facb8452954c6837387cb2d7b39c4 Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 09:23:57 -0600 Subject: [PATCH 02/10] feat(insert commands): added insert command from QB api Provided a Fluent command to use the Insert Record API from Quickbase fix #8 --- QuickbaseNet.Examples/Program.cs | 5 ++-- .../Helpers/QuickbaseCommandBuilder.cs | 4 ++-- QuickbaseNet/Services/QuickbaseClient.cs | 23 ++++++++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/QuickbaseNet.Examples/Program.cs b/QuickbaseNet.Examples/Program.cs index 9870e15..5c61bc5 100644 --- a/QuickbaseNet.Examples/Program.cs +++ b/QuickbaseNet.Examples/Program.cs @@ -15,8 +15,9 @@ static async Task Main(string[] args) .ForTable("bsfhutyzn") .ReturnFields(1, 2, 3) .AddNewRecord(record => record - .AddField("6", "Cupcakes") - .AddField("7", "$14")) + .AddField("6", "Hired consulting firm to do market study.") + .AddField("7", 5000) + .AddField("9", "2023-01-01")) .BuildInsertUpdateCommand(); var result = await quickBaseClient.InsertRecords(insertCommand); diff --git a/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs b/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs index 9698627..1312515 100644 --- a/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs +++ b/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs @@ -73,9 +73,9 @@ public class RecordBuilder { private readonly Dictionary _fields = new Dictionary(); - public RecordBuilder AddField(string fieldId, string value) + public RecordBuilder AddField(string fieldId, T value) { - _fields[fieldId] = new FieldValue { Value = value }; + _fields[fieldId] = new FieldValue { Value = value }; return this; } diff --git a/QuickbaseNet/Services/QuickbaseClient.cs b/QuickbaseNet/Services/QuickbaseClient.cs index 76d0129..957243b 100644 --- a/QuickbaseNet/Services/QuickbaseClient.cs +++ b/QuickbaseNet/Services/QuickbaseClient.cs @@ -64,7 +64,7 @@ public async Task> QueryRecords(Quickbas return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse.Message, errorResponse.Description)); } - public async Task<(QuickbaseRecordUpdateResponse Response, QuickbaseErrorResponse Error, bool IsSuccess)> InsertRecords(InsertOrUpdateRecordRequest quickBaseRequest) + public async Task> InsertRecords(InsertOrUpdateRecordRequest quickBaseRequest) { HttpContent content = new StringContent(JsonConvert.SerializeObject(quickBaseRequest), Encoding.UTF8, "application/json"); @@ -73,11 +73,28 @@ public async Task> QueryRecords(Quickbas if (response.IsSuccessStatusCode) { var jsonResponse = await response.Content.ReadAsStringAsync(); - return (JsonConvert.DeserializeObject(jsonResponse), null, true); + + // Check if data is null or empty + if (string.IsNullOrEmpty(jsonResponse)) + { + return QuickbaseResult.Failure(QuickbaseError.NotFound("QuickbaseNet.Failure", + "No records found", $"The query did not find any records matching that criteria")); + } + + return QuickbaseResult.Success(JsonConvert.DeserializeObject(jsonResponse)); } var errorResponse = await response.Content.ReadAsStringAsync(); - return (null, JsonConvert.DeserializeObject(errorResponse), false); + + // Check if its 4xx error + if (response.StatusCode >= System.Net.HttpStatusCode.BadRequest && + response.StatusCode < System.Net.HttpStatusCode.InternalServerError) + { + return QuickbaseResult.Failure(QuickbaseError.ClientError("QuickbaseNet.ClientError", errorResponse, "Client error")); + } + + // Its a 5xx error + return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse, "Server error")); } public async Task<(QuickbaseRecordUpdateResponse Response, QuickbaseErrorResponse Error, bool IsSuccess)> From 5a0a9e16b5e475f32a9efafcc2fca1f4377b1d60 Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 09:24:50 -0600 Subject: [PATCH 03/10] chore(non code files): commitizen setup Setup commitizen for use in this repository. --- package-lock.json | 1975 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 13 + 2 files changed, 1988 insertions(+) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c598cfa --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1975 @@ +{ + "name": "QuickbaseNet", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "cz-conventional-changelog": "^3.3.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "optional": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "optional": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-18.6.1.tgz", + "integrity": "sha512-05uiToBVfPhepcQWE1ZQBR/Io3+tb3gEotZjnI4tTzzPk16NffN6YABgwFQCLmzZefbDcmwWqJWc2XT47q7Znw==", + "dev": true, + "optional": true, + "dependencies": { + "@commitlint/types": "^18.6.1", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-18.6.1.tgz", + "integrity": "sha512-7s37a+iWyJiGUeMFF6qBlyZciUkF8odSAnHijbD36YDctLhGKoYltdvuJ/AFfRm6cBLRtRk9cCVPdsEFtt/2rg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-18.6.1.tgz", + "integrity": "sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==", + "dev": true, + "optional": true, + "dependencies": { + "@commitlint/config-validator": "^18.6.1", + "@commitlint/execute-rule": "^18.6.1", + "@commitlint/resolve-extends": "^18.6.1", + "@commitlint/types": "^18.6.1", + "chalk": "^4.1.0", + "cosmiconfig": "^8.3.6", + "cosmiconfig-typescript-loader": "^5.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@commitlint/load/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/load/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@commitlint/load/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "node_modules/@commitlint/load/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/load/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-18.6.1.tgz", + "integrity": "sha512-ifRAQtHwK+Gj3Bxj/5chhc4L2LIc3s30lpsyW67yyjsETR6ctHAHRu1FSpt0KqahK5xESqoJ92v6XxoDRtjwEQ==", + "dev": true, + "optional": true, + "dependencies": { + "@commitlint/config-validator": "^18.6.1", + "@commitlint/types": "^18.6.1", + "import-fresh": "^3.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types": { + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-18.6.1.tgz", + "integrity": "sha512-gwRLBLra/Dozj2OywopeuHj2ac26gjGkz2cZ+86cTJOdtWfiRRr4+e77ZDAGc6MDWxaWheI+mAV5TLWWRwqrFg==", + "dev": true, + "optional": true, + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@commitlint/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@commitlint/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "node_modules/@commitlint/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "optional": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/commitizen": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.3.0.tgz", + "integrity": "sha512-H0iNtClNEhT0fotHvGV3E9tDejDeS04sN1veIebsKYGMuGscFaswRoYJKmT3eW85eIJAs0F28bG2+a/9wCOfPw==", + "dev": true, + "dependencies": { + "cachedir": "2.3.0", + "cz-conventional-changelog": "3.3.0", + "dedent": "0.7.0", + "detect-indent": "6.1.0", + "find-node-modules": "^2.1.2", + "find-root": "1.1.0", + "fs-extra": "9.1.0", + "glob": "7.2.3", + "inquirer": "8.2.5", + "is-utf8": "^0.2.1", + "lodash": "4.17.21", + "minimist": "1.2.7", + "strip-bom": "4.0.0", + "strip-json-comments": "3.1.1" + }, + "bin": { + "commitizen": "bin/commitizen", + "cz": "bin/git-cz", + "git-cz": "bin/git-cz" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/conventional-commit-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz", + "integrity": "sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "optional": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz", + "integrity": "sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==", + "dev": true, + "optional": true, + "dependencies": { + "jiti": "^1.19.1" + }, + "engines": { + "node": ">=v16" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=8.2", + "typescript": ">=4" + } + }, + "node_modules/cz-conventional-changelog": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz", + "integrity": "sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "commitizen": "^4.0.3", + "conventional-commit-types": "^3.0.0", + "lodash.map": "^4.5.1", + "longest": "^2.0.1", + "word-wrap": "^1.0.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@commitlint/load": ">6.1.1" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "optional": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "optional": true + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-node-modules": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.1.3.tgz", + "integrity": "sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==", + "dev": true, + "dependencies": { + "findup-sync": "^4.0.0", + "merge": "^2.1.1" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, + "node_modules/findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", + "dev": true, + "optional": true, + "dependencies": { + "ini": "^1.3.4" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "optional": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inquirer": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "optional": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "optional": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "optional": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "optional": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "optional": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "optional": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "optional": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "optional": true + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "optional": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "optional": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "optional": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/longest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-2.0.1.tgz", + "integrity": "sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz", + "integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "optional": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "optional": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", + "dev": true, + "optional": true, + "dependencies": { + "global-dirs": "^0.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "optional": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e4cc536 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "devDependencies": { + "cz-conventional-changelog": "^3.3.0" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + }, + "scripts": { + "commit": "cz" + } +} From ff3439496a7fe2a935dde0e2c1fc6d593b7d6afa Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 09:46:18 -0600 Subject: [PATCH 04/10] feat(quickbaseclient.cs): added the update api command from qb api Added the Udpate API functionality via our fluent functions fix #9 --- QuickbaseNet/Services/QuickbaseClient.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/QuickbaseNet/Services/QuickbaseClient.cs b/QuickbaseNet/Services/QuickbaseClient.cs index 957243b..036c12b 100644 --- a/QuickbaseNet/Services/QuickbaseClient.cs +++ b/QuickbaseNet/Services/QuickbaseClient.cs @@ -97,8 +97,7 @@ public async Task> InsertRecords( return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse, "Server error")); } - public async Task<(QuickbaseRecordUpdateResponse Response, QuickbaseErrorResponse Error, bool IsSuccess)> - UpdateRecords(InsertOrUpdateRecordRequest quickBaseRequest) + public async Task> UpdateRecords(InsertOrUpdateRecordRequest quickBaseRequest) { HttpContent content = new StringContent(JsonConvert.SerializeObject(quickBaseRequest), Encoding.UTF8, "application/json"); @@ -107,11 +106,28 @@ public async Task> InsertRecords( if (response.IsSuccessStatusCode) { var jsonResponse = await response.Content.ReadAsStringAsync(); - return (JsonConvert.DeserializeObject(jsonResponse), null, true); + + // Check if data is null or empty + if (string.IsNullOrEmpty(jsonResponse)) + { + return QuickbaseResult.Failure(QuickbaseError.NotFound("QuickbaseNet.Failure", + "No records found", $"The query did not find any records matching that criteria")); + } + + return QuickbaseResult.Success(JsonConvert.DeserializeObject(jsonResponse)); } var errorResponse = await response.Content.ReadAsStringAsync(); - return (null, JsonConvert.DeserializeObject(errorResponse), false); + + // Check if its 4xx error + if (response.StatusCode >= System.Net.HttpStatusCode.BadRequest && + response.StatusCode < System.Net.HttpStatusCode.InternalServerError) + { + return QuickbaseResult.Failure(QuickbaseError.ClientError("QuickbaseNet.ClientError", errorResponse, "Client error")); + } + + // Its a 5xx error + return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse, "Server error")); } } } From 3fee68c786a5ecb010228a4cdfd046116ffde765 Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 09:56:18 -0600 Subject: [PATCH 05/10] test: removing tests for insert/update Removed the invalid and outdated tests for the Insert/Update commands. Need to rewrite these. --- .../QuickbaseClientTests.cs | 124 ------------------ .../Tests/QuickbaseClientTests.cs | 62 --------- 2 files changed, 186 deletions(-) delete mode 100644 QuickbaseNet.UnitTests/QuickbaseClientTests/QuickbaseClientTests.cs diff --git a/QuickbaseNet.UnitTests/QuickbaseClientTests/QuickbaseClientTests.cs b/QuickbaseNet.UnitTests/QuickbaseClientTests/QuickbaseClientTests.cs deleted file mode 100644 index 17ad5c8..0000000 --- a/QuickbaseNet.UnitTests/QuickbaseClientTests/QuickbaseClientTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Net; -using System.Text.Json; -using Bogus; -using QuickbaseNet.Requests; -using QuickbaseNet.Responses; -using QuickbaseNet.Services; -using QuickbaseNet.UnitTests.Mocks; - -namespace QuickbaseNet.UnitTests.QuickbaseClientTests; - -public class QuickbaseClientTests -{ - private readonly MockHttpMessageHandler _mockHandler; - private readonly QuickbaseClient _client; - private const string TestRealm = "testRealm"; - private const string TestToken = "testToken"; - private const string BaseUrl = "http://localhost/"; - - public QuickbaseClientTests() - { - _mockHandler = SetupMockHandlerWithSuccessResponse(); - _client = CreateConfiguredQuickbaseClient(); - } - - [Fact] - public async Task QueryRecords_ReturnsSuccessResponse_WhenCalled() - { - // Arrange - var request = new QuickbaseQueryRequest(); - - // Act - var response = await _client.QueryRecords(request); - - // Assert - Assert.True(response.IsSuccess); - Assert.NotNull(response.Value); - Assert.NotEmpty(response.Value.Data); - Assert.NotEmpty(response.Value.Fields); - Assert.NotNull(response.Value.Metadata); - } - - [Fact] - public async Task QueryRecords_ReturnsErrorResponse_WhenBadRequestOccurs() - { - // Arrange - SetupMockHandlerWithErrorResponse(); - var request = new QuickbaseQueryRequest - { - From = "tableId", - Where = "{1.CT.'query'}", - Select = [1, 2, 3] - }; - - // Act - var actualResponse = await _client.QueryRecords(request); - - // Assert - Assert.False(actualResponse.IsSuccess); - Assert.NotNull(actualResponse.QuickbaseError); - } - - #region Helpers - private void SetupMockHandlerWithErrorResponse() - { - var faker = new Faker(); - var errorResponse = new QuickbaseErrorResponse - { - Message = faker.Random.Words(), - Description = faker.Lorem.Paragraph() - }; - - _mockHandler.ResponseContent = JsonSerializer.Serialize(errorResponse); - _mockHandler.ResponseStatusCode = HttpStatusCode.BadRequest; - } - - private MockHttpMessageHandler SetupMockHandlerWithSuccessResponse() - { - var mockJsonResponse = GetMockJsonResponse(); - return new MockHttpMessageHandler - { - ResponseContent = mockJsonResponse, - ResponseStatusCode = HttpStatusCode.OK - }; - } - - private QuickbaseClient CreateConfiguredQuickbaseClient() - { - var httpClient = new HttpClient(_mockHandler) - { - BaseAddress = new Uri(BaseUrl) - }; - return new QuickbaseClient(TestRealm, TestToken) - { - Client = httpClient - }; - } - - private static string GetMockJsonResponse() - { - // JSON response string... - return @"{ - ""data"": [ - { - ""6"": { ""value"": ""Andre Harris"" }, - ""7"": { ""value"": 10 }, - ""8"": { ""value"": ""2019-12-18T08:00:00Z"" } - } - ], - ""fields"": [ - { ""id"": 6, ""label"": ""Full Name"", ""type"": ""text"" }, - { ""id"": 7, ""label"": ""Amount"", ""type"": ""numeric"" }, - { ""id"": 8, ""label"": ""Date time"", ""type"": ""date time"" } - ], - ""metadata"": { - ""totalRecords"": 10, - ""numRecords"": 1, - ""numFields"": 3, - ""skip"": 0 - } - }"; - } - - #endregion -} \ No newline at end of file diff --git a/QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs b/QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs index 5484e1a..c06dd71 100644 --- a/QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs +++ b/QuickbaseNet.UnitTests/Tests/QuickbaseClientTests.cs @@ -107,68 +107,6 @@ public async Task QueryRecords_ReturnsErrorResponse_When5xxOccurs() Assert.NotNull(actualResponse.QuickbaseError); } - [Fact] - public async Task InsertRecords_ReturnsSuccessResponse_WhenCalled() - { - // Arrange - var request = new Builder().Build(); - - // Act - var response = await _client.InsertRecords(request); - - // Assert - Assert.True(response.IsSuccess); - Assert.NotNull(response.Response); - Assert.Null(response.Error); - } - - [Fact] - public async Task InsertRecords_ReturnsErrorResponse_WhenFails() - { - // Arrange - SetupMockHandlerWithErrorResponse(); - var request = new Builder().Build(); - - // Act - var actualResponse = await _client.InsertRecords(request); - - // Assert - Assert.False(actualResponse.IsSuccess); - Assert.Null(actualResponse.Response); - Assert.NotNull(actualResponse.Error); - } - - [Fact] - public async Task UpdateRecords_ReturnsSuccessResponse_WhenCalled() - { - // Arrange - var request = new Builder().Build(); - - // Act - var response = await _client.UpdateRecords(request); - - // Assert - Assert.True(response.IsSuccess); - Assert.NotNull(response.Response); - Assert.Null(response.Error); - } - - [Fact] - public async Task UpdateRecords_ReturnsErrorResponse_WhenFails() - { - // Arrange - SetupMockHandlerWithErrorResponse(); - var request = new Builder().Build(); - - // Act - var actualResponse = await _client.UpdateRecords(request); - - // Assert - Assert.False(actualResponse.IsSuccess); - Assert.Null(actualResponse.Response); - Assert.NotNull(actualResponse.Error); - } - #region Helpers private void SetupMockHandlerWithErrorResponse() { From b0e07bbefbacfe3a011a4dc19218613a886bbfa5 Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 09:57:44 -0600 Subject: [PATCH 06/10] chore(examples): removed the example project Removed the example project from the solution, testing it locally on a seperate solution now. --- QuickbaseNet.Examples/Program.cs | 57 ------------------- .../QuickbaseNet.Examples.csproj | 14 ----- QuickbaseNet.sln | 8 +-- 3 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 QuickbaseNet.Examples/Program.cs delete mode 100644 QuickbaseNet.Examples/QuickbaseNet.Examples.csproj diff --git a/QuickbaseNet.Examples/Program.cs b/QuickbaseNet.Examples/Program.cs deleted file mode 100644 index 5c61bc5..0000000 --- a/QuickbaseNet.Examples/Program.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Newtonsoft.Json; -using QuickbaseNet.Helpers; -using QuickbaseNet.Requests; -using QuickbaseNet.Services; - -namespace QuickbaseNet.Examples -{ - internal class Program - { - static async Task Main(string[] args) - { - var quickBaseClient = new QuickbaseClient("builderprogram-pcohen", "b4mtg6_p7vz_0_dcmcjr3bdtp4csdtuu6qx3tyzuc"); - - var insertCommand = new QuickbaseCommandBuilder() - .ForTable("bsfhutyzn") - .ReturnFields(1, 2, 3) - .AddNewRecord(record => record - .AddField("6", "Hired consulting firm to do market study.") - .AddField("7", 5000) - .AddField("9", "2023-01-01")) - .BuildInsertUpdateCommand(); - - var result = await quickBaseClient.InsertRecords(insertCommand); - - // var quickBaseClient = new QuickbaseClient("diamond", Environment.GetEnvironmentVariable("QB_USERTOKEN")); - // var query = new QuickbaseQueryBuilder() - // .From("bsfhutyzn") - // .Select(6, 7, 8, 9, 10, 11, 12, 13) - // .Build(); - // - // string jsonRequest = JsonConvert.SerializeObject(query); - // - // var result = await quickBaseClient.QueryRecords(query); - if (result.IsSuccess) - { - Console.WriteLine("Success!"); - Console.WriteLine(JsonConvert.SerializeObject(result.Value, Formatting.Indented)); - } - else if (result.IsFailure) - { - Console.WriteLine("Error!"); - Console.WriteLine(JsonConvert.SerializeObject(result.QuickbaseError, Formatting.Indented)); - } - - - - // InsertOrUpdateRecordRequest request = recordBuilder.Build(); - - // var commandBuilder = new QuickbaseCommandBuilder() - // .ForTable("bck7gp3q2") - // .WithDeletionCriteria("{6.EX.'hello'}"); - // - // DeleteRecordRequest deleteRequest = commandBuilder.BuildDeleteCommand(); - - } - } -} diff --git a/QuickbaseNet.Examples/QuickbaseNet.Examples.csproj b/QuickbaseNet.Examples/QuickbaseNet.Examples.csproj deleted file mode 100644 index 1c00052..0000000 --- a/QuickbaseNet.Examples/QuickbaseNet.Examples.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - diff --git a/QuickbaseNet.sln b/QuickbaseNet.sln index 456a5a9..91857e2 100644 --- a/QuickbaseNet.sln +++ b/QuickbaseNet.sln @@ -5,9 +5,7 @@ VisualStudioVersion = 17.8.34322.80 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet", "QuickbaseNet\QuickbaseNet.csproj", "{375B33E5-C837-4915-844C-52057055E84C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet.Examples", "QuickbaseNet.Examples\QuickbaseNet.Examples.csproj", "{F92A2FF7-450E-4672-8781-BC648ACE2ACF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickbaseNet.UnitTests", "QuickbaseNet.UnitTests\QuickbaseNet.UnitTests.csproj", "{E2654CA5-971C-43D0-912E-D4445F1EB4B0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickbaseNet.UnitTests", "QuickbaseNet.UnitTests\QuickbaseNet.UnitTests.csproj", "{E2654CA5-971C-43D0-912E-D4445F1EB4B0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,10 +17,6 @@ Global {375B33E5-C837-4915-844C-52057055E84C}.Debug|Any CPU.Build.0 = Debug|Any CPU {375B33E5-C837-4915-844C-52057055E84C}.Release|Any CPU.ActiveCfg = Release|Any CPU {375B33E5-C837-4915-844C-52057055E84C}.Release|Any CPU.Build.0 = Release|Any CPU - {F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F92A2FF7-450E-4672-8781-BC648ACE2ACF}.Release|Any CPU.Build.0 = Release|Any CPU {E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2654CA5-971C-43D0-912E-D4445F1EB4B0}.Release|Any CPU.ActiveCfg = Release|Any CPU From a2bdc71b9d828c90d75d63e88ffce7eb3fde8eb4 Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 10:22:16 -0600 Subject: [PATCH 07/10] feat: add the delete command to interact with delete API from qb Add the fluent delete command to delete records via Quickbase API fix #10 --- QuickbaseNet/Services/QuickbaseClient.cs | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/QuickbaseNet/Services/QuickbaseClient.cs b/QuickbaseNet/Services/QuickbaseClient.cs index 036c12b..ad6efa7 100644 --- a/QuickbaseNet/Services/QuickbaseClient.cs +++ b/QuickbaseNet/Services/QuickbaseClient.cs @@ -129,5 +129,46 @@ public async Task> UpdateRecords( // Its a 5xx error return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse, "Server error")); } + + public async Task> DeleteRecords( + DeleteRecordRequest quickBaseRequest) + { + // Serialize your request object into a JSON string + var requestJson = JsonConvert.SerializeObject(quickBaseRequest); + HttpContent content = new StringContent(requestJson, Encoding.UTF8, "application/json"); + + // Create an HttpRequestMessage for DELETE + var request = new HttpRequestMessage(HttpMethod.Delete, "/v1/records") + { + Content = content + }; + + // Send the request + var response = await Client.SendAsync(request); + + // The rest of your method remains the same + if (response.IsSuccessStatusCode) + { + var jsonResponse = await response.Content.ReadAsStringAsync(); + + if (string.IsNullOrEmpty(jsonResponse)) + { + return QuickbaseResult.Failure(QuickbaseError.NotFound("QuickbaseNet.Failure", + "No records found", $"The query did not find any records matching that criteria")); + } + + return QuickbaseResult.Success(JsonConvert.DeserializeObject(jsonResponse)); + } + + var errorResponse = await response.Content.ReadAsStringAsync(); + + if (response.StatusCode >= System.Net.HttpStatusCode.BadRequest && + response.StatusCode < System.Net.HttpStatusCode.InternalServerError) + { + return QuickbaseResult.Failure(QuickbaseError.ClientError("QuickbaseNet.ClientError", errorResponse, "Client error")); + } + + return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse, "Server error")); + } } } From ff2231e5e317a2eff093cdcf05ad949a572857c2 Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 10:30:46 -0600 Subject: [PATCH 08/10] refactor(commands): use int type for fieldId spec Use integer for specifying which field ID will be updated. --- QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs | 5 ++--- QuickbaseNet/Services/QuickbaseClient.cs | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs b/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs index 1312515..fcda582 100644 --- a/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs +++ b/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs @@ -43,7 +43,6 @@ public QuickbaseCommandBuilder UpdateRecord(int recordId, Action return this; } - public QuickbaseCommandBuilder WithDeletionCriteria(string whereClause) { _whereClauseForDeletion = whereClause; @@ -73,9 +72,9 @@ public class RecordBuilder { private readonly Dictionary _fields = new Dictionary(); - public RecordBuilder AddField(string fieldId, T value) + public RecordBuilder AddField(int fieldId, T value) { - _fields[fieldId] = new FieldValue { Value = value }; + _fields[fieldId.ToString()] = new FieldValue { Value = value }; return this; } diff --git a/QuickbaseNet/Services/QuickbaseClient.cs b/QuickbaseNet/Services/QuickbaseClient.cs index ad6efa7..f2522c7 100644 --- a/QuickbaseNet/Services/QuickbaseClient.cs +++ b/QuickbaseNet/Services/QuickbaseClient.cs @@ -27,6 +27,7 @@ public QuickbaseClient(string realm, string userToken) { throw new ArgumentNullException(nameof(userToken), "User token cannot be null or empty"); } + Client.BaseAddress = new Uri(BaseUrl); Client.DefaultRequestHeaders.Add("QB-Realm-Hostname", $"{realm}.quickbase.com"); Client.DefaultRequestHeaders.Add("Authorization", $"QB-USER-TOKEN {userToken}"); From 046a0c676f61b0065c96375848dacf9aa46d4500 Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 10:40:53 -0600 Subject: [PATCH 09/10] docs(readme): update readme with examples Add some examples of the insert, update, and delete commands in the readme. --- README.md | 80 +++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 1ef5129..3de22c0 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ ## 📋 Overview -QuickbaseNet is a versatile C# library designed to simplify and streamline interactions with the QuickBase API. +QuickbaseNet is a versatile C# library designed to simplify and streamline interactions with the QuickBase API. Tailored for developers looking to efficiently perform CRUD operations and build complex queries, QuickbaseNet offers a set of intuitive tools including `QuickBaseCommandBuilder`, `QueryBuilder`, and `QuickbaseClient`. Whether you're managing database records or crafting detailed queries, QuickbaseNet enhances your experience with QuickBase tables through its fluent and user-friendly interfaces. ## ✨ Features - **Fluent Interface 🌊**: Methods for building various requests easily and intuitively. - +- **Comprehensive CRUD Operations 🛠️**: `QuickBaseCommandBuilder` for adding new records, updating existing ones, or deleting records efficiently. - **Advanced Query Support 🔍**: `QueryBuilder` for constructing complex query requests with ease. - **Seamless Client Setup 🌐**: `QuickbaseClient` for initializing connections with realm and user token for secure and straightforward API interaction. @@ -33,46 +33,25 @@ QuickbaseNet simplifies working with the QuickBase API across various operations // Initialize QuickbaseClient with your realm hostname and user token var quickbaseClient = new QuickbaseClient("your_realm_hostname", "your_user_token"); ``` - - + ### QueryBuilder - Crafting Queries with Precision 🔎 #### Building and Sending a Query 📤 @@ -134,7 +125,16 @@ var query = new QueryBuilder() .Build(); // Send the query and handle the response -var response = await quickbaseClient.QueryRecords(query); +var result = await quickbaseClient.QueryRecords(query); + +if (result.IsSuccess) +{ + // Process successful response +} +else +{ + // Handle error +} ``` ## 👐 Contributing From 23d2f042824cb7ddc589e1571477907c18db6a4c Mon Sep 17 00:00:00 2001 From: ducksoop Date: Tue, 13 Feb 2024 11:05:03 -0600 Subject: [PATCH 10/10] docs: add documentation to code Added extensive xmldocs to every class. --- QuickbaseNet/Errors/QuickbaseError.cs | 59 ++++++++++++++++++- QuickbaseNet/Errors/QuickbaseErrorType.cs | 18 ++++++ .../Helpers/QuickbaseCommandBuilder.cs | 55 ++++++++++++++++- QuickbaseNet/Helpers/QuickbaseQueryBuilder.cs | 36 ++++++++++- QuickbaseNet/Models/Field.cs | 14 +++++ QuickbaseNet/Models/FieldValue.cs | 11 ++++ QuickbaseNet/Models/GroupByItem.cs | 10 ++++ QuickbaseNet/Models/Metadata.cs | 18 ++++++ QuickbaseNet/Models/Options.cs | 14 +++++ QuickbaseNet/Models/SortByItem.cs | 10 ++++ QuickbaseNet/Requests/DeleteRecordRequest.cs | 12 +++- .../Requests/InsertOrUpdateRecordRequest.cs | 12 ++++ .../Requests/QuickbaseQueryRequest.cs | 26 ++++++++ .../Responses/QuickbaseErrorResponse.cs | 10 ++++ .../Responses/QuickbaseQueryResponse.cs | 14 +++++ .../QuickbaseRecordUpdateResponse.cs | 9 +++ QuickbaseNet/Services/QuickbaseClient.cs | 35 ++++++++++- 17 files changed, 356 insertions(+), 7 deletions(-) diff --git a/QuickbaseNet/Errors/QuickbaseError.cs b/QuickbaseNet/Errors/QuickbaseError.cs index bce12b5..da3ae41 100644 --- a/QuickbaseNet/Errors/QuickbaseError.cs +++ b/QuickbaseNet/Errors/QuickbaseError.cs @@ -1,10 +1,27 @@ namespace QuickbaseNet.Errors { + /// + /// Represents an error in the QuickBase API. + /// public class QuickbaseError { + /// + /// Represents no error. + /// public static readonly QuickbaseError None = new QuickbaseError(string.Empty, string.Empty, string.Empty, QuickbaseErrorType.Failure); + + /// + /// Represents an error indicating a null value was provided. + /// public static readonly QuickbaseError NullValue = new QuickbaseError("Error.NullValue", "Null value was provided", string.Empty, QuickbaseErrorType.Failure); + /// + /// Initializes a new instance of the QuickbaseError class. + /// + /// The error code. + /// The error message. + /// The error description. + /// The type of the error. public QuickbaseError(string code, string message, string description, QuickbaseErrorType quickbaseErrorType) { Code = code; @@ -13,24 +30,64 @@ public QuickbaseError(string code, string message, string description, Quickbase Type = quickbaseErrorType; } + /// + /// Gets the error code. + /// public string Code { get; private set; } + /// + /// Gets the error message. + /// public string Message { get; private set; } + /// + /// Gets the error description. + /// public string Description { get; private set; } + /// + /// Gets the type of the error. + /// public QuickbaseErrorType Type { get; private set; } + /// + /// Creates a new QuickbaseError representing a not found error. + /// + /// The error code. + /// The error message. + /// The error description. + /// A QuickbaseError representing a not found error. public static QuickbaseError NotFound(string code, string message, string description) => new QuickbaseError(code, message, description, QuickbaseErrorType.NotFound); + /// + /// Creates a new QuickbaseError representing a failure error. + /// + /// The error code. + /// The error message. + /// The error description. + /// A QuickbaseError representing a failure error. public static QuickbaseError Failure(string code, string message, string description) => new QuickbaseError(code, message, description, QuickbaseErrorType.Failure); + /// + /// Creates a new QuickbaseError representing a client error. + /// + /// The error code. + /// The error message. + /// The error description. + /// A QuickbaseError representing a client error. public static QuickbaseError ClientError(string code, string message, string description) => new QuickbaseError(code, message, description, QuickbaseErrorType.ClientError); + /// + /// Creates a new QuickbaseError representing a server error. + /// + /// The error code. + /// The error message. + /// The error description. + /// A QuickbaseError representing a server error. public static QuickbaseError ServerError(string code, string message, string description) => new QuickbaseError(code, message, description, QuickbaseErrorType.ServerError); } -} \ No newline at end of file +} diff --git a/QuickbaseNet/Errors/QuickbaseErrorType.cs b/QuickbaseNet/Errors/QuickbaseErrorType.cs index 0f3cd9b..0efbffb 100644 --- a/QuickbaseNet/Errors/QuickbaseErrorType.cs +++ b/QuickbaseNet/Errors/QuickbaseErrorType.cs @@ -1,10 +1,28 @@ namespace QuickbaseNet.Errors { + /// + /// Represents the type of QuickBase API error. + /// public enum QuickbaseErrorType { + /// + /// Indicates a general failure error. + /// Failure = 0, + + /// + /// Indicates a not found error. + /// NotFound = 1, + + /// + /// Indicates a client error. + /// ClientError = 3, + + /// + /// Indicates a server error. + /// ServerError = 4 } } \ No newline at end of file diff --git a/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs b/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs index fcda582..fbb5675 100644 --- a/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs +++ b/QuickbaseNet/Helpers/QuickbaseCommandBuilder.cs @@ -5,6 +5,9 @@ namespace QuickbaseNet.Helpers { + /// + /// Helper class for building QuickBase API commands. + /// public class QuickbaseCommandBuilder { private string _tableId; @@ -12,18 +15,33 @@ public class QuickbaseCommandBuilder private readonly List> _records = new List>(); private int[] _fieldsToReturn; + /// + /// Specifies the table to operate on. + /// + /// The ID of the QuickBase table. + /// The current instance of QuickbaseCommandBuilder. public QuickbaseCommandBuilder ForTable(string tableId) { _tableId = tableId; return this; } + /// + /// Specifies which fields to return after the operation. + /// + /// The IDs of the fields to return. + /// The current instance of QuickbaseCommandBuilder. public QuickbaseCommandBuilder ReturnFields(params int[] fieldIds) { _fieldsToReturn = fieldIds; return this; } + /// + /// Adds a new record to the command being built. + /// + /// Action to configure the new record. + /// The current instance of QuickbaseCommandBuilder. public QuickbaseCommandBuilder AddNewRecord(Action config) { var recordBuilder = new RecordBuilder(); @@ -32,23 +50,38 @@ public QuickbaseCommandBuilder AddNewRecord(Action config) return this; } + /// + /// Updates an existing record in the command being built. + /// + /// The ID of the record to update. + /// Action to configure the updated record. + /// The current instance of QuickbaseCommandBuilder. public QuickbaseCommandBuilder UpdateRecord(int recordId, Action config) { var recordBuilder = new RecordBuilder(); config(recordBuilder); // Assuming '3' is the default key field ID for record ID - recordBuilder.AddField("3", recordId.ToString()); + recordBuilder.AddField(3, recordId.ToString()); _records.Add(recordBuilder.Build()); return this; } + /// + /// Specifies deletion criteria for records. + /// + /// The deletion criteria. + /// The current instance of QuickbaseCommandBuilder. public QuickbaseCommandBuilder WithDeletionCriteria(string whereClause) { _whereClauseForDeletion = whereClause; return this; } + /// + /// Builds an insert or update command. + /// + /// An InsertOrUpdateRecordRequest object representing the command. public InsertOrUpdateRecordRequest BuildInsertUpdateCommand() { return new InsertOrUpdateRecordRequest @@ -59,6 +92,10 @@ public InsertOrUpdateRecordRequest BuildInsertUpdateCommand() }; } + /// + /// Builds a delete command. + /// + /// A DeleteRecordRequest object representing the command. public DeleteRecordRequest BuildDeleteCommand() { return new DeleteRecordRequest @@ -68,16 +105,30 @@ public DeleteRecordRequest BuildDeleteCommand() }; } + /// + /// Builder class for constructing record objects. + /// public class RecordBuilder { private readonly Dictionary _fields = new Dictionary(); + /// + /// Adds a field to the record being built. + /// + /// The type of the field value. + /// The ID of the field. + /// The value of the field. + /// The current instance of RecordBuilder. public RecordBuilder AddField(int fieldId, T value) { - _fields[fieldId.ToString()] = new FieldValue { Value = value }; + _fields[fieldId.ToString()] = new FieldValue { Value = value }; return this; } + /// + /// Builds the record. + /// + /// A dictionary representing the record. public Dictionary Build() { return _fields; diff --git a/QuickbaseNet/Helpers/QuickbaseQueryBuilder.cs b/QuickbaseNet/Helpers/QuickbaseQueryBuilder.cs index 5c1d162..c7684b9 100644 --- a/QuickbaseNet/Helpers/QuickbaseQueryBuilder.cs +++ b/QuickbaseNet/Helpers/QuickbaseQueryBuilder.cs @@ -5,6 +5,9 @@ namespace QuickbaseNet.Helpers { + /// + /// Helper class for building QuickBase API queries. + /// public class QuickbaseQueryBuilder { private string _from; @@ -13,24 +16,45 @@ public class QuickbaseQueryBuilder private List _sortBy; private List _groupBy; + /// + /// Specifies the table to query. + /// + /// The ID or name of the QuickBase table. + /// The current instance of QuickbaseQueryBuilder. public QuickbaseQueryBuilder From(string from) { _from = from; return this; } + /// + /// Specifies which fields to select in the query result. + /// + /// The IDs of the fields to select. + /// The current instance of QuickbaseQueryBuilder. public QuickbaseQueryBuilder Select(params int[] fields) { _select = fields.ToList(); return this; } + /// + /// Specifies the WHERE clause of the query. + /// + /// The WHERE clause. + /// The current instance of QuickbaseQueryBuilder. public QuickbaseQueryBuilder Where(string where) { _where = where; return this; } + /// + /// Specifies the fields to sort the query result by. + /// + /// The ID of the field to sort by. + /// The sorting order ("ASC" for ascending, "DESC" for descending). + /// The current instance of QuickbaseQueryBuilder. public QuickbaseQueryBuilder SortBy(int fieldId, string order) { if (_sortBy == null) @@ -40,6 +64,12 @@ public QuickbaseQueryBuilder SortBy(int fieldId, string order) return this; } + /// + /// Specifies the fields to group the query result by. + /// + /// The ID of the field to group by. + /// The grouping criteria. + /// The current instance of QuickbaseQueryBuilder. public QuickbaseQueryBuilder GroupBy(int fieldId, string grouping) { if (_groupBy == null) @@ -49,6 +79,10 @@ public QuickbaseQueryBuilder GroupBy(int fieldId, string grouping) return this; } + /// + /// Builds the query. + /// + /// A QuickbaseQueryRequest object representing the query. public QuickbaseQueryRequest Build() { return new QuickbaseQueryRequest @@ -61,4 +95,4 @@ public QuickbaseQueryRequest Build() }; } } -} \ No newline at end of file +} diff --git a/QuickbaseNet/Models/Field.cs b/QuickbaseNet/Models/Field.cs index 7759d80..3476236 100644 --- a/QuickbaseNet/Models/Field.cs +++ b/QuickbaseNet/Models/Field.cs @@ -2,12 +2,26 @@ namespace QuickbaseNet.Models { + /// + /// Represents a field in a QuickBase table. + /// public class Field { + /// + /// Gets or sets the ID of the field. + /// [JsonProperty("id")] public int Id { get; set; } + + /// + /// Gets or sets the label of the field. + /// [JsonProperty("label")] public string Label { get; set; } + + /// + /// Gets or sets the type of the field. + /// [JsonProperty("type")] public string Type { get; set; } } diff --git a/QuickbaseNet/Models/FieldValue.cs b/QuickbaseNet/Models/FieldValue.cs index 8a2654b..a0b5ec2 100644 --- a/QuickbaseNet/Models/FieldValue.cs +++ b/QuickbaseNet/Models/FieldValue.cs @@ -3,11 +3,22 @@ namespace QuickbaseNet.Models { + /// + /// Represents a value of a field in a QuickBase record. + /// public class FieldValue { + /// + /// Gets or sets the value of the field. + /// [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)] public object Value { get; set; } + /// + /// Gets the strongly-typed value of the field. + /// + /// The type to convert the value to. + /// The strongly-typed value of the field. public T GetValue() { return Value == null ? default : (T)Convert.ChangeType(Value, typeof(T)); diff --git a/QuickbaseNet/Models/GroupByItem.cs b/QuickbaseNet/Models/GroupByItem.cs index b44d9e6..74e9d91 100644 --- a/QuickbaseNet/Models/GroupByItem.cs +++ b/QuickbaseNet/Models/GroupByItem.cs @@ -2,10 +2,20 @@ namespace QuickbaseNet.Models { + /// + /// Represents a field used for grouping in a QuickBase query. + /// public class GroupByItem { + /// + /// Gets or sets the ID of the field used for grouping. + /// [JsonProperty("fieldId")] public int FieldId { get; set; } + + /// + /// Gets or sets the grouping criteria. + /// [JsonProperty("grouping")] public string Grouping { get; set; } } diff --git a/QuickbaseNet/Models/Metadata.cs b/QuickbaseNet/Models/Metadata.cs index ec6d985..f862f9b 100644 --- a/QuickbaseNet/Models/Metadata.cs +++ b/QuickbaseNet/Models/Metadata.cs @@ -2,14 +2,32 @@ namespace QuickbaseNet.Models { + /// + /// Represents metadata associated with a QuickBase response. + /// public class Metadata { + /// + /// Gets or sets the total number of records. + /// [JsonProperty("totalRecords")] public int TotalRecords { get; set; } + + /// + /// Gets or sets the number of records returned. + /// [JsonProperty("numRecords")] public int NumRecords { get; set; } + + /// + /// Gets or sets the number of fields returned. + /// [JsonProperty("numFields")] public int NumFields { get; set; } + + /// + /// Gets or sets the number of records to skip. + /// [JsonProperty("skip")] public int Skip { get; set; } } diff --git a/QuickbaseNet/Models/Options.cs b/QuickbaseNet/Models/Options.cs index c4542f1..19a4029 100644 --- a/QuickbaseNet/Models/Options.cs +++ b/QuickbaseNet/Models/Options.cs @@ -2,12 +2,26 @@ namespace QuickbaseNet.Models { + /// + /// Represents options for controlling various aspects of QuickBase operations. + /// public class Options { + /// + /// Gets or sets the number of records to skip in the result set. + /// [JsonProperty("skip")] public int Skip { get; set; } + + /// + /// Gets or sets the maximum number of records to return in the result set. + /// [JsonProperty("top")] public int Top { get; set; } + + /// + /// Gets or sets a value indicating whether to compare with the application's local time. + /// [JsonProperty("compareWithAppLocalTime")] public bool CompareWithAppLocalTime { get; set; } } diff --git a/QuickbaseNet/Models/SortByItem.cs b/QuickbaseNet/Models/SortByItem.cs index 325e4ea..cdf9d18 100644 --- a/QuickbaseNet/Models/SortByItem.cs +++ b/QuickbaseNet/Models/SortByItem.cs @@ -2,10 +2,20 @@ namespace QuickbaseNet.Models { + /// + /// Represents a field used for sorting in a QuickBase query. + /// public class SortByItem { + /// + /// Gets or sets the ID of the field used for sorting. + /// [JsonProperty("fieldId")] public int FieldId { get; set; } + + /// + /// Gets or sets the sorting order ("ASC" for ascending, "DESC" for descending). + /// [JsonProperty("order")] public string Order { get; set; } } diff --git a/QuickbaseNet/Requests/DeleteRecordRequest.cs b/QuickbaseNet/Requests/DeleteRecordRequest.cs index 5aa164d..70cec18 100644 --- a/QuickbaseNet/Requests/DeleteRecordRequest.cs +++ b/QuickbaseNet/Requests/DeleteRecordRequest.cs @@ -2,10 +2,20 @@ namespace QuickbaseNet.Requests { + /// + /// Represents a request to delete records in the QuickBase API. + /// public class DeleteRecordRequest { - [JsonProperty("from")] + /// + /// Gets or sets the ID or name of the table from which to delete records. + /// + [JsonProperty("from")] public string From { get; set; } + + /// + /// Gets or sets the WHERE clause specifying which records to delete. + /// [JsonProperty("where")] public string Where { get; set; } } diff --git a/QuickbaseNet/Requests/InsertOrUpdateRecordRequest.cs b/QuickbaseNet/Requests/InsertOrUpdateRecordRequest.cs index 41bf7e0..4da979a 100644 --- a/QuickbaseNet/Requests/InsertOrUpdateRecordRequest.cs +++ b/QuickbaseNet/Requests/InsertOrUpdateRecordRequest.cs @@ -5,14 +5,26 @@ namespace QuickbaseNet.Requests { + /// + /// Represents a request to insert or update records in the QuickBase API. + /// public class InsertOrUpdateRecordRequest { + /// + /// Gets or sets the ID or name of the table to which records will be inserted or updated. + /// [JsonProperty("to", NullValueHandling = NullValueHandling.Ignore)] public string To { get; set; } = string.Empty; + /// + /// Gets or sets the data to be inserted or updated, represented as a list of dictionaries mapping field IDs to field values. + /// [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] public List> Data { get; set; } = new List>(); + /// + /// Gets or sets the IDs of fields to return after the insert or update operation. + /// [JsonProperty("fieldsToReturn", NullValueHandling = NullValueHandling.Ignore)] public int[] FieldsToReturn { get; set; } = Array.Empty(); } diff --git a/QuickbaseNet/Requests/QuickbaseQueryRequest.cs b/QuickbaseNet/Requests/QuickbaseQueryRequest.cs index bad20cc..b3c42a4 100644 --- a/QuickbaseNet/Requests/QuickbaseQueryRequest.cs +++ b/QuickbaseNet/Requests/QuickbaseQueryRequest.cs @@ -4,18 +4,44 @@ namespace QuickbaseNet.Requests { + /// + /// Represents a request to perform a query in the QuickBase API. + /// public class QuickbaseQueryRequest { + /// + /// Gets or sets the ID or name of the table from which to query records. + /// [JsonProperty("from")] public string From { get; set; } + + /// + /// Gets or sets the list of field IDs to select in the query. + /// [JsonProperty("select")] public List Select { get; set; } + + /// + /// Gets or sets the WHERE clause specifying conditions for the query. + /// [JsonProperty("where")] public string Where { get; set; } + + /// + /// Gets or sets the list of fields and sort order to sort the query results by. + /// [JsonProperty("sortBy")] public List SortBy { get; set; } + + /// + /// Gets or sets the list of fields and grouping criteria to group the query results by. + /// [JsonProperty("groupBy")] public List GroupBy { get; set; } + + /// + /// Gets or sets the options for controlling various aspects of the query. + /// [JsonProperty("options")] public Options Options { get; set; } } diff --git a/QuickbaseNet/Responses/QuickbaseErrorResponse.cs b/QuickbaseNet/Responses/QuickbaseErrorResponse.cs index 6303b1a..12d9a61 100644 --- a/QuickbaseNet/Responses/QuickbaseErrorResponse.cs +++ b/QuickbaseNet/Responses/QuickbaseErrorResponse.cs @@ -2,10 +2,20 @@ namespace QuickbaseNet.Responses { + /// + /// Represents an error response from the QuickBase API. + /// public class QuickbaseErrorResponse { + /// + /// Gets or sets the error message. + /// [JsonProperty("message")] public string Message { get; set; } + + /// + /// Gets or sets the error description. + /// [JsonProperty("description")] public string Description { get; set; } } diff --git a/QuickbaseNet/Responses/QuickbaseQueryResponse.cs b/QuickbaseNet/Responses/QuickbaseQueryResponse.cs index 6dcad37..6d03b10 100644 --- a/QuickbaseNet/Responses/QuickbaseQueryResponse.cs +++ b/QuickbaseNet/Responses/QuickbaseQueryResponse.cs @@ -4,12 +4,26 @@ namespace QuickbaseNet.Responses { + /// + /// Represents a response from a query operation in the QuickBase API. + /// public class QuickbaseQueryResponse { + /// + /// Gets or sets the data retrieved from the query. + /// [JsonProperty("data")] public List> Data { get; set; } + + /// + /// Gets or sets the fields included in the response. + /// [JsonProperty("fields")] public List Fields { get; set; } + + /// + /// Gets or sets the metadata associated with the response. + /// [JsonProperty("metadata")] public Metadata Metadata { get; set; } } diff --git a/QuickbaseNet/Responses/QuickbaseRecordUpdateResponse.cs b/QuickbaseNet/Responses/QuickbaseRecordUpdateResponse.cs index 9e8c3d2..8d708bd 100644 --- a/QuickbaseNet/Responses/QuickbaseRecordUpdateResponse.cs +++ b/QuickbaseNet/Responses/QuickbaseRecordUpdateResponse.cs @@ -4,11 +4,20 @@ namespace QuickbaseNet.Responses { + /// + /// Represents a response from a record update operation in the QuickBase API. + /// public class QuickbaseRecordUpdateResponse { + /// + /// Gets or sets the data updated in the record. + /// [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] public List> Data { get; set; } = new List>(); + /// + /// Gets or sets the metadata associated with the response. + /// [JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)] public Metadata Metadata { get; set; } = new Metadata(); } diff --git a/QuickbaseNet/Services/QuickbaseClient.cs b/QuickbaseNet/Services/QuickbaseClient.cs index f2522c7..0accf8e 100644 --- a/QuickbaseNet/Services/QuickbaseClient.cs +++ b/QuickbaseNet/Services/QuickbaseClient.cs @@ -9,13 +9,25 @@ namespace QuickbaseNet.Services { + /// + /// Provides methods for interacting with the QuickBase API. + /// public class QuickbaseClient { private const string BaseUrl = "https://api.quickbase.com"; private const string UserAgent = "QuickbaseNet/1.0.0"; + /// + /// Gets or sets the HTTP client used to make requests to the QuickBase API. + /// public HttpClient Client { get; set; } = new HttpClient(); + /// + /// Initializes a new instance of the class with the specified realm and user token. + /// + /// The realm hostname of the QuickBase account. + /// The user token used for authentication. + /// Thrown when or is null or empty. public QuickbaseClient(string realm, string userToken) { if (string.IsNullOrEmpty(realm)) @@ -34,6 +46,11 @@ public QuickbaseClient(string realm, string userToken) Client.DefaultRequestHeaders.Add("User-Agent", UserAgent); } + /// + /// Sends a query request to the QuickBase API and retrieves the response. + /// + /// The query request to send. + /// A task representing the asynchronous operation. The task result contains the query response. public async Task> QueryRecords(QuickbaseQueryRequest quickBaseRequest) { HttpContent content = new StringContent(JsonConvert.SerializeObject(quickBaseRequest), Encoding.UTF8, "application/json"); @@ -65,6 +82,11 @@ public async Task> QueryRecords(Quickbas return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse.Message, errorResponse.Description)); } + /// + /// Sends a request to insert records to the QuickBase API and retrieves the response. + /// + /// The insert request to send. + /// A task representing the asynchronous operation. The task result contains the insert response. public async Task> InsertRecords(InsertOrUpdateRecordRequest quickBaseRequest) { HttpContent content = new StringContent(JsonConvert.SerializeObject(quickBaseRequest), Encoding.UTF8, "application/json"); @@ -98,6 +120,11 @@ public async Task> InsertRecords( return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse, "Server error")); } + /// + /// Sends a request to update records in the QuickBase API and retrieves the response. + /// + /// The update request to send. + /// A task representing the asynchronous operation. The task result contains the update response. public async Task> UpdateRecords(InsertOrUpdateRecordRequest quickBaseRequest) { HttpContent content = new StringContent(JsonConvert.SerializeObject(quickBaseRequest), Encoding.UTF8, "application/json"); @@ -131,8 +158,12 @@ public async Task> UpdateRecords( return QuickbaseResult.Failure(QuickbaseError.ServerError("QuickbaseNet.ServerError", errorResponse, "Server error")); } - public async Task> DeleteRecords( - DeleteRecordRequest quickBaseRequest) + /// + /// Sends a request to delete records from the QuickBase API and retrieves the response. + /// + /// The delete request to send. + /// A task representing the asynchronous operation. The task result contains the delete response. + public async Task> DeleteRecords(DeleteRecordRequest quickBaseRequest) { // Serialize your request object into a JSON string var requestJson = JsonConvert.SerializeObject(quickBaseRequest);