Skip to content
Open
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
27 changes: 19 additions & 8 deletions examples/tanstack-db-expo-starter/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ app.get(`/api/health`, (_req, res) => {
res.status(200).json({ status: `ok` })
})

// Generate a transaction ID
// Generate a transaction ID for Electric's optimistic write handshake.
async function generateTxId(tx: any): Promise<number> {
// This is specific to postgres and how electricsql works
const [{ txid }] = await tx.execute(sql`SELECT txid_current() as txid`)
const [{ txid }] = await tx.execute(
sql`SELECT pg_current_xact_id()::xid::text AS txid`
)
return Number(txid)
}

Expand All @@ -36,8 +37,8 @@ app.post(`/api/todos`, async (req, res) => {
const todoData = validateInsertTodo(req.body)

const result = await db.transaction(async (tx) => {
const txid = await generateTxId(tx)
const [newTodo] = await tx.insert(todos).values(todoData).returning()
const txid = await generateTxId(tx)
return { todo: newTodo, txid }
})

Expand All @@ -58,7 +59,6 @@ app.put(`/api/todos/:id`, async (req, res) => {
const todoData = validateUpdateTodo(req.body)

const result = await db.transaction(async (tx) => {
const txid = await generateTxId(tx)
const [updatedTodo] = await tx
.update(todos)
.set({ ...todoData, updated_at: new Date() })
Expand All @@ -68,6 +68,8 @@ app.put(`/api/todos/:id`, async (req, res) => {
if (!updatedTodo) {
throw new Error(`Todo not found`)
}

const txid = await generateTxId(tx)
return { todo: updatedTodo, txid }
})

Expand All @@ -91,7 +93,6 @@ app.delete(`/api/todos/:id`, async (req, res) => {
const { id } = req.params

const result = await db.transaction(async (tx) => {
const txid = await generateTxId(tx)
const [deleted] = await tx
.delete(todos)
.where(eq(todos.id, Number(id)))
Expand All @@ -100,6 +101,8 @@ app.delete(`/api/todos/:id`, async (req, res) => {
if (!deleted) {
throw new Error(`Todo not found`)
}

const txid = await generateTxId(tx)
return { success: true, txid }
})

Expand Down Expand Up @@ -145,7 +148,7 @@ app.get(`/api/todos`, async (req, res) => {
}

// Copy headers, excluding problematic ones
const headers = {}
const headers: Record<string, string> = {}
response.headers.forEach((value, key) => {
if (
key.toLowerCase() !== `content-encoding` &&
Expand All @@ -155,11 +158,19 @@ app.get(`/api/todos`, async (req, res) => {
}
})

if (!response.body) {
res.writeHead(502, { 'Content-Type': `application/json` })
res.end(JSON.stringify({ error: `Electric response body is empty` }))
return
}

// Set status and headers
res.writeHead(response.status, response.statusText, headers)

// Convert Web Streams to Node.js stream and pipe
const nodeStream = Readable.fromWeb(response.body)
const nodeStream = Readable.fromWeb(
response.body as unknown as Parameters<typeof Readable.fromWeb>[0]
)

// Handle stream errors gracefully
nodeStream.on(`error`, (err) => {
Expand Down
9 changes: 1 addition & 8 deletions examples/tanstack-db-expo-starter/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,14 @@
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true
}
},
"web": {
"favicon": "./assets/favicon.png"
Expand Down
6 changes: 3 additions & 3 deletions examples/tanstack-db-expo-starter/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const todoCollection = createCollection(
transaction.mutations[0].modified
)

return { txid: String(txid) }
return { txid }
},
onUpdate: async ({ transaction }) => {
const {
Expand All @@ -46,13 +46,13 @@ const todoCollection = createCollection(
} = transaction.mutations[0]
const { txid } = await apiClient.updateTodo(id, changes)

return { txid: String(txid) }
return { txid }
},
onDelete: async ({ transaction }) => {
const { id } = transaction.mutations[0].original
const { txid } = await apiClient.deleteTodo(id)

return { txid: String(txid) }
return { txid }
},
getKey: (item) => item.id,
})
Expand Down
86 changes: 45 additions & 41 deletions examples/tanstack-db-expo-starter/package.json
Original file line number Diff line number Diff line change
@@ -1,51 +1,55 @@
{
"name": "expo-db-electric-starter",
"version": "1.0.22",
"dependencies": {
"@electric-sql/client": "^1.5.21",
"@expo/metro-runtime": "56.0.15",
"@tanstack/electric-db-collection": "^0.3.6",
"@tanstack/react-db": "^0.1.86",
"cors": "^2.8.6",
"date-fns": "^4.4.0",
"drizzle-orm": "^0.45.2",
"drizzle-zod": "^0.8.3",
"expo": "56.0.12",
"expo-constants": "56.0.18",
"expo-linking": "56.0.14",
"expo-router": "56.2.11",
"expo-status-bar": "56.0.4",
"express": "^5.2.1",
"pg": "^8.21.0",
"postgres": "^3.4.9",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.85.3",
"react-native-random-uuid": "^0.1.4",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
"react-native-web": "0.21.2",
"react-native-worklets": "0.8.3",
"zod": "^4.4.3"
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/pg": "^8.20.0",
"@types/react": "^19.2.17",
"concurrently": "^10.0.3",
"dotenv": "^17.4.2",
"drizzle-kit": "^0.31.10",
"tsx": "^4.22.4",
"typescript": "~6.0.3"
},
"main": "index.ts",
"private": true,
"scripts": {
"start": "docker compose up -d && expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"api": "tsx api/index.ts",
"db:generate": "drizzle-kit generate",
"db:push": "tsx src/db/migrate.ts",
"db:studio": "drizzle-kit studio",
"api": "tsx api/index.ts"
},
"dependencies": {
"@electric-sql/client": "1.5.21",
"@expo/metro-runtime": "~5.0.4",
"@tanstack/electric-db-collection": "^0.0.15",
"@tanstack/react-db": "^0.0.27",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"cors": "^2.8.5",
"date-fns": "^4.1.0",
"drizzle-orm": "^0.44.3",
"drizzle-zod": "^0.8.2",
"expo": "53.0.20",
"expo-constants": "^17.1.7",
"expo-router": "~5.1.4",
"expo-status-bar": "~2.2.0",
"express": "^5.1.0",
"pg": "^8.16.3",
"postgres": "^3.4.7",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.80.1",
"react-native-random-uuid": "^0.1.4",
"react-native-web": "^0.20.0",
"zod": "^4.0.5"
},
"devDependencies": {
"@babel/core": "^7.28.0",
"@types/pg": "^8.15.4",
"@types/react": "~19.1.8",
"concurrently": "^9.2.0",
"dotenv": "^16.5.0",
"drizzle-kit": "^0.31.4",
"tsx": "^4.20.3",
"typescript": "~5.8.3"
},
"private": true
"ios": "expo start --ios",
"start": "docker compose up -d && expo start",
"web": "expo start --web"
}
}
127 changes: 63 additions & 64 deletions examples/tanstack-db-web-starter/package.json
Original file line number Diff line number Diff line change
@@ -1,79 +1,78 @@
{
"name": "tanstack-start-db-electric-starter",
"private": true,
"version": "0.1.0",
"type": "module",
"dependencies": {
"@electric-sql/client": "^1.5.21",
"@tailwindcss/vite": "^4.3.1",
"@tanstack/electric-db-collection": "^0.3.6",
"@tanstack/react-db": "^0.1.86",
"@tanstack/react-router": "^1.170.16",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-start": "^1.168.26",
"@tanstack/router-plugin": "^1.168.18",
"@trpc/client": "^11.18.0",
"@trpc/server": "^11.18.0",
"better-auth": "^1.6.19",
"drizzle-orm": "^0.45.2",
"drizzle-zod": "^0.8.3",
"lucide-react": "^1.21.0",
"nitro": "3.0.260610-beta",
"pg": "^8.21.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"tailwindcss": "^4.3.1",
"zod": "^4.4.3"
},
"devDependencies": {
"@dotenvx/dotenvx": "^1.73.1",
"@eslint/compat": "^2.1.0",
"@eslint/js": "^9.39.2",
"@tanstack/devtools-vite": "^0.7.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.2",
"@types/node": "^25.9.3",
"@types/pg": "^8.20.0",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.61.1",
"@typescript-eslint/parser": "^8.61.1",
"@vitejs/plugin-react": "^6.0.2",
"drizzle-kit": "^0.31.10",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"jsdom": "^29.1.1",
"open-cli": "^9.0.0",
"prettier": "^3.8.4",
"sst": "4.15.2",
"tsx": "^4.22.4",
"typescript": "^5.9.3",
"vite": "^8.0.16",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.1.9",
"web-vitals": "^5.3.0"
},
"engines": {
"node": ">=20.19.0 || >=22.12.0"
},
"private": true,
"scripts": {
"dev": "vite dev",
"backend:up": "docker compose up -d",
"backend:down": "docker compose down",
"backend:clear": "docker compose down -v",
"start": "node .output/server/index.mjs",
"backend:down": "docker compose down",
"backend:up": "docker compose up -d",
"build": "vite build",
"dev": "vite dev",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"migrate": "drizzle-kit migrate",
"migrate:generate": "drizzle-kit generate",
"psql": "dotenvx run -- sh -c 'psql \"$DATABASE_URL\"'",
"serve": "vite preview",
"test": "echo 'No tests yet'",
"lint:check": "eslint .",
"lint": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dependencies": {
"@electric-sql/client": "^1.2.0",
"@tailwindcss/vite": "^4.0.6",
"@tanstack/electric-db-collection": "^0.2.8",
"@tanstack/react-db": "^0.1.52",
"@tanstack/react-router": "^1.139.7",
"@tanstack/react-router-devtools": "^1.139.7",
"@tanstack/react-start": "^1.139.9",
"@tanstack/router-plugin": "^1.139.7",
"@trpc/client": "^11.7.2",
"@trpc/server": "^11.7.2",
"better-auth": "^1.6.2",
"drizzle-kit": "^0.30.0",
"drizzle-orm": "^0.39.0",
"drizzle-zod": "^0.7.1",
"lucide-react": "^0.544.0",
"nitro": "3.0.1-alpha.1",
"pg": "^8.16.3",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwindcss": "^4.1.13",
"zod": "^4.0.14"
"start": "node .output/server/index.mjs",
"test": "echo 'No tests yet'"
},
"devDependencies": {
"@dotenvx/dotenvx": "^1.51.2",
"@eslint/compat": "^1.3.1",
"@eslint/js": "^9.32.0",
"@tanstack/devtools-vite": "^0.3.11",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.2.0",
"@types/node": "^22.10.2",
"@types/pg": "^8.10.0",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@typescript-eslint/eslint-plugin": "^8.46.0",
"@typescript-eslint/parser": "^8.46.0",
"@vitejs/plugin-react": "^5.0.4",
"drizzle-kit": "^0.31.4",
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react": "^7.37.5",
"jsdom": "^27.0.0",
"open-cli": "^8.0.0",
"prettier": "^3.6.2",
"sst": "3.13.2",
"tsx": "^4.20.3",
"typescript": "^5.7.2",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.15",
"web-vitals": "^5.1.0"
}
"type": "module"
}
Loading
Loading