Skip to content
Merged
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
57 changes: 57 additions & 0 deletions .changeset/new-response-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
"@remix-relay/react": major
"@remix-relay/server": major
---

Update to GraphQL incremental delivery format (incrementalSpec v0.2)

### Breaking changes

- **`graphql` peer dependency updated from `17.0.0-alpha.2` to `17.0.0-alpha.9`** - Adopts the newer incremental delivery response format with `pending`/`incremental`/`completed` fields.

- **Client fetch function must use `processMultipartResponse`** - The client-side relay environment fetch function must be updated to use the new `processMultipartResponse` utility exported from `@remix-relay/react`, which handles the new response format. The `Accept` header must also change from `deferSpec=20220824` to `incrementalSpec=v0.2`.

### Migration

Update the `graphql` dependency:

```shell
pnpm add graphql@17.0.0-alpha.9
```

Update the client relay environment fetch function to use `processMultipartResponse`:

```typescript
import {
getCachedResponse,
processMultipartResponse,
} from "@remix-relay/react";

const fetchFn: FetchFunction = (params, variables, cacheConfig) => {
return (
getCachedResponse(params, variables, cacheConfig) ??
Observable.create((sink) => {
(async () => {
try {
const response = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "multipart/mixed; incrementalSpec=v0.2, application/json",
},
body: JSON.stringify({ query: params.text, variables }),
});

const parts = await meros(response);

for await (const payload of processMultipartResponse(parts)) {
sink.next(payload);
}
} finally {
sink.complete();
}
})();
})
);
};
```
27 changes: 7 additions & 20 deletions apps/counter-app/app/lib/relay-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import {
import { PayloadExtensions } from "relay-runtime/lib/network/RelayNetworkTypes";
import { toast } from "sonner";
import invariant from "tiny-invariant";
import { getCachedResponse } from "@remix-relay/react";
import {
getCachedResponse,
processMultipartResponse,
} from "@remix-relay/react";
import { trackPromise } from "~/components/Progress";

const isServer = typeof document === "undefined";
Expand All @@ -39,7 +42,7 @@ const fetchFn: FetchFunction = (
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "multipart/mixed; deferSpec=20220824, application/json",
Accept: "multipart/mixed; incrementalSpec=v0.2, application/json",
},
body: JSON.stringify({
query: params.text,
Expand All @@ -55,24 +58,8 @@ const fetchFn: FetchFunction = (

const parts = await meros(response);

if (parts instanceof Response) {
const result = await parts.json();
if (result.errors) {
throw new Error(result.errors?.[0]?.message);
}

sink.next(result);
} else {
for await (const part of parts) {
if (part.body.errors) {
throw new Error(part.body.errors?.[0]?.message);
}

sink.next({
...part.body,
...part.body?.incremental?.[0],
});
}
for await (const payload of processMultipartResponse(parts)) {
sink.next(payload);
}
} catch (err) {
if (!isServer) {
Expand Down
4 changes: 2 additions & 2 deletions apps/counter-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"write-graphql-schema": "tsx ./scripts/write-graphql-schema.ts"
},
"dependencies": {
"@apollo/server": "5.0.0",
"@apollo/server": "^5.1.0",
"@as-integrations/express5": "^1.1.2",
"@paralleldrive/cuid2": "^3.3.0",
"@pothos/core": "^4.12.0",
Expand All @@ -34,7 +34,7 @@
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"express": "^5.2.1",
"graphql": "17.0.0-alpha.2",
"graphql": "17.0.0-alpha.9",
"graphql-relay": "^0.10.2",
"graphql-subscriptions": "^3.0.0",
"graphql-ws": "^6.0.7",
Expand Down
2 changes: 1 addition & 1 deletion apps/counter-app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ directive @stream(
if: Boolean! = true

"""Number of items to return immediately"""
initialCount: Int = 0
initialCount: Int! = 0

"""Unique name"""
label: String
Expand Down
26 changes: 7 additions & 19 deletions apps/movie-app/app/lib/relay-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
RecordSource,
Store,
} from "relay-runtime";
import { getCachedResponse } from "@remix-relay/react";
import {
getCachedResponse,
processMultipartResponse,
} from "@remix-relay/react";

const isServer = typeof document === "undefined";

Expand All @@ -30,7 +33,7 @@ const fetchFn: FetchFunction = (
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "multipart/mixed; deferSpec=20220824, application/json",
Accept: "multipart/mixed; incrementalSpec=v0.2, application/json",
},
body: JSON.stringify({
doc_id: params.id,
Expand All @@ -40,23 +43,8 @@ const fetchFn: FetchFunction = (

const parts = await meros(response);

// Check if it's a Response-like object (has .json method)
if (parts && typeof (parts as Response).json === "function") {
sink.next(await (parts as Response).json());
} else {
for await (const part of parts as AsyncIterable<{
json: boolean;
body: unknown;
}>) {
const data = part.json
? {
...(part.body as object),
...((part.body as { incremental?: object[] })
?.incremental?.[0] ?? {}),
}
: JSON.parse(part.body as string);
sink.next(data);
}
for await (const payload of processMultipartResponse(parts)) {
sink.next(payload);
}
} catch (err) {
if (!isServer) {
Expand Down
3 changes: 1 addition & 2 deletions apps/movie-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"write-graphql-schema": "tsx ./scripts/write-graphql-schema.ts"
},
"dependencies": {
"@graphql-tools/executor": "^1.5.1",
"@graphql-yoga/plugin-defer-stream": "^3.18.0",
"@graphql-yoga/plugin-persisted-operations": "^3.18.0",
"@pothos/core": "^4.12.0",
Expand All @@ -30,7 +29,7 @@
"@remix-relay/ui": "workspace:*",
"class-variance-authority": "^0.7.1",
"drizzle-orm": "^0.45.1",
"graphql": "17.0.0-alpha.2",
"graphql": "17.0.0-alpha.9",
"graphql-yoga": "^5.18.0",
"isbot": "^5.1.34",
"lodash-es": "^4.17.23",
Expand Down
2 changes: 1 addition & 1 deletion apps/movie-app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ directive @stream(
if: Boolean! = true

"""Number of items to return immediately"""
initialCount: Int = 0
initialCount: Int! = 0

"""Unique name"""
label: String
Expand Down
27 changes: 7 additions & 20 deletions apps/trellix-relay/app/lib/relay-environment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import {
import { PayloadExtensions } from "relay-runtime/lib/network/RelayNetworkTypes";
import { toast } from "sonner";
import invariant from "tiny-invariant";
import { getCachedResponse } from "@remix-relay/react";
import {
getCachedResponse,
processMultipartResponse,
} from "@remix-relay/react";
import { trackPromise } from "~/components/Progress";

const isServer = typeof document === "undefined";
Expand All @@ -40,7 +43,7 @@ const fetchFn: FetchFunction = (
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "multipart/mixed; deferSpec=20220824, application/json",
Accept: "multipart/mixed; incrementalSpec=v0.2, application/json",
},
body: JSON.stringify({
query: params.text,
Expand All @@ -56,24 +59,8 @@ const fetchFn: FetchFunction = (

const parts = await meros(response);

if (parts instanceof Response) {
const result = await parts.json();
if (result.errors) {
throw new Error(result.errors?.[0]?.message);
}

sink.next(result);
} else {
for await (const part of parts) {
if (part.body.errors) {
throw new Error(part.body.errors?.[0]?.message);
}

sink.next({
...part.body,
...part.body?.incremental?.[0],
});
}
for await (const payload of processMultipartResponse(parts)) {
sink.next(payload);
}
} catch (err) {
if (!isServer) {
Expand Down
4 changes: 2 additions & 2 deletions apps/trellix-relay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"write-graphql-schema": "tsx ./scripts/write-graphql-schema.ts"
},
"dependencies": {
"@apollo/server": "5.0.0",
"@apollo/server": "^5.1.0",
"@as-integrations/express5": "^1.1.2",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
Expand All @@ -46,7 +46,7 @@
"cors": "^2.8.6",
"drizzle-orm": "^0.45.1",
"express": "^5.2.1",
"graphql": "17.0.0-alpha.2",
"graphql": "17.0.0-alpha.9",
"graphql-relay": "^0.10.2",
"graphql-subscriptions": "^3.0.0",
"graphql-ws": "^6.0.7",
Expand Down
2 changes: 1 addition & 1 deletion apps/trellix-relay/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ directive @stream(
if: Boolean! = true

"""Number of items to return immediately"""
initialCount: Int = 0
initialCount: Int! = 0

"""Unique name"""
label: String
Expand Down
38 changes: 12 additions & 26 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pnpm create react-router@latest --template remix-run/react-router-templates/node
For this guide we'll use [Pothos](https://pothos-graphql.dev/) to create the GraphQL schema.

```shell
pnpm add @pothos/core graphql@17.0.0-alpha.2
pnpm add @pothos/core graphql@17.0.0-alpha.9
```

> [!NOTE]
Expand Down Expand Up @@ -191,24 +191,20 @@ Add an `app/lib/relay-environment.ts` file.

```typescript
import { meros } from "meros/browser";
import type { FetchFunction } from "relay-runtime";
import {
type CacheConfig,
type FetchFunction,
type RequestParameters,
type Variables,
Environment,
Network,
Observable,
RecordSource,
Store,
} from "relay-runtime";
import { getCachedResponse } from "@remix-relay/react";
import {
getCachedResponse,
processMultipartResponse,
} from "@remix-relay/react";

const fetchFn: FetchFunction = (
params: RequestParameters,
variables: Variables,
cacheConfig: CacheConfig,
) => {
const fetchFn: FetchFunction = (params, variables, cacheConfig) => {
return (
getCachedResponse(params, variables, cacheConfig) ??
Observable.create((sink) => {
Expand All @@ -218,25 +214,15 @@ const fetchFn: FetchFunction = (
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "multipart/mixed; deferSpec=20220824, application/json",
Accept: "multipart/mixed; incrementalSpec=v0.2, application/json",
},
body: JSON.stringify({
query: params.text,
variables,
}),
body: JSON.stringify({ query: params.text, variables }),
});

const parts = await meros(response);

if (parts instanceof Response) {
sink.next(await parts.json());
} else {
for await (const part of parts) {
sink.next({
...part.body,
...part.body?.incremental?.[0],
});
}
for await (const payload of processMultipartResponse(parts)) {
sink.next(payload);
}
} finally {
sink.complete();
Expand Down Expand Up @@ -265,7 +251,7 @@ export function getCurrentEnvironment() {
}
```

Note the use of `fetch` to request data, and the [meros](https://github.com/maraisr/meros) library to read the multipart response. This enables streaming of client requests.
Note the use of `fetch` to request data, and the [meros](https://github.com/maraisr/meros) library to read the multipart response. The `processMultipartResponse` utility from `@remix-relay/react` handles the incremental delivery format used by `@defer`.

Add providers and a Suspense boundary to `app/root.tsx`.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"graphql": "17.0.0-alpha.2"
"graphql": "17.0.0-alpha.9"
}
}
}
Expand Down
5 changes: 1 addition & 4 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@
"lint": "eslint . --max-warnings 0",
"typecheck": "tsc -b --noEmit"
},
"dependencies": {
"tiny-invariant": "^1.3.3"
},
"devDependencies": {
"@remix-relay/eslint-config": "workspace:*",
"@remix-relay/typescript-config": "workspace:*",
Expand All @@ -58,7 +55,7 @@
"@types/react-relay": "^18.2.1",
"@types/relay-runtime": "^20.1.1",
"eslint": "^9.39.0",
"graphql": "17.0.0-alpha.2",
"graphql": "17.0.0-alpha.9",
"jiti": "^2.6.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
Expand Down
Loading