Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,8 @@ TestResults/
*.dylib
*.dll
*.so

.env

# Ignore user id file
user_id.txt
25 changes: 25 additions & 0 deletions demos/CommandLine/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# PowerSync server URL
POWERSYNC_URL=http://localhost:8080

# Set to true if you want to use Supabase as the backend
# Set to false if you want to use the PowerSync self-hosted backend
USE_SUPABASE=false

# --- Supabase Connector Settings ---
# These values are used only if USE_SUPABASE=true

# Supabase project URL
SUPABASE_URL=http://localhost:54321

# Supabase anon key (public client access)
SUPABASE_ANON_KEY=your_anon_key_here

# Supabase credentials for an already existing user (used for login)
[email protected]
SUPABASE_PASSWORD=your_supabase_password

# --- PowerSync Backend Settings ---
# These values are used only if USE_SUPABASE=false

# URL of your PowerSync self-hosted backend
BACKEND_URL=http://localhost:6060
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
32 changes: 28 additions & 4 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,31 @@ 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);

// Ensure this user already exists
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 @@ -60,7 +85,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 Expand Up @@ -88,7 +113,6 @@ static async Task Main()
}
});


// Start live updating table
await AnsiConsole.Live(panel)
.StartAsync(async ctx =>
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);
}
}
27 changes: 27 additions & 0 deletions demos/CommandLine/Models/Supabase/List.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

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

namespace CommandLine.Models.Supabase;

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
[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; }
}
44 changes: 44 additions & 0 deletions demos/CommandLine/Models/Supabase/Todos.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

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

namespace CommandLine.Models.Supabase;

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
[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
34 changes: 32 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,36 @@ 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.

> **Note for Supabase users:**
> If you are using `USE_SUPABASE=true`, this demo expects a valid, **already existing Supabase user**.
> You must provide their credentials via the `.env` file using `SUPABASE_USERNAME` and `SUPABASE_PASSWORD`.

## 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 +59,4 @@ To run the Command-Line interface:

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