Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ TestResults/
*.dylib
*.dll
*.so

.env
9 changes: 9 additions & 0 deletions demos/CommandLine/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
SUPABASE_URL=your-supabase-url
SUPABASE_ANON_KEY=your_anon_key_here
POWERSYNC_URL=your-powersync-url
BACKEND_URL=your-backend-url
SUPABASE_USERNAME=your-supabase-username
SUPABASE_PASSWORD=your-supabase-password
# Set to true if you want to use Supabase as the backend
# Set to false if you want to use the Powersync backend
USE_SUPABASE=false
2 changes: 2 additions & 0 deletions demos/CommandLine/CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dotenv.Net" Version="3.2.1" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.1" />
<PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="supabase" Version="1.1.1" />

</ItemGroup>

Expand Down
24 changes: 24 additions & 0 deletions demos/CommandLine/CommandLine.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLine", "CommandLine.csproj", "{6BB9F16E-3825-DE76-1286-9E5E2406710D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6BB9F16E-3825-DE76-1286-9E5E2406710D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BB9F16E-3825-DE76-1286-9E5E2406710D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BB9F16E-3825-DE76-1286-9E5E2406710D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BB9F16E-3825-DE76-1286-9E5E2406710D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A5588511-5909-4F05-80EB-09A56805607C}
EndGlobalSection
EndGlobal
36 changes: 33 additions & 3 deletions demos/CommandLine/Demo.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
namespace CommandLine;

using CommandLine.Utils;
using PowerSync.Common.Client;
using PowerSync.Common.Client.Connection;
using Spectre.Console;

class Demo
{

private record ListResult(string id, string name, string owner_id, string created_at);
static async Task Main()
{
Expand All @@ -16,7 +17,30 @@ static async Task Main()
});
await db.Init();

var connector = new NodeConnector();
var config = new Config();

IPowerSyncBackendConnector connector;

string connectorUserId = "";

if (config.UseSupabase)
{
var supabaseConnector = new SupabaseConnector(config);

await supabaseConnector.Login(config.SupabaseUsername, config.SupabasePassword);

connectorUserId = supabaseConnector.UserId;

connector = supabaseConnector;
}
else
{
var nodeConnector = new NodeConnector(config);

connectorUserId = nodeConnector.UserId;

connector = nodeConnector;
}

var table = new Table()
.AddColumn("id")
Expand Down Expand Up @@ -47,6 +71,12 @@ static async Task Main()
}
});

// await db.Execute("insert into lists (id, name, owner_id, created_at) values (uuid(), 'New User33', ?, datetime())", [connector.UserId]);

await db.Execute(
"UPDATE lists SET name = ?, created_at = datetime() WHERE owner_id = ? and id = ?", ["update CHCHCHCHCH", connectorUserId, "0bf55412-d35b-4814-ade9-daea4865df96"]
);

