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
53 changes: 53 additions & 0 deletions contents/docs/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,10 @@ Zero provides utilities to make it easy to implement the queries endpoint in any
text: 'Hono',
sync: {api: 'hono'},
},
{
text: 'One',
sync: {api: 'one'},
},
]}
>

Expand Down Expand Up @@ -613,6 +617,28 @@ app.post('/api/query', async c => {
})
```

```ts
// app/api/query+api.ts
import {handleQueryRequest} from '@rocicorp/zero/server'
import {mustGetQuery} from '@rocicorp/zero'
import {queries} from '../zero/queries.ts'
import {schema} from '../zero/schema.ts'
import type {Endpoint} from 'one'

export const POST: Endpoint = async request => {
const result = await handleQueryRequest(
(name, args) => {
const query = mustGetQuery(queries, name)
return query.fn({args, ctx: {userId: 'anon'}})
},
schema,
request
)

return Response.json(result)
}
```

</CodeGroup>

Stop and re-run zero-cache with the URL of the queries endpoint:
Expand Down Expand Up @@ -1043,6 +1069,10 @@ Then, use the `dbProvider` to handle the mutate request:
text: 'Hono',
sync: {api: 'hono'},
},
{
text: 'One',
sync: {api: 'one'},
},
]}
>

Expand Down Expand Up @@ -1146,6 +1176,29 @@ app.post('/api/mutate', async c => {
})
```

```ts
// app/api/mutate+api.ts
import {handleMutateRequest} from '@rocicorp/zero/server'
import {mustGetMutator} from '@rocicorp/zero'
import {mutators} from '../zero/mutators.ts'
import {dbProvider} from '../db-provider.ts'
import type {Endpoint} from 'one'

export const POST: Endpoint = async request => {
const result = await handleMutateRequest(
dbProvider,
transact =>
transact((tx, name, args) => {
const mutator = mustGetMutator(mutators, name)
return mutator.fn({args, tx, ctx: {userId: 'anon'}})
}),
request
)

return Response.json(result)
}
```

</CodeGroup>

Restart zero-cache to tell it about this new endpoint:
Expand Down
57 changes: 56 additions & 1 deletion contents/docs/mutators.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ A copy of each mutator exists on both the client and on your server:
invert="light"
/>

Often the implementations will be the same, and you can just share their code. This is easy with full-stack frameworks like TanStack Start or Next.js.
Often the implementations will be the same, and you can just share their code. This is easy with full-stack frameworks like TanStack Start, Next.js, or One.

But the implementations don't have to be the same, or even compute the same result. For example, the server can add extra checks to enforce permissions, or send notifications or interact with other systems.

Expand Down Expand Up @@ -527,6 +527,7 @@ You can use the `handleMutateRequest` and `mustGetMutator` functions to implemen
{text: 'Next.js', sync: {api: 'nextjs'}},
{text: 'Solid Start', sync: {api: 'solid'}},
{text: 'Hono', sync: {api: 'hono'}},
{text: 'One', sync: {api: 'one'}},
]}
>

Expand Down Expand Up @@ -637,6 +638,29 @@ app.post('/api/zero/mutate', async c => {
})
```

```ts
// app/api/zero/mutate+api.ts
import {handleMutateRequest} from '@rocicorp/zero/server'
import {mustGetMutator} from '@rocicorp/zero'
import {mutators} from 'mutators.ts'
import {dbProvider} from 'db-provider.ts'
import type {Endpoint} from 'one'

export const POST: Endpoint = async request => {
const result = await handleMutateRequest(
dbProvider,
transact =>
transact((tx, name, args) => {
const mutator = mustGetMutator(mutators, name)
return mutator.fn({args, tx, ctx: {userId: 'anon'}})
}),
request
)

return Response.json(result)
}
```

</CodeGroup>

<Note heading="Using a different bindings library">
Expand Down Expand Up @@ -678,6 +702,7 @@ It is also of course possible for the entire push endpoint to return an HTTP err
{text: 'Next.js', sync: {api: 'nextjs'}},
{text: 'Solid Start', sync: {api: 'solid'}},
{text: 'Hono', sync: {api: 'hono'}},
{text: 'One', sync: {api: 'one'}},
]}
>

Expand Down Expand Up @@ -712,6 +737,12 @@ app.post('/api/zero/mutate', async c => {
})
```

