diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 401338e..d31e317 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -24,8 +24,15 @@ jobs:
- name: Build
run: dotnet build --configuration Release --no-restore
- - name: Initialize Testing Stack
- run: docker-compose up -d
+ - uses: supabase/setup-cli@v1
+ with:
+ version: latest
+
+ - name: Start supabase
+ run: supabase start
+
+# - name: Initialize Testing Stack
+# run: docker-compose up -d
- name: Test
run: dotnet test --no-restore
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 0000000..eeb4cf7
--- /dev/null
+++ b/.github/workflows/release-please.yml
@@ -0,0 +1,21 @@
+on:
+ push:
+ branches:
+ - master
+
+permissions:
+ contents: write
+ pull-requests: write
+ issues: write
+
+name: release-please
+
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: googleapis/release-please-action@v4
+ with:
+ target-branch: ${{ github.ref_name }}
+ manifest-file: .release-please-manifest.json
+ config-file: release-please-config.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index cd21e83..d795982 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,12 +1,19 @@
name: Publish NuGet Package
on:
- push:
- branches:
- - release/* # Default release branch
+ workflow_run:
+ workflows: ["release-please"]
+ branches: [master]
+ types:
+ - completed
+
+# push:
+# branches:
+# - release/* # Default release branch
jobs:
publish:
+ if: ${{ startsWith(github.event.head_commit.message, 'chore(main)') && github.ref == 'refs/heads/main' && github.event_name == 'push' && github.event.workflow_run.conclusion == 'success' }}
name: build, pack & publish
runs-on: ubuntu-latest
steps:
@@ -24,7 +31,7 @@ jobs:
check-name: build-and-test
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
-
+
- name: Restore dependencies
run: dotnet restore
@@ -35,4 +42,5 @@ jobs:
run: dotnet pack ./Functions/Functions.csproj --configuration Release
- name: Publish on version change
- run: dotnet nuget push "**/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
+ run: echo "publish on nuget"
+ # run: dotnet nuget push "**/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 0000000..895bf0e
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+ ".": "2.0.0"
+}
diff --git a/Functions/Client.cs b/Functions/Client.cs
index 16dc389..6ed1cac 100644
--- a/Functions/Client.cs
+++ b/Functions/Client.cs
@@ -1,16 +1,16 @@
-using Newtonsoft.Json;
-using Supabase.Core;
-using Supabase.Core.Extensions;
-using Supabase.Functions.Interfaces;
-using Supabase.Functions.Responses;
-using System;
+using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Web;
+using Newtonsoft.Json;
+using Supabase.Core;
+using Supabase.Core.Extensions;
using Supabase.Functions.Exceptions;
+using Supabase.Functions.Interfaces;
+using Supabase.Functions.Responses;
[assembly: InternalsVisibleTo("FunctionsTests")]
@@ -24,7 +24,7 @@ public partial class Client : IFunctionsClient
///
/// Function that can be set to return dynamic headers.
- ///
+ ///
/// Headers specified in the method parameters will ALWAYS take precedence over headers returned by this function.
///
public Func>? GetHeaders { get; set; }
@@ -45,8 +45,11 @@ public Client(string baseUrl)
/// Anon Key.
/// Options
///
- public async Task RawInvoke(string functionName, string? token = null,
- InvokeFunctionOptions? options = null)
+ public async Task RawInvoke(
+ string functionName,
+ string? token = null,
+ InvokeFunctionOptions? options = null
+ )
{
var url = $"{_baseUrl}/{functionName}";
@@ -60,8 +63,11 @@ public async Task RawInvoke(string functionName, string? token = nu
/// Anon Key.
/// Options
///
- public async Task Invoke(string functionName, string? token = null,
- InvokeFunctionOptions? options = null)
+ public async Task Invoke(
+ string functionName,
+ string? token = null,
+ InvokeFunctionOptions? options = null
+ )
{
var url = $"{_baseUrl}/{functionName}";
var response = await HandleRequest(url, token, options);
@@ -77,8 +83,12 @@ public async Task Invoke(string functionName, string? token = null,
/// Anon Key.
/// Options
///
- public async Task Invoke(string functionName, string? token = null,
- InvokeFunctionOptions? options = null) where T : class
+ public async Task Invoke(
+ string functionName,
+ string? token = null,
+ InvokeFunctionOptions? options = null
+ )
+ where T : class
{
var url = $"{_baseUrl}/{functionName}";
var response = await HandleRequest(url, token, options);
@@ -96,8 +106,11 @@ public async Task Invoke(string functionName, string? token = null,
///
///
///
- private async Task HandleRequest(string url, string? token = null,
- InvokeFunctionOptions? options = null)
+ private async Task HandleRequest(
+ string url,
+ string? token = null,
+ InvokeFunctionOptions? options = null
+ )
{
options ??= new InvokeFunctionOptions();
@@ -113,26 +126,34 @@ private async Task HandleRequest(string url, string? token
options.Headers["X-Client-Info"] = Util.GetAssemblyVersion(typeof(Client));
+ if (options.FunctionRegion != FunctionRegion.Any)
+ {
+ options.Headers["x-region"] = options.FunctionRegion.ToString();
+ }
+
var builder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(builder.Query);
builder.Query = query.ToString();
- using var requestMessage = new HttpRequestMessage(HttpMethod.Post, builder.Uri);
- requestMessage.Content = new StringContent(JsonConvert.SerializeObject(options.Body), Encoding.UTF8,
- "application/json");
+ using var requestMessage = new HttpRequestMessage(options.HttpMethod, builder.Uri);
+ requestMessage.Content = new StringContent(
+ JsonConvert.SerializeObject(options.Body),
+ Encoding.UTF8,
+ "application/json"
+ );
foreach (var kvp in options.Headers)
{
requestMessage.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value);
}
-
+
if (_httpClient.Timeout != options.HttpTimeout)
{
_httpClient = new HttpClient();
_httpClient.Timeout = options.HttpTimeout;
}
-
+
var response = await _httpClient.SendAsync(requestMessage);
if (response.IsSuccessStatusCode && !response.Headers.Contains("x-relay-error"))
@@ -143,10 +164,10 @@ private async Task HandleRequest(string url, string? token
{
Content = content,
Response = response,
- StatusCode = (int)response.StatusCode
+ StatusCode = (int)response.StatusCode,
};
exception.AddReason();
throw exception;
}
}
-}
\ No newline at end of file
+}
diff --git a/Functions/Functions.csproj b/Functions/Functions.csproj
index eec4ade..4108d70 100644
--- a/Functions/Functions.csproj
+++ b/Functions/Functions.csproj
@@ -16,8 +16,10 @@
https://avatars.githubusercontent.com/u/54469796?s=200&v=4
https://github.com/supabase-community/functions-csharp
supabase, functions
+
2.0.0
2.0.0
+
true
icon.png
README.md
@@ -31,7 +33,7 @@
- 2.0.0
+ 2.0.0
$(VersionPrefix)-$(VersionSuffix)
$(VersionPrefix)
@@ -46,4 +48,4 @@
-
\ No newline at end of file
+
diff --git a/Functions/InvokeFunctionOptions.cs b/Functions/InvokeFunctionOptions.cs
index b4252f4..6958621 100644
--- a/Functions/InvokeFunctionOptions.cs
+++ b/Functions/InvokeFunctionOptions.cs
@@ -1,6 +1,7 @@
using System;
-using Newtonsoft.Json;
using System.Collections.Generic;
+using System.Net.Http;
+using Newtonsoft.Json;
namespace Supabase.Functions
{
@@ -8,7 +9,7 @@ public partial class Client
{
///
/// Options that can be supplied to a function invocation.
- ///
+ ///
/// Note: If Headers.Authorization is set, it can be later overriden if a token is supplied in the method call.
///
public class InvokeFunctionOptions
@@ -16,8 +17,8 @@ public class InvokeFunctionOptions
///
/// Headers to be included on the request.
///
- public Dictionary Headers { get; set; } = new Dictionary();
-
+ public Dictionary Headers { get; set; } =
+ new Dictionary();
///
/// Body of the Request
@@ -30,6 +31,160 @@ public class InvokeFunctionOptions
/// https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=net-8.0#remarks
///
public TimeSpan HttpTimeout { get; set; } = TimeSpan.FromSeconds(100);
+
+ ///
+ /// Http method of the Request
+ ///
+ public HttpMethod HttpMethod { get; set; } = HttpMethod.Post;
+
+ ///
+ /// Region of the request
+ ///
+ public FunctionRegion FunctionRegion { get; set; } = FunctionRegion.Any;
+ }
+
+ ///
+ /// Define the region for requests
+ ///
+ public class FunctionRegion : IEquatable
+ {
+ private string _region;
+
+ ///
+ /// Empty region
+ ///
+ public static FunctionRegion Any { get; } = new FunctionRegion("any");
+
+ ///
+ /// Represents the region "ap-northeast-1" for function requests.
+ ///
+ public static FunctionRegion ApNortheast1 { get; } =
+ new FunctionRegion("ap-northeast-1");
+
+ ///
+ /// Represents the "ap-northeast-2" region for function invocation.
+ ///
+ public static FunctionRegion ApNortheast2 { get; } =
+ new FunctionRegion("ap-northeast-2");
+
+ ///
+ /// Represents the "ap-south-1" region used for requests.
+ ///
+ public static FunctionRegion ApSouth1 { get; } = new FunctionRegion("ap-south-1");
+
+ ///
+ /// Represents the region "ap-southeast-1" for function invocation.
+ ///
+ public static FunctionRegion ApSoutheast1 { get; } =
+ new FunctionRegion("ap-southeast-1");
+
+ ///
+ /// Represents the "ap-southeast-2" region for requests.
+ ///
+ public static FunctionRegion ApSoutheast2 { get; } =
+ new FunctionRegion("ap-southeast-2");
+
+ ///
+ /// Represents the Canada (Central) region for requests.
+ ///
+ public static FunctionRegion CaCentral1 { get; } = new FunctionRegion("ca-central-1");
+
+ ///
+ /// Represents the "eu-central-1" region for function invocation.
+ ///
+ public static FunctionRegion EuCentral1 { get; } = new FunctionRegion("eu-central-1");
+
+ ///
+ /// Represents the "eu-west-1" function region for requests.
+ ///
+ public static FunctionRegion EuWest1 { get; } = new FunctionRegion("eu-west-1");
+
+ ///
+ /// Represents the "eu-west-2" region for function invocation requests.
+ ///
+ public static FunctionRegion EuWest2 { get; } = new FunctionRegion("eu-west-2");
+
+ ///
+ /// Represents the AWS region 'eu-west-3'.
+ ///
+ public static FunctionRegion EuWest3 { get; } = new FunctionRegion("eu-west-3");
+
+ ///
+ /// Represents the South America (São Paulo) region for requests.
+ ///
+ public static FunctionRegion SaEast1 { get; } = new FunctionRegion("sa-east-1");
+
+ ///
+ /// Represents the "us-east-1" region for function requests.
+ ///
+ public static FunctionRegion UsEast1 { get; } = new FunctionRegion("us-east-1");
+
+ ///
+ /// Represents the us-west-1 region for function requests.
+ ///
+ public static FunctionRegion UsWest1 { get; } = new FunctionRegion("us-west-1");
+
+ ///
+ /// Represents the "us-west-2" region for requests.
+ ///
+ public static FunctionRegion UsWest2 { get; } = new FunctionRegion("us-west-2");
+
+ ///
+ /// Define the region for requests
+ ///
+ public FunctionRegion(string region)
+ {
+ _region = region;
+ }
+
+ ///
+ /// Check if the object is identical to the reference passed
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is FunctionRegion r && Equals(r);
+ }
+
+ ///
+ /// Generate Hash code
+ ///
+ public override int GetHashCode()
+ {
+ return _region.GetHashCode();
+ }
+
+ ///
+ /// Check if the object is identical to the reference passed
+ ///
+ public bool Equals(FunctionRegion other)
+ {
+ return _region == other._region;
+ }
+
+ ///
+ /// Overloading the operator ==
+ ///
+ public static bool operator ==(FunctionRegion left, FunctionRegion right) =>
+ Equals(left, right);
+
+ ///
+ /// Overloading the operator !=
+ ///
+ public static bool operator !=(FunctionRegion left, FunctionRegion right) =>
+ !Equals(left, right);
+
+ ///
+ /// Overloads the explicit cast operator to convert a FunctionRegion object to a string.
+ ///
+ public static explicit operator string(FunctionRegion region) => region.ToString();
+
+ ///
+ /// Overloads the explicit cast operator to convert a string to a FunctionRegion object.
+ ///
+ public static explicit operator FunctionRegion(string region) =>
+ new FunctionRegion(region);
+
+ public override string ToString() => _region;
}
}
-}
\ No newline at end of file
+}
diff --git a/FunctionsTests/ClientTests.cs b/FunctionsTests/ClientTests.cs
index 06c4a8a..78bbbcd 100644
--- a/FunctionsTests/ClientTests.cs
+++ b/FunctionsTests/ClientTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
+using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
@@ -19,8 +20,8 @@ public class ClientTests
[TestInitialize]
public void Initialize()
{
- _token = GenerateToken("37c304f8-51aa-419a-a1af-06154e63707a");
- _client = new Client("http://localhost:9000");
+ _token = GenerateToken("super-secret-jwt-token-with-at-least-32-characters-long");
+ _client = new Client("http://localhost:54321/functions/v1");
}
[TestMethod("Invokes a function.")]
@@ -28,41 +29,57 @@ public async Task Invokes()
{
const string function = "hello";
- var result = await _client.Invoke(function, _token, new InvokeFunctionOptions
- {
- Body = new Dictionary
+ var result = await _client.Invoke(
+ function,
+ _token,
+ new InvokeFunctionOptions
{
- {"name", "supabase" }
+ Body = new Dictionary { { "name", "supabase" } },
+ HttpMethod = HttpMethod.Post,
}
- });
+ );
Assert.IsTrue(result.Contains("supabase"));
-
- var result2 = await _client.Invoke>(function, _token, new InvokeFunctionOptions
- {
- Body = new Dictionary
+ var result2 = await _client.Invoke>(
+ function,
+ _token,
+ new InvokeFunctionOptions
{
- { "name", "functions" }
+ Body = new Dictionary { { "name", "functions" } },
+ HttpMethod = HttpMethod.Post,
}
- });
+ );
Assert.IsInstanceOfType(result2, typeof(Dictionary));
Assert.IsTrue(result2.ContainsKey("message"));
Assert.IsTrue(result2["message"].Contains("functions"));
-
- var result3 = await _client.RawInvoke(function, _token, new InvokeFunctionOptions
- {
- Body = new Dictionary
+ var result3 = await _client.RawInvoke(
+ function,
+ _token,
+ new InvokeFunctionOptions
{
- { "name", "functions" }
+ Body = new Dictionary { { "name", "functions" } },
+ HttpMethod = HttpMethod.Post,
}
- });
+ );
var bytes = await result3.ReadAsByteArrayAsync();
Assert.IsInstanceOfType(bytes, typeof(byte[]));
+
+ var result4 = await _client.Invoke(
+ function,
+ _token,
+ new InvokeFunctionOptions
+ {
+ Body = [],
+ HttpMethod = HttpMethod.Get,
+ }
+ );
+
+ Assert.IsTrue(result4.Contains(function));
}
private static string GenerateToken(string secret)
@@ -71,7 +88,10 @@ private static string GenerateToken(string secret)
var tokenDescriptor = new SecurityTokenDescriptor
{
- SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature)
+ SigningCredentials = new SigningCredentials(
+ signingKey,
+ SecurityAlgorithms.HmacSha256Signature
+ ),
};
var tokenHandler = new JwtSecurityTokenHandler();
@@ -79,4 +99,4 @@ private static string GenerateToken(string secret)
return tokenHandler.WriteToken(securityToken);
}
}
-}
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index ce7bf1d..27995c4 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,7 @@
-
-
-
-
-
-
-
-
-
-
+# Supabase.Functions
+
+[](https://github.com/supabase-community/functions-csharp/actions/workflows/build-and-test.yml)
+[](https://www.nuget.com/packages/Supabase.Functions/)
---
diff --git a/Supabase.Functions.sln b/Supabase.Functions.sln
index 2bdd908..1710bd7 100644
--- a/Supabase.Functions.sln
+++ b/Supabase.Functions.sln
@@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml
.github\workflows\build-documentation.yaml = .github\workflows\build-documentation.yaml
.github\workflows\release.yml = .github\workflows\release.yml
+ .github\workflows\release-please.yml = .github\workflows\release-please.yml
EndProjectSection
EndProject
Global
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 0000000..d93bd7d
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,16 @@
+{
+ "packages": {
+ ".": {
+ "changelog-path": "CHANGELOG.md",
+ "bump-minor-pre-major": false,
+ "bump-patch-for-minor-pre-major": false,
+ "draft": false,
+ "prerelease": false,
+ "release-type": "simple",
+ "extra-files": [
+ "Functions/Functions.csproj"
+ ]
+ }
+ },
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
+}
diff --git a/supabase/functions/hello/index.ts b/supabase/functions/hello/index.ts
index 820653e..09b19bb 100644
--- a/supabase/functions/hello/index.ts
+++ b/supabase/functions/hello/index.ts
@@ -6,12 +6,19 @@ import { serve } from "https://deno.land/std@0.131.0/http/server.ts"
console.log("Hello from Functions!")
-serve(async (req) => {
- const { name } = await req.json()
+serve(async (req: Request) => {
+ let value = req.url.substring(req.url.lastIndexOf("/") + 1)
+ if (req.body != null) {
+ const { name } = await req.json()
+ value = name
+ }
+
const data = {
- message: `Hello ${name}!`,
+ message: `Hello ${value}!`,
}
+ console.log("response", JSON.stringify(data))
+
return new Response(
JSON.stringify(data),
{ headers: { "Content-Type": "application/json" } },