mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Add TanStack Start SDK integration
This commit is contained in:
parent
e831972c4c
commit
1db0f30675
@ -361,3 +361,6 @@ A: Invalid `tools` entries are rejected by `requestBodySchema` in `apps/backend/
|
||||
|
||||
## Q: Why did the internal metrics E2E snapshots need to change in April 2026?
|
||||
A: The `/api/v1/internal/metrics` response now intentionally includes `analytics_overview.daily_anonymous_visitors_fallback`, `analytics_overview.anonymous_visitors_fallback`, and `active_users_by_country`. Those additions are reflected in `packages/stack-shared/src/interface/admin-metrics.ts` and the backend route, so the E2E snapshots must include them instead of treating them as regressions.
|
||||
|
||||
## Q: How should a TanStack Start SDK package be added without dragging Dashboard V2 logic into the same PR?
|
||||
A: Keep the integration PR scoped to generated package registration (`packages/tanstack-start/package.json`, `.gitignore`, `scripts/generate-sdks.ts`, `scripts/utils.ts`), template/package dependency metadata, and SDK runtime changes needed by TanStack Start (`cookie.ts`, token-store handling, handler SSR guard). Leave dashboard routes, hooks, app wiring, and admin API types in the dashboard PR.
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -140,10 +140,12 @@ packages/js/*
|
||||
packages/react/*
|
||||
packages/next/*
|
||||
packages/stack/*
|
||||
packages/tanstack-start/*
|
||||
!packages/js/package.json
|
||||
!packages/react/package.json
|
||||
!packages/next/package.json
|
||||
!packages/stack/package.json
|
||||
!packages/tanstack-start/package.json
|
||||
|
||||
# claude code
|
||||
.claude/scheduled_tasks.lock
|
||||
|
||||
115
packages/tanstack-start/package.json
Normal file
115
packages/tanstack-start/package.json
Normal file
@ -0,0 +1,115 @@
|
||||
{
|
||||
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
|
||||
"name": "@stackframe/tanstack-start",
|
||||
"version": "2.8.86",
|
||||
"repository": "https://github.com/stack-auth/stack-auth",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/esm/index.js",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./convex.config": {
|
||||
"types": "./dist/integrations/convex/component/convex.config.d.ts",
|
||||
"import": "./dist/esm/integrations/convex/component/convex.config.js",
|
||||
"require": "./dist/integrations/convex/component/convex.config.js"
|
||||
},
|
||||
"./convex-auth.config": {
|
||||
"types": "./dist/integrations/convex.d.ts",
|
||||
"import": "./dist/esm/integrations/convex.js",
|
||||
"require": "./dist/integrations/convex.js"
|
||||
}
|
||||
},
|
||||
"homepage": "https://stack-auth.com",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rimraf dist && rimraf node_modules",
|
||||
"lint": "eslint --ext .tsx,.ts .",
|
||||
"build": "rimraf dist && pnpm run css && tsdown",
|
||||
"dev": "rimraf dist && concurrently -n \"build,codegen\" -k \"tsdown --watch\" \"pnpm run codegen:watch\"",
|
||||
"codegen": "pnpm run css",
|
||||
"codegen:watch": "pnpm run css:watch",
|
||||
"css": "pnpm run css-tw && pnpm run css-sc",
|
||||
"css:watch": "concurrently -n \"tw,sc\" -k \"pnpm run css-tw:watch\" \"pnpm run css-sc:watch\"",
|
||||
"css-tw:watch": "tailwindcss -i ./src/global.css -o ./src/generated/tailwind.css --watch",
|
||||
"css-tw": "tailwindcss -i ./src/global.css -o ./src/generated/tailwind.css",
|
||||
"css-sc": "tsx ./scripts/process-css.ts ./src/generated/tailwind.css ./src/generated/global-css.ts",
|
||||
"css-sc:watch": "chokidar --silent './src/generated/tailwind.css' -c 'pnpm run css-sc' --throttle 2000"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"dist",
|
||||
"CHANGELOG.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ai-sdk/react": "^3.0.72",
|
||||
"ai": "^6.0.0",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@stripe/react-stripe-js": "^3.8.1",
|
||||
"@stripe/stripe-js": "^7.7.0",
|
||||
"@simplewebauthn/browser": "^13.2.2",
|
||||
"@stackframe/stack-shared": "workspace:*",
|
||||
"@stackframe/stack-ui": "workspace:*",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"color": "^5.0.3",
|
||||
"cookie": "^1.1.1",
|
||||
"jose": "^6.1.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.378.0",
|
||||
"oauth4webapi": "^3.8.3",
|
||||
"@oslojs/otp": "^1.1.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react-easy-crop": "^5.5.6",
|
||||
"react-hook-form": "^7.70.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"rrweb": "^1.1.3",
|
||||
"tsx": "^4.21.0",
|
||||
"yup": "^1.7.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=18.3.0",
|
||||
"@tanstack/react-router": ">=1.100.0",
|
||||
"@tanstack/react-start": ">=1.100.0",
|
||||
"react": ">=18.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@tanstack/react-router": {
|
||||
"optional": true
|
||||
},
|
||||
"@tanstack/react-start": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quetzallabs/i18n": "^0.1.19",
|
||||
"@types/color": "^3.0.6",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react-avatar-editor": "^13.0.3",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"chokidar-cli": "^3.0.0",
|
||||
"esbuild": "^0.20.2",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-parser": "^9.0.2",
|
||||
"@tanstack/react-router": "^1.167.4",
|
||||
"@tanstack/react-start": "^1.166.15",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"rimraf": "^6.1.2",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tsdown": "^0.20.3",
|
||||
"convex": "^1.27.0"
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,8 @@
|
||||
"name": "@stackframe/js",
|
||||
"//": "ELSE_IF_PLATFORM next",
|
||||
"name": "@stackframe/stack",
|
||||
"//": "ELSE_IF_PLATFORM tanstack-start",
|
||||
"name": "@stackframe/tanstack-start",
|
||||
"//": "ELSE_IF_PLATFORM react",
|
||||
"name": "@stackframe/react",
|
||||
"//": "END_PLATFORM",
|
||||
@ -131,6 +133,10 @@
|
||||
"react-dom": ">=18.3.0",
|
||||
"next": ">=14.1 || >=15.0.0-canary.0 || >=15.0.0-rc.0",
|
||||
"//": "END_PLATFORM",
|
||||
"//": "IF_PLATFORM tanstack-start",
|
||||
"@tanstack/react-router": ">=1.100.0",
|
||||
"@tanstack/react-start": ">=1.100.0",
|
||||
"//": "END_PLATFORM",
|
||||
"react": ">=18.3.0"
|
||||
},
|
||||
"//": "END_PLATFORM",
|
||||
@ -141,6 +147,14 @@
|
||||
"optional": true
|
||||
},
|
||||
"//": "END_PLATFORM",
|
||||
"//": "IF_PLATFORM tanstack-start",
|
||||
"@tanstack/react-router": {
|
||||
"optional": true
|
||||
},
|
||||
"@tanstack/react-start": {
|
||||
"optional": true
|
||||
},
|
||||
"//": "END_PLATFORM",
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
@ -160,6 +174,10 @@
|
||||
"i18next-parser": "^9.0.2",
|
||||
"//": "NEXT_LINE_PLATFORM next",
|
||||
"next": "^14.2.35",
|
||||
"//": "NEXT_LINE_PLATFORM template tanstack-start",
|
||||
"@tanstack/react-router": "^1.167.4",
|
||||
"//": "NEXT_LINE_PLATFORM template tanstack-start",
|
||||
"@tanstack/react-start": "^1.166.15",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"react": "^19.0.0",
|
||||
|
||||
@ -94,12 +94,20 @@
|
||||
"@types/react-dom": ">=18.3.0",
|
||||
"react-dom": ">=18.3.0",
|
||||
"next": ">=14.1 || >=15.0.0-canary.0 || >=15.0.0-rc.0",
|
||||
"@tanstack/react-router": ">=1.100.0",
|
||||
"@tanstack/react-start": ">=1.100.0",
|
||||
"react": ">=18.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"@tanstack/react-router": {
|
||||
"optional": true
|
||||
},
|
||||
"@tanstack/react-start": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
@ -117,6 +125,8 @@
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-parser": "^9.0.2",
|
||||
"next": "^14.2.35",
|
||||
"@tanstack/react-router": "^1.167.4",
|
||||
"@tanstack/react-start": "^1.166.15",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"react": "^19.0.0",
|
||||
@ -127,4 +137,4 @@
|
||||
"tsdown": "^0.20.3",
|
||||
"convex": "^1.27.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +237,7 @@ export function StackHandlerClient(props: BaseHandlerProps & Partial<RouteProps>
|
||||
const navigateRef = useRef(navigate);
|
||||
navigateRef.current = navigate;
|
||||
const currentLocation = props.location ?? window.location.pathname;
|
||||
const searchParamsSource = new URLSearchParams(window.location.search);
|
||||
const searchParamsSource = new URLSearchParams(typeof window === "undefined" ? "" : window.location.search);
|
||||
const redirectTargets: (string | undefined)[] = [];
|
||||
END_PLATFORM */
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { cookies as rscCookies, headers as rscHeaders } from '@stackframe/stack-sc/force-react-server'; // THIS_LINE_PLATFORM next
|
||||
import { getCookie as tssGetCookie, getCookies as tssGetCookies, setCookie as tssSetCookie, deleteCookie as tssDeleteCookie, getRequestHeader as tssGetRequestHeader } from '@tanstack/react-start/server'; // THIS_LINE_PLATFORM tanstack-start
|
||||
import { isBrowserLike } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { StackAssertionError } from '@stackframe/stack-shared/dist/utils/errors';
|
||||
import Cookies from "js-cookie";
|
||||
@ -104,12 +105,73 @@ export async function createCookieHelper(): Promise<CookieHelper> {
|
||||
await rscCookies(),
|
||||
await rscHeaders(),
|
||||
);
|
||||
// ELSE_IF_PLATFORM tanstack-start
|
||||
return createTanStackStartCookieHelper();
|
||||
// ELSE_PLATFORM
|
||||
return await createPlaceholderCookieHelper();
|
||||
// END_PLATFORM
|
||||
}
|
||||
}
|
||||
|
||||
export function createCookieHelperSync(): CookieHelper {
|
||||
if (isBrowserLike()) {
|
||||
return createBrowserCookieHelper();
|
||||
}
|
||||
// IF_PLATFORM tanstack-start
|
||||
return createTanStackStartCookieHelper();
|
||||
// ELSE_PLATFORM
|
||||
function throwError(): never {
|
||||
throw new StackAssertionError("Synchronous server cookie helpers are not available on this platform");
|
||||
}
|
||||
return {
|
||||
get: throwError,
|
||||
getAll: throwError,
|
||||
set: throwError,
|
||||
setOrDelete: throwError,
|
||||
delete: throwError,
|
||||
};
|
||||
// END_PLATFORM
|
||||
}
|
||||
|
||||
// IF_PLATFORM tanstack-start
|
||||
function determineSecureFromTanStackStartContext(): boolean {
|
||||
return tssGetRequestHeader("x-forwarded-proto") === "https"
|
||||
|| (tssGetCookie("stack-is-https") !== undefined);
|
||||
}
|
||||
|
||||
function requiresSecureAttribute(name: string): boolean {
|
||||
return name.startsWith("__Host-");
|
||||
}
|
||||
|
||||
function createTanStackStartCookieHelper(): CookieHelper {
|
||||
const helper: CookieHelper = {
|
||||
get: (name: string) => tssGetCookie(name) ?? null,
|
||||
getAll: () => tssGetCookies(),
|
||||
set: (name: string, value: string, options: SetCookieOptions) => {
|
||||
tssSetCookie(name, value, {
|
||||
secure: options.secure ?? (requiresSecureAttribute(name) || determineSecureFromTanStackStartContext()),
|
||||
maxAge: options.maxAge === "session" ? undefined : options.maxAge,
|
||||
domain: options.domain,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
});
|
||||
},
|
||||
setOrDelete: (name, value, options) => {
|
||||
if (value === null) helper.delete(name, options);
|
||||
else helper.set(name, value, options);
|
||||
},
|
||||
delete: (name: string, options: DeleteCookieOptions) => {
|
||||
tssDeleteCookie(name, {
|
||||
secure: requiresSecureAttribute(name),
|
||||
domain: options.domain,
|
||||
path: "/",
|
||||
});
|
||||
},
|
||||
};
|
||||
return helper;
|
||||
}
|
||||
// END_PLATFORM
|
||||
|
||||
export function createBrowserCookieHelper(): CookieHelper {
|
||||
return {
|
||||
get: getCookieClient,
|
||||
@ -232,6 +294,8 @@ export async function isSecure(): Promise<boolean> {
|
||||
}
|
||||
// IF_PLATFORM next
|
||||
return determineSecureFromServerContext(await rscCookies(), await rscHeaders());
|
||||
// ELSE_IF_PLATFORM tanstack-start
|
||||
return determineSecureFromTanStackStartContext();
|
||||
// END_PLATFORM
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ import React, { useCallback, useMemo } from "react"; // THIS_LINE_PLATFORM react
|
||||
import type * as yup from "yup";
|
||||
import { constructRedirectUrl } from "../../../../utils/url";
|
||||
import { getNewOAuthProviderOrScopeUrl, callOAuthCallback } from "../../../auth";
|
||||
import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, isSecure as isSecureCookieContext, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
|
||||
import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createCookieHelperSync, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, isSecure as isSecureCookieContext, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
|
||||
import { envVars } from "../../../env";
|
||||
import { ApiKey, ApiKeyCreationOptions, ApiKeyUpdateOptions, apiKeyCreationOptionsToCrud } from "../../api-keys";
|
||||
import { ConvexCtx, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrlOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, RequestLike, ResolvedHandlerUrls, TokenStoreInit, stackAppInternalsSymbol } from "../../common";
|
||||
@ -930,6 +930,11 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
|
||||
|
||||
switch (tokenStoreInit) {
|
||||
case "cookie": {
|
||||
// IF_PLATFORM tanstack-start
|
||||
if (!isBrowserLike()) {
|
||||
return this._getOrCreateTokenStore(cookieHelper, "nextjs-cookie");
|
||||
}
|
||||
// END_PLATFORM
|
||||
return this._getBrowserCookieTokenStore();
|
||||
}
|
||||
case "nextjs-cookie": {
|
||||
@ -1024,6 +1029,11 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
|
||||
|
||||
// IF_PLATFORM react-like
|
||||
protected _useTokenStore(overrideTokenStoreInit?: TokenStoreInit): Store<TokenObject> {
|
||||
// IF_PLATFORM tanstack-start
|
||||
if (!isBrowserLike()) {
|
||||
return this._getOrCreateTokenStore(createCookieHelperSync(), overrideTokenStoreInit);
|
||||
}
|
||||
// END_PLATFORM
|
||||
suspendIfSsr();
|
||||
const cookieHelper = createBrowserCookieHelper();
|
||||
const tokenStore = this._getOrCreateTokenStore(cookieHelper, overrideTokenStoreInit);
|
||||
|
||||
1093
pnpm-lock.yaml
1093
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -191,6 +191,14 @@ withGeneratorLock(async () => {
|
||||
return baseEditFn({ relativePath, content, platforms: PLATFORMS["react"] });
|
||||
},
|
||||
});
|
||||
|
||||
generateFromTemplate({
|
||||
src: srcDir,
|
||||
dest: path.resolve(baseDir, "tanstack-start"),
|
||||
editFn: (relativePath, content) => {
|
||||
return baseEditFn({ relativePath, content, platforms: PLATFORMS["tanstack-start"] });
|
||||
},
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
|
||||
@ -9,7 +9,8 @@ export const PLATFORMS = {
|
||||
"next": ['next', 'react-like', 'js-like'],
|
||||
"js": ['js', 'js-like'],
|
||||
"react": ['react', 'react-like', 'js-like'],
|
||||
"template": ['template', 'react-like', 'next', 'js', 'js-like', 'python-like'],
|
||||
"tanstack-start": ['tanstack-start', 'react', 'react-like', 'js-like'],
|
||||
"template": ['template', 'react-like', 'next', 'js', 'js-like', 'python-like', 'tanstack-start'],
|
||||
"python": ['python', 'python-like'],
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"STACK_*",
|
||||
"CRON_SECRET",
|
||||
"NEXT_PUBLIC_*",
|
||||
"VITE_*",
|
||||
"NEXT_PUBLIC_SENTRY_*",
|
||||
"SENTRY_*",
|
||||
"VERCEL_GIT_COMMIT_SHA",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user