Skip to content

Commit

Permalink
TanStack Start integration (#412)
Browse files Browse the repository at this point in the history
* add `(TeeTo|ReadFrom)ReadableStreamLink` links

* add `skipDataTransport` function

* also export type `ReadableStreamLinkEvent`

* forgot file

* implement multipart streaming

* cleanup

* WIP

* handle re-consumption of readFromReadableStreamKey

* close on unsubscribe, catch errors

* tryClose, test adjustments

* adjust errorlink type

* streamline bundling

* fix jest integration test

* PreloadQuery: add `@defer` example

* add detail, suspense-transition-bug

* work around bug for now

* exports

* add exported types

* initialize basic react-router project

* clean up and `yarn react-router reveal`

* add raw experiment

* move eslint config into shared position

* Fix bundling configuration so parts of the `graphql` package don't end (#388)

* Release version 0.11.6@latest to npm

* Update peer deps for React 19 compatibility (#399)

* update devDependencies to React 19, adjust tests and examples (#400)

* Release version 0.11.7@latest to npm

* add example

* update react-router

* working

* patch PR in

* last adjustments

* fix up test for new deps

* clean up template fluff

* move `IncrementalSchemaLink` and others to `shared`

* use `@defer` in the example

* update turbo-stream patch

* update example to React 19

* adjust `prepublishOnly`

* add to pkg-pr-new-publish

* add empty unit test

* increase delay

* streamline integration test build

* needs more time in CI

* exclude shared

* copy react-router package to tanstack-start folder

* package init

* init tanstack start project

* current progress

* fix duplicate `@apollo/client` dependency

* make things work

* adjustments

* generalize apiRoute

* api, use schemalink on server

* update tanstack start

* add TSR devtools

* workaround for TanStack/router#3117

* trigger CI

* update patchfiles

* add new properties

* changeset

* trigger CI

* fix build warning

* port over vercel template from https://github.com/remix-run/react-router-templates/tree/main/vercel

* adjust folder structure

* more vercel adjustments

* use `promiscade` instead of a patched `turbo-stream` package

* update promiscade

* add promiscade-related comment

* lockfile

* workarounds for promiscade

* move `graphql` dependency into monorepo
to prevent type mismatches

* adjust comments

* these types are correct now

* update lockfile

* rename route

* add `useSuspenseQuery` demo, too

* fix import

* simplify client-side

* remove log

* remove unneccessary type assertion

* slight changes, README

* fix up important block

* fix typo, reorder

* fixup shape test

* add bundleInfo, adjust tests

* adjust tests

* adjust for old node

* add globalThis.window in test

* bump promiscade

* update tanstack start, remove workaround for fixed bug

* update package.json

* add TanStack/router#3161

* lockfile

* update correct react types

* eliminate context reliance

this makes it easier to use `yalc`

* sync updates from TanStack/router#2698

* lockfiles

* script

* fighting with duplicate dependencies

* unify react types version

* remove unused path

* more config file mentions

* TSR 6d6780b255c458e9776f15c134d93fab0aeafa80

* pass `request` to `makeClient` in entry.server.tsx

* delete file after merge

* lockfile

* Update packages/tanstack-start/README.md

Co-authored-by: Jerel Miller <[email protected]>

---------

Co-authored-by: phryneas <[email protected]>
Co-authored-by: Nick Muller <[email protected]>
Co-authored-by: Jerel Miller <[email protected]>
  • Loading branch information
4 people authored Jan 23, 2025
1 parent bfe287e commit 29875bd
Show file tree
Hide file tree
Showing 53 changed files with 11,817 additions and 468 deletions.
766 changes: 766 additions & 0 deletions .yarn/patches/@tanstack-react-router-npm-1.97.3-ad16343f36.patch

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions .yarn/patches/@tanstack-start-npm-1.97.3-da273c2822.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
diff --git a/dist/cjs/client/tsrScript.cjs b/dist/cjs/client/tsrScript.cjs
index 1b90ed6231c28246be7cc96ac546977d2b2d0442..f206b954ee627f53a177f974128a1e1be79473cb 100644
--- a/dist/cjs/client/tsrScript.cjs
+++ b/dist/cjs/client/tsrScript.cjs
@@ -1,4 +1,4 @@
"use strict";
-const minifiedScript = 'const __TSR__={matches:[],streamedValues:{},queue:[],runQueue:()=>{let e=!1;__TSR__.queue=__TSR__.queue.filter(r=>r()?(e=!0,!1):!0),e&&__TSR__.runQueue()},initMatch:e=>{__TSR__.queue.push(()=>(__TSR__.matches[e.index]||(__TSR__.matches[e.index]=e,Object.entries(e.extracted).forEach(([r,t])=>{if(t.type==="stream"){let a;t.value=new ReadableStream({start(o){a=o}}),t.value.controller=a}else if(t.type==="promise"){let a,o;t.value=new Promise((l,u)=>{o=u,a=l}),t.value.reject=o,t.value.resolve=a}})),!0)),__TSR__.runQueue()},resolvePromise:e=>{__TSR__.queue.push(()=>{const r=__TSR__.matches[e.matchIndex];if(r){const t=r.extracted[e.id];if(t&&t.type==="promise"&&t.value&&e.promiseState.status==="success")return t.value.resolve(e.promiseState.data),!0}return!1}),__TSR__.runQueue()},cleanScripts:()=>{document.querySelectorAll(".tsr-once").forEach(e=>{e.remove()})}};window.__TSR__=__TSR__;\n';
+const minifiedScript = 'const __TSR__={matches:[],streamedValues:{},setStreamedValue(e,r){__TSR__.streamedValues[e]={value:r},typeof __TSR__ROUTER__<"u"&&__TSR__ROUTER__.emit({type:"onStreamedValue",key:e})},queue:[],runQueue:()=>{let e=!1;__TSR__.queue=__TSR__.queue.filter(r=>r()?(e=!0,!1):!0),e&&__TSR__.runQueue()},initMatch:e=>{__TSR__.queue.push(()=>(__TSR__.matches[e.index]||(__TSR__.matches[e.index]=e,Object.entries(e.extracted).forEach(([r,t])=>{if(t.type==="stream"){let a;t.value=new ReadableStream({start(o){a=o}}),t.value.controller=a}else if(t.type==="promise"){let a,o;t.value=new Promise((l,u)=>{o=u,a=l}),t.value.reject=o,t.value.resolve=a}})),!0)),__TSR__.runQueue()},resolvePromise:e=>{__TSR__.queue.push(()=>{const r=__TSR__.matches[e.matchIndex];if(r){const t=r.extracted[e.id];if(t&&t.type==="promise"&&t.value&&e.promiseState.status==="success")return t.value.resolve(e.promiseState.data),!0}return!1}),__TSR__.runQueue()},cleanScripts:()=>{document.querySelectorAll(".tsr-once").forEach(e=>{e.remove()})}};window.__TSR__=__TSR__;\n';
module.exports = minifiedScript;
//# sourceMappingURL=tsrScript.cjs.map
diff --git a/dist/esm/client/tsrScript.js b/dist/esm/client/tsrScript.js
index eb4cd1eec8da7fee74455d9e5f6c5c634ffabd87..9ace36dd92f618e1f7be2bdf5c88adcfd19872e9 100644
--- a/dist/esm/client/tsrScript.js
+++ b/dist/esm/client/tsrScript.js
@@ -1,4 +1,4 @@
-const minifiedScript = 'const __TSR__={matches:[],streamedValues:{},queue:[],runQueue:()=>{let e=!1;__TSR__.queue=__TSR__.queue.filter(r=>r()?(e=!0,!1):!0),e&&__TSR__.runQueue()},initMatch:e=>{__TSR__.queue.push(()=>(__TSR__.matches[e.index]||(__TSR__.matches[e.index]=e,Object.entries(e.extracted).forEach(([r,t])=>{if(t.type==="stream"){let a;t.value=new ReadableStream({start(o){a=o}}),t.value.controller=a}else if(t.type==="promise"){let a,o;t.value=new Promise((l,u)=>{o=u,a=l}),t.value.reject=o,t.value.resolve=a}})),!0)),__TSR__.runQueue()},resolvePromise:e=>{__TSR__.queue.push(()=>{const r=__TSR__.matches[e.matchIndex];if(r){const t=r.extracted[e.id];if(t&&t.type==="promise"&&t.value&&e.promiseState.status==="success")return t.value.resolve(e.promiseState.data),!0}return!1}),__TSR__.runQueue()},cleanScripts:()=>{document.querySelectorAll(".tsr-once").forEach(e=>{e.remove()})}};window.__TSR__=__TSR__;\n';
+const minifiedScript = 'const __TSR__={matches:[],streamedValues:{},setStreamedValue(e,r){__TSR__.streamedValues[e]={value:r},typeof __TSR__ROUTER__<"u"&&__TSR__ROUTER__.emit({type:"onStreamedValue",key:e})},queue:[],runQueue:()=>{let e=!1;__TSR__.queue=__TSR__.queue.filter(r=>r()?(e=!0,!1):!0),e&&__TSR__.runQueue()},initMatch:e=>{__TSR__.queue.push(()=>(__TSR__.matches[e.index]||(__TSR__.matches[e.index]=e,Object.entries(e.extracted).forEach(([r,t])=>{if(t.type==="stream"){let a;t.value=new ReadableStream({start(o){a=o}}),t.value.controller=a}else if(t.type==="promise"){let a,o;t.value=new Promise((l,u)=>{o=u,a=l}),t.value.reject=o,t.value.resolve=a}})),!0)),__TSR__.runQueue()},resolvePromise:e=>{__TSR__.queue.push(()=>{const r=__TSR__.matches[e.matchIndex];if(r){const t=r.extracted[e.id];if(t&&t.type==="promise"&&t.value&&e.promiseState.status==="success")return t.value.resolve(e.promiseState.data),!0}return!1}),__TSR__.runQueue()},cleanScripts:()=>{document.querySelectorAll(".tsr-once").forEach(e=>{e.remove()})}};window.__TSR__=__TSR__;\n';
export {
minifiedScript as default
};
2 changes: 1 addition & 1 deletion examples/app-dir-experiments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"lint": "next lint"
},
"dependencies": {
"@apollo/client": "3.10.4",
"@apollo/client": "^3.12.6",
"@apollo/experimental-nextjs-app-support": "workspace:^",
"@apollo/server": "^4.9.5",
"@as-integrations/next": "^3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion examples/hack-the-supergraph-ssr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"lint": "next lint"
},
"dependencies": {
"@apollo/client": "3.10.4",
"@apollo/client": "^3.12.6",
"@apollo/experimental-nextjs-app-support": "workspace:^",
"@apollo/space-kit": "^9.11.0",
"@chakra-ui/next-js": "2.4.2",
Expand Down
2 changes: 1 addition & 1 deletion examples/polls-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"codegen": "graphql-codegen --config codegen.ts"
},
"dependencies": {
"@apollo/client": "3.10.4",
"@apollo/client": "^3.12.6",
"@apollo/experimental-nextjs-app-support": "workspace:^",
"@apollo/server": "^4.9.5",
"@types/node": "20.12.11",
Expand Down
2 changes: 2 additions & 0 deletions integration-test/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ yalc.lock
.next/
test-results/
webpack-stats.json
.output
.vinxi

This file was deleted.

1 change: 1 addition & 0 deletions integration-test/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"graphql": "^16.7.1",
"graphql-17-alpha2": "npm:[email protected]",
"graphql-tag": "^2.12.6",
"next": "^15.0.3",
"react": "^19.0.0",
Expand Down
10 changes: 8 additions & 2 deletions integration-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@
"@apollo/client-react-streaming": "exec:./shared/build-client-react-streaming.cjs",
"@apollo/experimental-nextjs-app-support": "exec:./shared/build-experimental-nextjs-app-support.cjs",
"@apollo/client-integration-react-router": "exec:./shared/build-client-integration-react-router.cjs",
"graphql": "17.0.0-alpha.2"
"@apollo/client-integration-tanstack-start": "exec:./shared/build-client-integration-tanstack-start.cjs",
"@apollo/client": "3.12.6",
"graphql": "17.0.0-alpha.2",
"turbo-stream": "npm:@phryneas/[email protected]",
"@tanstack/react-router": "patch:@tanstack/react-router@npm%3A1.97.3#~/../.yarn/patches/@tanstack-react-router-npm-1.97.3-ad16343f36.patch",
"@tanstack/start": "patch:@tanstack/start@npm%3A1.97.3#~/../.yarn/patches/@tanstack-start-npm-1.97.3-da273c2822.patch",
"@types/react": "19.0.2"
},
"workspaces": [
"*"
],
"scripts": {
"build:libs": "find . -regextype posix-extended -regex '.*/node_modules/@apollo/(client-react-streaming|experimental-nextjs-app-support|client-integration-react-router)' -printf 'rm -r %p\n' -exec rm -r {} +; glob \"../.yarn/cache/@apollo-*exec*\" \"$HOME/.yarn/berry/cache/@apollo-*exec*\" --cmd='rm -v' ; yarn"
"build:libs": "find . -regextype posix-extended -regex '.*/node_modules/@apollo/(client-react-streaming|experimental-nextjs-app-support|client-integration-react-router|client-integration-tanstack-start)' -printf 'rm -r %p\n' -exec rm -r {} +; glob \"../.yarn/cache/@apollo-*exec*\" \"$HOME/.yarn/berry/cache/@apollo-*exec*\" --cmd='rm -v' ; yarn"
},
"devDependencies": {
"glob": "^10.3.10",
Expand Down
51 changes: 2 additions & 49 deletions integration-test/react-router/app/routes/api.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,4 @@
import { ActionFunctionArgs } from "react-router";
import { experimentalExecuteIncrementally, parse } from "graphql";
import { schema } from "@integration-test/shared/schema";
import { apiRouteHandler } from "@integration-test/shared/apiRoute";

/**
* `@defer`-capable very crude implementation of a GraphQL server.
*/

export async function action({ request }: ActionFunctionArgs) {
const body = await request.json();
const { query, variables } = body;
const document = parse(query);

const result = await experimentalExecuteIncrementally({
schema,
document,
variableValues: variables,
contextValue: { from: "network" },
});

if (`initialResult` in result) {
const encoder = new TextEncoder();
const generator = async function* (): AsyncIterable<Uint8Array> {
yield encoder.encode(
"\r\n---\r\ncontent-type: application/json; charset=utf-8\r\n\r\n" +
JSON.stringify(result.initialResult) +
"\r\n---\r\n"
);
for await (const partialResult of result.subsequentResults) {
yield encoder.encode(
"content-type: application/json; charset=utf-8\r\n\r\n" +
JSON.stringify(partialResult) +
(partialResult.hasNext ? "\r\n---\r\n" : "\r\n-----\r\n")
);
}
};
return new Response(generator() as any, {
status: 200,
headers: {
"Content-Type": 'multipart/mixed; boundary="-"; deferSpec=20220824',
},
});
} else {
return new Response(JSON.stringify(result), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}
}
export const action = apiRouteHandler({ schema });
2 changes: 1 addition & 1 deletion integration-test/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"isbot": "^5.1.17",
"react": "^19",
"react-dom": "^19",
"react-router": "patch:react-router@npm%3A7.0.2#~/.yarn/patches/react-router-npm-7.0.2-b96f2bd13c.patch"
"react-router": "patch:react-router@npm%3A7.0.2#~/../.yarn/patches/react-router-npm-7.0.2-b96f2bd13c.patch"
},
"devDependencies": {
"@react-router/dev": "^7.0.1",
Expand Down
63 changes: 63 additions & 0 deletions integration-test/shared/apiRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
experimentalExecuteIncrementally,
GraphQLSchema,
parse,
} from "graphql";

/**
* `@defer`-capable very crude implementation of a GraphQL server.
*/

export function apiRouteHandler({
schema,
contextValue = { from: "network" },
}: {
schema: GraphQLSchema;
contextValue?: { from: string };
}) {
return async function handler({
request,
}: {
request: Request;
}): Promise<Response> {
const body = await request.json();
const { query, variables } = body;
const document = parse(query);
const result = await experimentalExecuteIncrementally({
schema,
document,
variableValues: variables,
contextValue,
});
if (`initialResult` in result) {
const encoder = new TextEncoder();
const generator = async function* (): AsyncIterable<Uint8Array> {
yield encoder.encode(
"\r\n---\r\ncontent-type: application/json; charset=utf-8\r\n\r\n" +
JSON.stringify(result.initialResult) +
"\r\n---\r\n"
);
for await (const partialResult of result.subsequentResults) {
yield encoder.encode(
"content-type: application/json; charset=utf-8\r\n\r\n" +
JSON.stringify(partialResult) +
(partialResult.hasNext ? "\r\n---\r\n" : "\r\n-----\r\n")
);
}
};
return new Response(generator() as any, {
status: 200,
headers: {
"Content-Type": 'multipart/mixed; boundary="-"; deferSpec=20220824',
},
});
} else {
return new Response(JSON.stringify(result), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
The integration tests need the latest version of the `@apollo/client-react-streaming` package.
This script can be used with the `exec:` protocol (https://yarnpkg.com/protocol/exec) to build
the package.
*/

const { join } = require("node:path");
const { $, cd, retry, sleep } = /** @type {typeof import('zx')} */ (
require(require.resolve("zx", { paths: [process.env.PROJECT_CWD] }))
);

$.stdio = "inherit";

(async function run() {
const monorepoRoot = join(process.env.PROJECT_CWD, "..");
const archive = join(monorepoRoot, "packages/tanstack-start/archive.tgz");

// give the main build script a chance to kick in
await sleep(1000);

await retry(120, "1s", () => $`test -f ${archive}`);

cd(monorepoRoot);
await $`tar -x -z --strip-components=1 -f ${archive} -C ${execEnv.buildDir}`;
})();
3 changes: 3 additions & 0 deletions integration-test/tanstack-start/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineConfig } from "@tanstack/start/config";

export default defineConfig({});
6 changes: 6 additions & 0 deletions integration-test/tanstack-start/app/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {
createStartAPIHandler,
defaultAPIFileRouteHandler,
} from "@tanstack/start/api";

export default createStartAPIHandler(defaultAPIFileRouteHandler);
8 changes: 8 additions & 0 deletions integration-test/tanstack-start/app/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/// <reference types="vinxi/types/client" />
import { hydrateRoot } from "react-dom/client";
import { StartClient } from "@tanstack/start";
import { createRouter } from "./router";

const router = createRouter();

hydrateRoot(document, <StartClient router={router} />);
Loading

0 comments on commit 29875bd

Please sign in to comment.