mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 03:05:42 +00:00
Connect the frontend with the api and powersync service
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
VITE_BACKEND_URL = "http://localhost:6060"
|
||||
VITE_POWERSYNC_URL = "http://localhost:8080"
|
||||
VITE_CHECKPOINT_MODE = "managed"
|
||||
+5
-1
@@ -13,6 +13,8 @@
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@heroui/react": "^2.7.6",
|
||||
"@powersync/react": "^1.5.3",
|
||||
"@powersync/web": "^1.20.0",
|
||||
"@radix-ui/react-tooltip": "^1.2.3",
|
||||
"@react-aria/visually-hidden": "3.8.21",
|
||||
"@react-types/shared": "3.28.0",
|
||||
@@ -26,7 +28,8 @@
|
||||
"react-router-dom": "6.23.0",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwind-variants": "0.3.0",
|
||||
"tailwindcss": "3.4.16"
|
||||
"tailwindcss": "3.4.16",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.5.7",
|
||||
@@ -49,6 +52,7 @@
|
||||
"prettier": "3.3.3",
|
||||
"typescript": "5.6.3",
|
||||
"vite": "^5.2.0",
|
||||
"vite-plugin-top-level-await": "^1.5.0",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
+9
-6
@@ -3,15 +3,18 @@ import "@/styles/globals.css"
|
||||
import { TooltipProvider } from "./components/tooltip/tooltip";
|
||||
import { ReactFlowProvider } from "@xyflow/react";
|
||||
import useAppRoutes from "./routes/app-route";
|
||||
import { SyncProvider } from "./providers/sync-provider";
|
||||
|
||||
function App() {
|
||||
const appRoutes = useAppRoutes() ;
|
||||
const appRoutes = useAppRoutes();
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
{appRoutes}
|
||||
</TooltipProvider>
|
||||
</ReactFlowProvider>
|
||||
<SyncProvider>
|
||||
<ReactFlowProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
{appRoutes}
|
||||
</TooltipProvider>
|
||||
</ReactFlowProvider>
|
||||
</SyncProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { column, Schema, Table } from '@powersync/web';
|
||||
|
||||
export const DATATYPE_TABLE = 'dataTypes';
|
||||
|
||||
|
||||
const data_types = new Table(
|
||||
{
|
||||
name: column.text,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
|
||||
export const AppSchema = new Schema({
|
||||
data_types,
|
||||
|
||||
});
|
||||
|
||||
export type Database = (typeof AppSchema)['types'];
|
||||
export type DataTypesRecord = Database['data_types'];
|
||||
// OR:
|
||||
// export type Todo = RowType<typeof todos>;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import Table from "./table/table";
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { Relationship } from "./table/relationship";
|
||||
import DBController from "./db-controller/db-controller";
|
||||
import { useQuery } from "@powersync/react";
|
||||
|
||||
interface DatabaseProps {
|
||||
initialTables?: TableType
|
||||
@@ -172,6 +173,10 @@ const DatabasePage: React.FC<DatabaseProps> = ({ initialTables }) => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const { data , isLoading , error , isFetching } = useQuery('SELECT * FROM data_types ;' );
|
||||
|
||||
|
||||
console.log (data)
|
||||
|
||||
const nodeTypes = useMemo(() => ({ table: Table }), []);
|
||||
const onConnect = useCallback((params: any) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
import { PowerSyncDatabase } from '@powersync/web';
|
||||
import { PowerSyncContext } from "@powersync/react";
|
||||
import { createContext, Suspense, useContext, useEffect, useState } from 'react';
|
||||
import { AppSchema } from '@/lib/schemas/app-schema';
|
||||
import { StackRenderConnector } from '@/utils/stackrender-connector';
|
||||
import { CircularProgress } from '@heroui/react';
|
||||
|
||||
export const db = new PowerSyncDatabase({
|
||||
database: {
|
||||
dbFilename: 'stackrender'
|
||||
},
|
||||
schema: AppSchema,
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
const ConnectorContext = createContext<StackRenderConnector | null>(null);
|
||||
export const useConnector = () => useContext(ConnectorContext);
|
||||
|
||||
|
||||
interface SyncProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SyncProvider: React.FC<SyncProviderProps> = ({ children }) => {
|
||||
|
||||
const [powerSync] = useState(db);
|
||||
const [connector] = useState(new StackRenderConnector());
|
||||
useEffect(() => {
|
||||
|
||||
powerSync.init();
|
||||
powerSync.connect(connector);
|
||||
}, [powerSync, connector])
|
||||
return (
|
||||
<Suspense fallback={<CircularProgress/>}>
|
||||
<PowerSyncContext.Provider value={powerSync}>
|
||||
<ConnectorContext.Provider value={connector}>
|
||||
{children}
|
||||
</ConnectorContext.Provider>
|
||||
</PowerSyncContext.Provider>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { AbstractPowerSyncDatabase, PowerSyncBackendConnector } from '@powersync/web';
|
||||
|
||||
export type DemoConfig = {
|
||||
backendUrl: string;
|
||||
powersyncUrl: string;
|
||||
};
|
||||
|
||||
enum CheckpointMode {
|
||||
CUSTOM = 'custom',
|
||||
MANAGED = 'managed'
|
||||
}
|
||||
|
||||
const USER_ID_STORAGE_KEY = 'ps_user_id';
|
||||
|
||||
export class StackRenderConnector implements PowerSyncBackendConnector {
|
||||
readonly config: DemoConfig;
|
||||
readonly userId: string;
|
||||
|
||||
private _clientId: string | null;
|
||||
|
||||
constructor() {
|
||||
let userId = localStorage.getItem(USER_ID_STORAGE_KEY);
|
||||
if (!userId) {
|
||||
userId = uuid();
|
||||
localStorage.setItem(USER_ID_STORAGE_KEY, userId);
|
||||
}
|
||||
this.userId = userId;
|
||||
this._clientId = null;
|
||||
|
||||
this.config = {
|
||||
backendUrl: import.meta.env.VITE_BACKEND_URL as string,
|
||||
powersyncUrl: import.meta.env.VITE_POWERSYNC_URL as string
|
||||
};
|
||||
}
|
||||
|
||||
async fetchCredentials() {
|
||||
const tokenEndpoint = 'api/auth/token';
|
||||
const res = await fetch(`${this.config.backendUrl}/${tokenEndpoint}?user_id=${this.userId}`);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Received ${res.status} from ${tokenEndpoint}: ${await res.text()}`);
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
|
||||
return {
|
||||
endpoint: this.config.powersyncUrl,
|
||||
token: body.token
|
||||
};
|
||||
}
|
||||
|
||||
async uploadData(database: AbstractPowerSyncDatabase): Promise<void> {
|
||||
|
||||
const transaction = await database.getNextCrudTransaction();
|
||||
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._clientId) {
|
||||
this._clientId = await database.getClientId();
|
||||
}
|
||||
|
||||
try {
|
||||
let batch: any[] = [];
|
||||
for (let operation of transaction.crud) {
|
||||
let payload = {
|
||||
op: operation.op,
|
||||
table: operation.table,
|
||||
id: operation.id,
|
||||
data: operation.opData
|
||||
};
|
||||
batch.push(payload);
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.config.backendUrl}/api/data`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ batch })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Received ${response.status} from /api/data: ${await response.text()}`);
|
||||
}
|
||||
|
||||
await transaction.complete(
|
||||
import.meta.env.VITE_CHECKPOINT_MODE == CheckpointMode.CUSTOM
|
||||
? await this.getCheckpoint(this._clientId)
|
||||
: undefined
|
||||
);
|
||||
} catch (ex: any) {
|
||||
console.debug(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a custom Write Checkpoint from the backend. This is only used
|
||||
* when custom Write Checkpoints are enabled during build.
|
||||
*/
|
||||
async getCheckpoint(client_id: string) {
|
||||
|
||||
const r = await fetch(`${this.config.backendUrl}/api/data/checkpoint`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: this.userId,
|
||||
client_id: client_id
|
||||
})
|
||||
});
|
||||
const j = await r.json();
|
||||
return j.checkpoint as string;
|
||||
}
|
||||
}
|
||||
+12
-2
@@ -1,11 +1,21 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tsconfigPaths()],
|
||||
plugins: [react(), tsconfigPaths() , topLevelAwait()],
|
||||
server : {
|
||||
port : 3000
|
||||
} ,
|
||||
optimizeDeps: {
|
||||
// Don't optimize these packages as they contain web workers and WASM files.
|
||||
// https://github.com/vitejs/vite/issues/11672#issuecomment-1415820673
|
||||
exclude: ['@journeyapps/wa-sqlite', '@powersync/web'],
|
||||
include: ['@powersync/web > js-logger']
|
||||
},
|
||||
worker: {
|
||||
format: 'es',
|
||||
plugins: () => [ topLevelAwait()]
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user