var _ = Task.Run(async () =>
{
while (running)
Expand All @@ -60,7 +90,7 @@ static async Task Main()
}
else if (key.Key == ConsoleKey.Enter)
{
await db.Execute("insert into lists (id, name, owner_id, created_at) values (uuid(), 'New User', ?, datetime())", [connector.UserId]);
await db.Execute("insert into lists (id, name, owner_id, created_at) values (uuid(), 'New User', ?, datetime())", [connectorUserId]);
}
else if (key.Key == ConsoleKey.Backspace)
{
Expand Down
38 changes: 38 additions & 0 deletions demos/CommandLine/Helpers/SupabasePatchHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace CommandLine.Helpers;

using System.Linq.Expressions;
using Newtonsoft.Json;
using Supabase.Postgrest.Interfaces;
using Supabase.Postgrest.Models;

public static class SupabasePatchHelper
{
// Applies a "SET" operation to the table, setting the value of a specific property.
public static IPostgrestTable<T> ApplySet<T>(
IPostgrestTable<T> table, // The table to apply the operation to
string jsonPropertyName, // The name of the JSON property to update
object value // The new value to set for the property
) where T : BaseModel, new() // Ensures T is a subclass of BaseModel with a parameterless constructor
{
// Find the property on the model that matches the JSON property name
var property = typeof(T)
.GetProperties() // Get all properties of the model type
.FirstOrDefault(p =>
// Check if the property has a JsonPropertyAttribute
p.GetCustomAttributes(typeof(JsonPropertyAttribute), true)
.FirstOrDefault() is JsonPropertyAttribute attr &&
attr.PropertyName == jsonPropertyName); // Check if the JSON property name matches

if (property == null)
throw new ArgumentException($"'{jsonPropertyName}' is not a valid property on type '{typeof(T).Name}'");

// Create an expression to access the specified property on the model
var parameter = Expression.Parameter(typeof(T), "x"); // Define a parameter for the expression
var propertyAccess = Expression.Property(parameter, property.Name); // Access the property
var converted = Expression.Convert(propertyAccess, typeof(object)); // Convert the value to object type
var lambda = Expression.Lambda<Func<T, object>>(converted, parameter); // Create a lambda expression for the property

// Apply the "SET" operation to the table using the lambda expression
return table.Set(lambda, value);
}
}
25 changes: 25 additions & 0 deletions demos/CommandLine/Models/List.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace CommandLine.Models;

using Newtonsoft.Json;
using Supabase.Postgrest.Attributes;
using Supabase.Postgrest.Models;

[Table("lists")]
class List : BaseModel
{
[PrimaryKey("id")]
[JsonProperty("id")]
public string Id { get; set; }

[Column("created_at")]
[JsonProperty("created_at")]
public string CreatedAt { get; set; }

[Column("name")]
[JsonProperty("name")]
public string Name { get; set; }

[Column("owner_id")]
[JsonProperty("owner_id")]
public string OwnerId { get; set; }
}
41 changes: 41 additions & 0 deletions demos/CommandLine/Models/Todos.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace CommandLine.Models;

using Newtonsoft.Json;
using Supabase.Postgrest.Attributes;
using Supabase.Postgrest.Models;

[Table("todos")]
class Todo : BaseModel
{
[PrimaryKey("id")]
[JsonProperty("id")]
public string Id { get; set; }

[Column("list_id")]
[JsonProperty("list_id")]
public string ListId { get; set; }

[Column("created_at")]
[JsonProperty("created_at")]
public string CreatedAt { get; set; }

[Column("completed_at")]
[JsonProperty("completed_at")]
public string CompletedAt { get; set; }

[Column("description")]
[JsonProperty("description")]
public string Description { get; set; }

[Column("created_by")]
[JsonProperty("created_by")]
public string CreatedBy { get; set; }

[Column("completed_by")]
[JsonProperty("completed_by")]
public string CompletedBy { get; set; }

[Column("completed")]
[JsonProperty("completed")]
public int Completed { get; set; }
}
8 changes: 4 additions & 4 deletions demos/CommandLine/NodeConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
using PowerSync.Common.Client;
using PowerSync.Common.Client.Connection;
using PowerSync.Common.DB.Crud;

using CommandLine.Utils;

public class NodeConnector : IPowerSyncBackendConnector
{
Expand All @@ -22,15 +22,15 @@ public class NodeConnector : IPowerSyncBackendConnector
public string UserId { get; private set; }
private string? clientId;

public NodeConnector()
public NodeConnector(Config config)
{
_httpClient = new HttpClient();

// Load or generate User ID
UserId = LoadOrGenerateUserId();

BackendUrl = "http://localhost:6060";
PowerSyncUrl = "http://localhost:8080";
BackendUrl = config.BackendUrl;
PowerSyncUrl = config.PowerSyncUrl;

clientId = null;
}
Expand Down
30 changes: 28 additions & 2 deletions demos/CommandLine/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PowerSync CLI demo app
# PowerSync CLI Demo App

This demo features a CLI-based table view that stays *live* using a *watch query*, ensuring the data updates in real time as changes occur.
To run this demo, you need to have one of our Node.js self-host demos ([Postgres](https://github.com/powersync-ja/self-host-demo/tree/main/demos/nodejs) | [MongoDB](https://github.com/powersync-ja/self-host-demo/tree/main/demos/nodejs-mongodb) | [MySQL](https://github.com/powersync-ja/self-host-demo/tree/main/demos/nodejs-mysql)) running, as it provides the PowerSync server that this CLI's PowerSync SDK connects to.
Expand All @@ -9,6 +9,32 @@ Changes made to the backend's source DB or to the self-hosted web UI will be syn

This essentially uses anonymous authentication. A random user ID is generated and stored in local storage. The backend returns a valid token which is not linked to a specific user. All data is synced to all users.

## Connection Options

By default, this demo uses the NodeConnector for connecting to the PowerSync server. However, you can swap this out with the SupabaseConnector if needed

1. Copy the `.env.template` file to a new `.env` file:
```bash
# On Linux/macOS
cp .env.template .env

# On Windows
copy .env.template .env
```

2. Replace the necessary fields in the `.env` file with your Supabase and PowerSync credentials:
```
SUPABASE_URL=your-supabase-url
SUPABASE_ANON_KEY=your_anon_key_here
POWERSYNC_URL=your-powersync-url
BACKEND_URL=your-backend-url
SUPABASE_USERNAME=your-supabase-username
SUPABASE_PASSWORD=your-supabase-password
# Set to true if you want to use Supabase as the backend
# Set to false if you want to use the Powersync backend
USE_SUPABASE=false
```

## Getting Started

In the repo root, run the following to download the PowerSync extension:
Expand All @@ -29,4 +55,4 @@ To run the Command-Line interface:

```bash
dotnet run Demo
```
```
Loading