Skip to content

Commit

Permalink
feat!: move table to query param & rename existing headers and query …
Browse files Browse the repository at this point in the history
…params (#1900)

Included renames:
- `electric-chunk-last-offset` -> `electric-offset`
- `electric-next-cursor` -> `electric-cursor`
- `electric-shape-id` -> `electric-handle`
- `?shape_id` -> `?handle`

Added query parameter: `?table` instead of `/:table` in the path

Closes #1771, #1796, #1798

---------

Co-authored-by: Kyle Mathews <[email protected]>
Co-authored-by: msfstef <[email protected]>
Co-authored-by: Ilia Borovitinov <[email protected]>
  • Loading branch information
4 people authored Nov 5, 2024
1 parent 0873da2 commit 4d872b6
Show file tree
Hide file tree
Showing 77 changed files with 1,517 additions and 1,191 deletions.
6 changes: 6 additions & 0 deletions .changeset/hot-lions-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@electric-sql/client": minor
"@electric-sql/react": minor
---

All `Shape` interfaces (`ShapeStream`, `Shape`, `useShape`) now require `table` as an additional configuration parameter, and the shape API endpoint url only needs to point to `/v1/shape`.
6 changes: 6 additions & 0 deletions .changeset/silly-pants-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@electric-sql/client": minor
"@core/sync-service": minor
---

[breaking] Changes the API contract for the server to use new, clearer header names and query parameter names. One highlight is the change from `shape_id` to `handle` as the URL query parameter
5 changes: 5 additions & 0 deletions .changeset/tender-pens-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@core/sync-service": minor
---

[BREAKING] All shape API endpoints now accept `table` as a query parameter rather than a path parameter, so `/v1/shape/foo?offset=-1` now becomes `/v1/shape?table=foo&offset=-1`.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ shape-data.json
test-dbs
tsconfig.tsbuildinfo
wal
/shapes
.sst
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ docker compose -f .support/docker-compose.yml up
You can then use the [HTTP API](https://electric-sql.com/docs/api/http) to sync data from your Postgres. For example, to start syncing the whole `foo` table:

```sh
curl -i 'http://localhost:3000/v1/shape/foo?offset=-1'
curl -i 'http://localhost:3000/v1/shape?table=foo&offset=-1'
```

Or use one of the clients or integrations, such as the [`useShape`](https://electric-sql.com/docs/api/integrations/react) React hook:
Expand All @@ -69,7 +69,8 @@ import { useShape } from '@electric-sql/react'

function Component() {
const { data } = useShape({
url: `http://localhost:3000/v1/shape/foo`,
url: `http://localhost:3000/v1/shape`,
table: `foo`,
where: `title LIKE 'foo%'`,
})

Expand Down
11 changes: 5 additions & 6 deletions examples/auth/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,17 @@ const usersShape = (): ShapeStreamOptions => {
const queryParams = new URLSearchParams(window.location.search)
const org_id = queryParams.get(`org_id`)
return {
url: new URL(
`/shape-proxy/users?org_id=${org_id}`,
window.location.origin
).href,
url: new URL(`/shape-proxy?org_id=${org_id}`, window.location.origin)
.href,
table: `users`,
headers: {
Authorization: org_id || ``,
},
}
} else {
return {
url: new URL(`https://not-sure-how-this-works.com/shape-proxy/items`)
.href,
url: new URL(`https://not-sure-how-this-works.com/shape-proxy`).href,
table: `items`,
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
export async function GET(
request: Request,
{ params }: { params: { table: string } }
) {
export async function GET(request: Request) {
const url = new URL(request.url)
const { table } = params

// Constuct the upstream URL
const originUrl = new URL(`http://localhost:3000/v1/shape/${table}`)
const originUrl = new URL(`http://localhost:3000/v1/shape`)
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value)
})
Expand Down
4 changes: 2 additions & 2 deletions examples/bash-client/bash-client.bash
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

# URL to download the JSON file from (without the output parameter)
BASE_URL="http://localhost:3000/v1/shape/todos"
BASE_URL="http://localhost:3000/v1/shape?table=todos"

# Directory to store individual JSON files
OFFSET_DIR="./json_files"
Expand Down Expand Up @@ -78,7 +78,7 @@ process_json() {

# Main loop to poll for updates every second
while true; do
url="$BASE_URL?offset=$LATEST_OFFSET"
url="$BASE_URL&offset=$LATEST_OFFSET"
echo $url

LATEST_OFFSET=$(process_json "$url" "shape-data.json")
Expand Down
3 changes: 2 additions & 1 deletion examples/basic-example/src/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const baseUrl = import.meta.env.ELECTRIC_URL ?? `http://localhost:3000`

export const Example = () => {
const { data: items } = useShape<Item>({
url: `${baseUrl}/v1/shape/items`,
url: `${baseUrl}/v1/shape`,
table: `items`
})

/*
Expand Down
1 change: 1 addition & 0 deletions examples/linearlite/.env.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_URL=postgresql://neondb_owner:[email protected]/neondb?sslmode=require
3 changes: 2 additions & 1 deletion examples/linearlite/src/pages/Issue/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export interface CommentsProps {
function Comments(commentProps: CommentsProps) {
const [newCommentBody, setNewCommentBody] = useState<string>(``)
const allComments = useShape({
url: `${baseUrl}/v1/shape/comment`,
url: `${baseUrl}/v1/shape`,
table: `comment`,
})! as Comment[]

const comments = allComments.data.filter(
Expand Down
3 changes: 2 additions & 1 deletion examples/linearlite/src/shapes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { baseUrl } from './electric'

export const issueShape = {
url: `${baseUrl}/v1/shape/issue`,
url: `${baseUrl}/v1/shape`,
table: `issue`,
}
7 changes: 4 additions & 3 deletions examples/nextjs-example/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { matchStream } from "./match-stream"
const itemShape = () => {
if (typeof window !== `undefined`) {
return {
url: new URL(`/shape-proxy/items`, window?.location.origin).href,
url: new URL(`/shape-proxy`, window?.location.origin).href,
table: `items`,
}
} else {
return {
url: new URL(`https://not-sure-how-this-works.com/shape-proxy/items`)
.href,
url: new URL(`https://not-sure-how-this-works.com/shape-proxy`).href,
table: `items`,
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
export async function GET(
request: Request,
{ params }: { params: { table: string } }
) {
export async function GET(request: Request) {
const url = new URL(request.url)
const { table } = params
const originUrl = new URL(`http://localhost:3000/v1/shape/${table}`)
const originUrl = new URL(`http://localhost:3000/v1/shape`)
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value)
})
Expand Down
3 changes: 2 additions & 1 deletion examples/redis-sync/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ client.connect().then(async () => {
const updateKeyScriptSha1 = await client.SCRIPT_LOAD(script)

const itemsStream = new ShapeStream({
url: `http://localhost:3000/v1/shape/items`,
url: `http://localhost:3000/v1/shape`,
table: `items`,
})
itemsStream.subscribe(async (messages: Message[]) => {
// Begin a Redis transaction
Expand Down
3 changes: 2 additions & 1 deletion examples/remix-basic/app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { matchStream } from "../match-stream"

const itemShape = () => {
return {
url: new URL(`/shape-proxy/items`, window.location.origin).href,
url: new URL(`/shape-proxy`, window.location.origin).href,
table: `items`,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { LoaderFunctionArgs } from "@remix-run/node"

export async function loader({ params, request }: LoaderFunctionArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url)
const { table } = params
const originUrl = new URL(`http://localhost:3000/v1/shape/${table}`)
const originUrl = new URL(`http://localhost:3000/v1/shape`)
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value)
})
Expand Down
13 changes: 7 additions & 6 deletions examples/tanstack-example/src/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const baseUrl = import.meta.env.ELECTRIC_URL ?? `http://localhost:3000`
const baseApiUrl = `http://localhost:3001`

const itemShape = () => ({
url: new URL(`/v1/shape/items`, baseUrl).href,
url: new URL(`/v1/shape`, baseUrl).href,
table: `items`
})

async function createItem(newId: string) {
Expand Down Expand Up @@ -43,11 +44,11 @@ async function clearItems(numItems: number) {
const findUpdatePromise =
numItems > 0
? matchStream({
stream: itemsStream,
operations: [`delete`],
// First delete will match
matchFn: () => true,
})
stream: itemsStream,
operations: [`delete`],
// First delete will match
matchFn: () => true,
})
: Promise.resolve()

// Delete all items
Expand Down
3 changes: 2 additions & 1 deletion examples/todo-app/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ type ToDo = {

export default function Index() {
const { data: todos } = useShape<ToDo>({
url: `http://localhost:3000/v1/shape/todos`,
url: `http://localhost:3000/v1/shape`,
table: `todos`,
})
todos.sort((a, b) => a.created_at - b.created_at)
console.log({ todos })
Expand Down
12 changes: 6 additions & 6 deletions integration-tests/tests/crash-recovery.lux
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@
# Initialize a shape and collect the offset
[shell client]
# strip ANSI codes from response for easier matching
!curl -v -X GET http://localhost:3000/v1/shape/items?offset=-1
?electric-shape-id: ([\d-]+)
[local shape_id=$1]
?electric-chunk-last-offset: ([\w\d_]+)
[local last_offset=$1]
!curl -v -X GET "http://localhost:3000/v1/shape?table=items&offset=-1"
?electric-handle: ([\d-]+)
[local handle=$1]
?electric-offset: ([\w\d_]+)
[local offset=$1]

## Terminate electric
[shell electric]
Expand All @@ -58,7 +58,7 @@

# Client should be able to continue same shape
[shell client]
!curl -v -X GET "http://localhost:3000/v1/shape/items?offset=$last_offset&shape_id=$shape_id"
!curl -v -X GET "http://localhost:3000/v1/shape?table=items&handle=$handle&offset=$offset"
??HTTP/1.1 200 OK

[cleanup]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@

## Initialize a couple of shapes so that Electric starts processing transactions from Postgres
[shell client]
!curl -i http://localhost:3000/v1/shape/roots?offset=-1
!curl -i "http://localhost:3000/v1/shape?table=roots&offset=-1"
??200 OK

!curl -i http://localhost:3000/v1/shape/leaves?offset=-1
!curl -i "http://localhost:3000/v1/shape?table=leaves&offset=-1"
??200 OK

## Commit enough new transactions for shape storage to hit the simulated failure.
Expand Down
3 changes: 2 additions & 1 deletion packages/react-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import { useShape } from "@electric-sql/react"

export default function MyComponent () {
const { isLoading, data } = useShape({
url: "http://my-api.com/shape/foo",
url: "http://my-api.com/shape",
table: `foo`,
})

if (isLoading) {
Expand Down
3 changes: 3 additions & 0 deletions packages/react-hooks/test/react-hooks.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Row } from 'packages/typescript-client/dist'
describe(`useShape`, () => {
it(`should infer correct return type when no selector is provided`, () => {
const shape = useShape({
table: ``,
url: ``,
})

Expand All @@ -20,6 +21,7 @@ describe(`useShape`, () => {

it(`should infer correct return type when a selector is provided`, () => {
const shape = useShape({
table: ``,
url: ``,
selector: (_value: UseShapeResult) => {
return {
Expand All @@ -36,6 +38,7 @@ describe(`useShape`, () => {

it(`should raise a type error if type argument does not equal inferred return type`, () => {
const shape = useShape<Row, number>({
table: ``,
url: ``,
// @ts-expect-error - should have type mismatch, because doesn't match the declared `Number` type
selector: (_value: UseShapeResult) => {
Expand Down
Loading

0 comments on commit 4d872b6

Please sign in to comment.