```ts
export const POST: Endpoint = async () => {
throw new Error('zonk') // will trigger resend
}
```

</CodeGroup>

If Zero receives any response from the mutate endpoint other than HTTP 200, 401, or 403, it will disconnect and enter the [error state](/docs/connection#errors).
Expand Down Expand Up @@ -1011,6 +1042,7 @@ Then in your mutate handler:
{text: 'Next.js', sync: {api: 'nextjs'}},
{text: 'Solid Start', sync: {api: 'solid'}},
{text: 'Hono', sync: {api: 'hono'}},
{text: 'One', sync: {api: 'one'}},
]}
>

Expand Down Expand Up @@ -1122,6 +1154,29 @@ app.post('/api/zero/mutate', async c => {
})
```

```ts
export const POST: Endpoint = async request => {
const asyncTasks: Array<() => Promise<void>> = []
const mutators = createMutators(asyncTasks)

const result = await handleMutateRequest(
dbProvider,
transact =>
transact((tx, name, args) => {
const mutator = mustGetMutator(mutators, name)
return mutator.fn({tx, ctx: {userId: 'anon'}, args})
}),
request
)

// Run all async tasks
// If any fail, do not block the response, since the
// mutation result has already been written to the database.
await Promise.allSettled(asyncTasks.map(task => task()))
return Response.json(result)
}
```

</CodeGroup>

## Custom Mutate Implementation
Expand Down
25 changes: 24 additions & 1 deletion contents/docs/queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ A copy of each query exists on both the client and on your server:
invert="dark"
/>

Often the implementations will be the same, and you can just share their code. This is easy with full-stack frameworks like TanStack Start or Next.js.
Often the implementations will be the same, and you can just share their code. This is easy with full-stack frameworks like TanStack Start, Next.js, or One.

But the implementations don't have to be the same, or even compute the same result. For example, the server can add extra filters to enforce permissions that the client query does not.

Expand Down Expand Up @@ -251,6 +251,7 @@ You can use the `handleQueryRequest` and `mustGetQuery` functions to implement t
{text: 'Next.js', sync: {api: 'nextjs'}},
{text: 'Solid Start', sync: {api: 'solid'}},
{text: 'Hono', sync: {api: 'hono'}},
{text: 'One', sync: {api: 'one'}},
]}
>

Expand Down Expand Up @@ -346,6 +347,28 @@ app.post('/api/zero/query', async c => {
})
```

```ts
// app/api/zero/query+api.ts
import {handleQueryRequest} from '@rocicorp/zero/server'
import {mustGetQuery} from '@rocicorp/zero'
import {queries} from 'queries.ts'
import {schema} from 'schema.ts'
import type {Endpoint} from 'one'

export const POST: Endpoint = async request => {
const result = await handleQueryRequest(
(name, args) => {
const query = mustGetQuery(queries, name)
return query.fn({args, ctx: {userId: 'anon'}})
},
schema,
request
)

return Response.json(result)
}
```

</CodeGroup>

`handleQueryRequest` accepts a standard `Request` and returns a JSON object which can be serialized and returned by your server framework of choice.
Expand Down
15 changes: 15 additions & 0 deletions contents/docs/server-zql.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ For now, we don't recommend using Zero with SSR. Use your framework's recommende
{text: 'TanStack Start', sync: {api: 'tanstack'}},
{text: 'Next.js', sync: {api: 'nextjs'}},
{text: 'SolidStart', sync: {api: 'solid'}},
{text: 'One', sync: {api: 'one'}},
]}
>

Expand Down Expand Up @@ -315,4 +316,18 @@ export default function Root() {
}
```

```tsx
// One uses Vite's client rendering by default,
// so ZeroProvider works without special wrappers
import {ZeroProvider} from '@rocicorp/zero/react'

export default function Root() {
return (
<ZeroProvider>
<App />
</ZeroProvider>
)
}
```

</CodeGroup>