项目文件夹

文件
wehub-resource-sync 98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-linux (24) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (24) (push) Has been cancelled
Expo App TypeScript typecheck / typecheck (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:40:49 +08:00

243 行
7.7 KiB
Markdown

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# Electric SQL — Sync Engine Evaluation
Research completed 2026-04-08.
## What it is
Electric SQL is a **read-path sync engine** — a standalone Elixir service
that sits between Postgres and client applications. It tails the Postgres
WAL (Write-Ahead Log) and streams subsets of data ("Shapes") to clients
over HTTP.
It is NOT a database, NOT a Postgres extension, NOT a write-path solution.
```
Postgres ──[logical replication]──> Electric (Elixir) ──[HTTP]──> CDN ──> Clients
```
## Open source
- **License**: Apache-2.0
- **Repo**: github.com/electric-sql/electric
- **Stars**: ~10,046
- **NPM downloads**: ~1.68M/month (`@electric-sql/client`)
- **Maintained by**: Electric DB Inc.
- **Status**: GA (1.0 released March 2025, currently 1.1+)
- **Commercial offering**: Electric Cloud (hosted, pay for writes + retention,
reads/fan-out free)
## Core API
### Server setup (Docker)
```yaml
electric:
image: electricsql/electric:latest
environment:
DATABASE_URL: postgresql://user:pass@host:5432/db
ELECTRIC_SECRET: your-secret
ports:
- "3000:3000"
```
Requires: Postgres 14+, `wal_level=logical`, user with `REPLICATION` role.
Electric creates in your database:
- Publication: `electric_publication_default`
- Replication slot: `electric_slot_default`
- Sets `REPLICA IDENTITY FULL` on synced tables
- No extensions required
### Client — React hooks
```tsx
import { useShape } from '@electric-sql/react'
function TaskList() {
const { isLoading, data } = useShape<Task>({
url: `http://localhost:3000/v1/shape`,
params: {
table: 'tasks',
where: `org_id = '123'`,
columns: `id,title,status`,
},
})
if (isLoading) return <div>Loading...</div>
return <ul>{data.map(t => <li key={t.id}>{t.title}</li>)}</ul>
}
```
### Client — vanilla TypeScript
```ts
import { ShapeStream, Shape } from '@electric-sql/client'
const stream = new ShapeStream({
url: `http://localhost:3000/v1/shape`,
params: {
table: 'tasks',
where: `org_id = '123'`,
},
})
const shape = new Shape(stream)
const rows = await shape.rows // wait for initial sync
shape.subscribe(({ rows }) => {
console.log('Updated:', rows)
})
```
### Raw HTTP API
```sh
# Initial sync — returns all matching rows
curl 'http://localhost:3000/v1/shape?table=tasks&offset=-1'
# Live updates — long-polls until new data arrives
curl 'http://localhost:3000/v1/shape?table=tasks&live=true&handle=abc&offset=0_5'
# SSE mode
curl 'http://localhost:3000/v1/shape?table=tasks&live=true&live_sse=true&handle=abc&offset=0_5'
# Changes only (skip initial snapshot)
curl 'http://localhost:3000/v1/shape?table=tasks&offset=-1&log=changes_only'
```
Response format:
```json
[
{"offset":"0_0","value":{"id":"1","title":"Fix bug"},"key":"\"public\".\"tasks\"/\"1\"","headers":{"operation":"insert"}},
{"headers":{"control":"up-to-date"}}
]
```
### Writes — BYO (by design)
Electric does NOT handle writes. You write through your own API:
```
Client → tRPC/REST mutation → your backend → Postgres INSERT
→ WAL → Electric → shape stream → all subscribers get the update
```
With TanStack DB, optimistic updates work via txid confirmation:
```ts
const collection = createCollection(
electricCollectionOptions<Task>({
shapeOptions: { url, params: { table: 'tasks' } },
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
const result = await apiClient.task.create.mutate(item)
return { txid: result.txid } // confirms optimistic write
},
}),
)
```
## Shapes — capabilities and limitations
A Shape is a declarative subset of one Postgres table.
**Can do:**
- Single table with WHERE filter, column selection
- SQL operators, boolean logic, LIKE, IN, comparisons
- Parameterized queries (`$1`, `$2`) for injection safety
- Subqueries (experimental): `id IN (SELECT user_id FROM memberships WHERE org_id = $1)`
- Progressive loading with ORDER BY, LIMIT, OFFSET
- Partitioned tables
**Cannot do:**
- No JOINs (single-table only — subscribe to multiple shapes, join client-side)
- No aggregations (COUNT, SUM, AVG)
- No non-deterministic functions (now(), random()) in WHERE
- Shapes are immutable — switch context = new subscription
## How sync works under the hood
1. Electric connects to Postgres via **logical replication** (standard PG
feature used for standby replicas)
2. Creates a publication and replication slot
3. `ShapeLogCollector` GenServer consumes WAL stream, evaluates each
INSERT/UPDATE/DELETE against registered shapes' WHERE clauses
4. Matching changes are distributed to shape-specific logs
5. Clients consume via HTTP long-polling or SSE
6. `up-to-date` control message = caught up to current state
7. `must-refetch` = server needs client to re-sync from scratch
**Consistency**: changes delivered in WAL (LSN) order within a shape.
Eventually consistent. No gaps.
**No conflict resolution**: Electric is read-only. Conflicts only exist
in your write path — your problem to solve.
## Performance
| Metric | Value |
|--------|-------|
| Live update latency (optimized WHERE) | **6ms** (3ms PG + 3ms Electric) |
| Live update latency (non-optimized WHERE) | ~100ms at 10K shapes |
| Write throughput | **4,000–6,000 rows/sec** |
| Initial sync | CDN-cacheable at edge |
| Concurrent clients tested | 2K clients × 500-row shapes |
| Single client max | 1M rows with linear sync time |
| Production scale | 100K–1M concurrent users |
**CDN caching is the scaling secret**: initial sync responses are HTTP-cacheable.
Live-mode long-polling requests are collapsed by CDN (N clients waiting =
1 upstream request). This means Electric + CDN scales to millions with
minimal Postgres load.
## Frontend integration
| Framework | Package | Status |
|-----------|---------|--------|
| React | `@electric-sql/react` | First-class |
| React Native / Expo | `@electric-sql/react` | Works (same client) |
| TanStack DB | `@tanstack/electric-db-collection` | Deep integration |
| Next.js | Via `@electric-sql/react` | Integration guide |
| Phoenix/LiveView | `phoenix_sync` | First-class (Elixir) |
| Yjs | `y-electric` | Integration package |
| Any language | HTTP + JSON | Roll your own |
Note: PGlite does NOT yet work in React Native.
## Recommended stack (what Superset uses)
```
Reads: Postgres → Electric → Cloudflare Worker (auth proxy) → TanStack DB
Writes: Client → tRPC mutation → API server → Postgres → Electric confirms
```
This is the officially recommended "TanStack" stack in Electric's docs.
Superset validates it works in production at scale.
## Comparison to alternatives
| | Electric | Custom WS | Supabase Realtime | PowerSync | CRDTs |
|---|---|---|---|---|---|
| Protocol | HTTP (CDN-cacheable) | WS (stateful) | WS | Custom | P2P |
| Initial sync | Built-in + cached | Build yourself | Separate query | Built-in | Built-in |
| Writes | BYO | Full control | Built-in | Built-in (sync rules) | Automatic |
| Scaling | CDN fan-out | Connection-bound | Connection-bound | Medium | P2P |
| Conflicts | BYO | BYO | N/A | Built-in | Automatic (math) |
| Best for | Structured data sync | Full control | Notifications | Mobile offline | Collaborative editing |
| Complexity | Low | High | Medium | Medium | High |
## Relevance for Happy
**Strong fit:**
- Read-heavy pattern (streaming agent state to UI) maps perfectly
- CDN caching scales without Postgres load
- 6ms latency for live agent activity updates
- HTTP works everywhere (web, mobile, CLI)
- Postgres stays source of truth with full SQL power
- Expo/React Native support
**Concerns:**
- No joins — need multiple shapes + client-side joining
- Must build own write path (fine — agent commands go through backend anyway)
- One more service to operate (or use Electric Cloud)
- Shapes are immutable (session switch = new subscription)