├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml └── workflows │ ├── pr.yml │ └── ci.yml ├── .npmrc ├── examples ├── astro-react │ ├── src │ │ ├── env.d.ts │ │ ├── app │ │ │ ├── secret.secret$.ts │ │ │ ├── entry-client.tsx │ │ │ ├── manifest.tsx │ │ │ ├── entry-server.tsx │ │ │ └── root.tsx │ │ └── pages │ │ │ ├── [...app].ts │ │ │ └── hello.astro │ ├── tsconfig.json │ ├── .vscode │ │ ├── extensions.json │ │ └── launch.json │ ├── .gitignore │ ├── astro.config.mjs │ ├── package.json │ ├── public │ │ └── favicon.svg │ └── README.md ├── astro-solid │ ├── src │ │ ├── env.d.ts │ │ ├── app │ │ │ ├── server.secret$.ts │ │ │ ├── manifest.tsx │ │ │ ├── entry-client.tsx │ │ │ ├── entry-server.tsx │ │ │ └── root.tsx │ │ └── pages │ │ │ ├── [...app].ts │ │ │ └── hello.astro │ ├── .vscode │ │ ├── extensions.json │ │ └── launch.json │ ├── tsconfig.json │ ├── .gitignore │ ├── astro.config.mjs │ ├── package.json │ ├── public │ │ └── favicon.svg │ └── README.md ├── astro-react-router │ ├── src │ │ ├── env.d.ts │ │ ├── app │ │ │ ├── secret.secret$.ts │ │ │ ├── manifest.tsx │ │ │ ├── entry-client.tsx │ │ │ ├── entry-server.tsx │ │ │ └── root.tsx │ │ └── pages │ │ │ └── [...app].ts │ ├── tsconfig.json │ ├── .vscode │ │ ├── extensions.json │ │ └── launch.json │ ├── .gitignore │ ├── astro.config.mjs │ ├── package.json │ ├── public │ │ └── favicon.svg │ └── README.md ├── astro-solid-router │ ├── src │ │ ├── env.d.ts │ │ ├── pages │ │ │ ├── [...app].ts │ │ │ └── hello.astro │ │ └── app │ │ │ ├── manifest.tsx │ │ │ ├── data │ │ │ ├── index.ts │ │ │ ├── FormError.tsx │ │ │ ├── LICENSE.remix.md │ │ │ ├── useLoader.ts │ │ │ ├── useAction.tsx │ │ │ └── Form.tsx │ │ │ ├── entry-client.tsx │ │ │ ├── entry-server.tsx │ │ │ └── root.tsx │ ├── .vscode │ │ ├── extensions.json │ │ └── launch.json │ ├── tsconfig.json │ ├── .gitignore │ ├── astro.config.mjs │ ├── package.json │ ├── public │ │ └── favicon.svg │ └── README.md ├── astro-react-todomvc │ ├── src │ │ ├── env.d.ts │ │ ├── app │ │ │ ├── db.secret$.ts │ │ │ ├── manifest.tsx │ │ │ ├── entry-client.tsx │ │ │ ├── entry-server.tsx │ │ │ └── root.tsx │ │ ├── pages │ │ │ └── [...app].ts │ │ └── app.css │ ├── tsconfig.json │ ├── prisma │ │ ├── dev.db │ │ └── schema.prisma │ ├── .vscode │ │ ├── extensions.json │ │ └── launch.json │ ├── .gitignore │ ├── astro.config.mjs │ ├── public │ │ └── favicon.svg │ ├── package.json │ └── README.md └── astro-solid-hackernews │ ├── src │ ├── env.d.ts │ ├── pages │ │ ├── [...app].ts │ │ └── hello.astro │ └── app │ │ ├── manifest.tsx │ │ ├── types.ts │ │ ├── entry-client.tsx │ │ ├── components │ │ ├── toggle.tsx │ │ ├── nav.tsx │ │ ├── comment.tsx │ │ └── story.tsx │ │ ├── entry-server.tsx │ │ ├── root.css │ │ └── root.tsx │ ├── .vscode │ ├── extensions.json │ └── launch.json │ ├── tsconfig.json │ ├── .gitignore │ ├── astro.config.mjs │ ├── package.json │ ├── public │ └── favicon.svg │ └── README.md ├── pnpm-workspace.yaml ├── scripts ├── build.ts ├── builder.ts ├── tsconfig.json ├── config.ts └── types.ts ├── media ├── header.png ├── logo.sketch ├── repo-dark.png └── logo.svg ├── .prettierrc ├── CONTRIBUTING.md ├── docs ├── overview.md ├── installation.md └── config.json ├── packages └── bling │ ├── tsconfig.json │ ├── src │ ├── astro.ts │ ├── types.ts │ ├── vite.ts │ ├── client.ts │ ├── utils │ │ └── utils.ts │ └── server.ts │ └── package.json ├── .gitignore ├── LICENSE ├── package.json └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: tannerlinsley 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} -------------------------------------------------------------------------------- /examples/astro-react/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/astro-solid/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | - "examples/*" 4 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { builder } from './builder' 2 | 3 | builder() 4 | -------------------------------------------------------------------------------- /examples/astro-react-router/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /media/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanStack/bling/HEAD/media/header.png -------------------------------------------------------------------------------- /examples/astro-react-todomvc/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /media/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanStack/bling/HEAD/media/logo.sketch -------------------------------------------------------------------------------- /media/repo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanStack/bling/HEAD/media/repo-dark.png -------------------------------------------------------------------------------- /examples/astro-react-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest" 3 | } -------------------------------------------------------------------------------- /examples/astro-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest" 3 | } 4 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest" 3 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /examples/astro-react/src/app/secret.secret$.ts: -------------------------------------------------------------------------------- 1 | export const secret = 'This is a server-only secret!' 2 | -------------------------------------------------------------------------------- /examples/astro-solid/src/app/server.secret$.ts: -------------------------------------------------------------------------------- 1 | export const secret = 'This is a server-only secret!' 2 | -------------------------------------------------------------------------------- /examples/astro-react-router/src/app/secret.secret$.ts: -------------------------------------------------------------------------------- 1 | export const secret = 'This is a server-only secret!' 2 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/prisma/dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanStack/bling/HEAD/examples/astro-react-todomvc/prisma/dev.db -------------------------------------------------------------------------------- /examples/astro-react/src/pages/[...app].ts: -------------------------------------------------------------------------------- 1 | import { requestHandler } from '../app/entry-server' 2 | 3 | export const all = requestHandler 4 | -------------------------------------------------------------------------------- /examples/astro-solid/src/pages/[...app].ts: -------------------------------------------------------------------------------- 1 | import { requestHandler } from '../app/entry-server' 2 | 3 | export const all = requestHandler 4 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/src/app/db.secret$.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | export const prisma = new PrismaClient() 3 | -------------------------------------------------------------------------------- /examples/astro-react/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /examples/astro-react/src/pages/hello.astro: -------------------------------------------------------------------------------- 1 | --- 2 | console.log('hello') 3 | --- 4 | 5 | 6 |
Hello!
7 | 8 | -------------------------------------------------------------------------------- /examples/astro-solid/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /examples/astro-solid/src/pages/hello.astro: -------------------------------------------------------------------------------- 1 | --- 2 | console.log('hello') 3 | --- 4 | 5 | 6 |
Hello!
7 | 8 | -------------------------------------------------------------------------------- /examples/astro-react-router/src/pages/[...app].ts: -------------------------------------------------------------------------------- 1 | import { requestHandler } from '../app/entry-server' 2 | 3 | export const all = requestHandler 4 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/src/pages/[...app].ts: -------------------------------------------------------------------------------- 1 | import { requestHandler } from '../app/entry-server' 2 | 3 | export const all = requestHandler 4 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/pages/[...app].ts: -------------------------------------------------------------------------------- 1 | import { requestHandler } from '../app/entry-server' 2 | 3 | export const all = requestHandler 4 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/pages/[...app].ts: -------------------------------------------------------------------------------- 1 | import { requestHandler } from '../app/entry-server' 2 | 3 | export const all = requestHandler 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | - Clone the repo 4 | - Make changes 5 | - Run `npm run build` to build the dist files 6 | - Submit a pull request 7 | -------------------------------------------------------------------------------- /examples/astro-react-router/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /examples/astro-solid-router/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/pages/hello.astro: -------------------------------------------------------------------------------- 1 | --- 2 | console.log('hello') 3 | --- 4 | 5 | 6 |
Hello!
7 | 8 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/pages/hello.astro: -------------------------------------------------------------------------------- 1 | --- 2 | console.log('hello') 3 | --- 4 | 5 | 6 |
Hello!
7 | 8 | -------------------------------------------------------------------------------- /examples/astro-react/src/app/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import * as ReactDOM from 'react-dom/client' 2 | import { App } from './root' 3 | 4 | ReactDOM.hydrateRoot(document, ) 5 | -------------------------------------------------------------------------------- /examples/astro-solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "jsxImportSource": "solid-js" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "jsxImportSource": "solid-js" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | --- 4 | 5 | TanStack Bling is a collection of framework agnostic utilities for client/server RPCs, env isolation, islands, module splitting, and more. 6 | -------------------------------------------------------------------------------- /examples/astro-react/src/app/manifest.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | export const manifestContext = createContext<{ 'entry-client': string }>({ 4 | 'entry-client': '', 5 | }) 6 | -------------------------------------------------------------------------------- /examples/astro-solid-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "jsxImportSource": "solid-js" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/astro-solid/src/app/manifest.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'solid-js' 2 | 3 | export const manifestContext = createContext<{ 'entry-client': string }>({ 4 | 'entry-client': '', 5 | }) 6 | -------------------------------------------------------------------------------- /examples/astro-react-router/src/app/manifest.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | export const manifestContext = createContext<{ 'entry-client': string }>({ 4 | 'entry-client': '', 5 | }) 6 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/src/app/manifest.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | export const manifestContext = createContext<{ 'entry-client': string }>({ 4 | 'entry-client': '', 5 | }) 6 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/app/manifest.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'solid-js' 2 | 3 | export const manifestContext = createContext<{ 'entry-client': string }>({ 4 | 'entry-client': '', 5 | }) 6 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | --- 4 | 5 | You can install TanStack Bling with any [NPM](https://npmjs.com) package manager. 6 | 7 | ```bash 8 | npm install @tanstack/bling 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/manifest.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'solid-js' 2 | 3 | export const manifestContext = createContext<{ 'entry-client': string }>({ 4 | 'entry-client': '', 5 | }) 6 | -------------------------------------------------------------------------------- /examples/astro-react/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/astro-solid/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/astro-react-router/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/astro-solid-router/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/astro-react/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /examples/astro-solid/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /examples/astro-solid/src/app/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import { hydrate } from 'solid-js/web' 2 | import { manifestContext } from './manifest' 3 | import { App } from './root' 4 | 5 | hydrate( 6 | () => ( 7 | 8 | 9 | 10 | ), 11 | document, 12 | ) 13 | -------------------------------------------------------------------------------- /examples/astro-react-router/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /examples/astro-solid-router/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/app/data/index.ts: -------------------------------------------------------------------------------- 1 | export { useRouteData } from '@solidjs/router' 2 | export { useAction, useMultiAction as useMultiAction } from './useAction' 3 | export { useLoader, refetchLoaders } from './useLoader' 4 | export type { FormAction, FormMethod, FormProps, SubmitOptions } from './Form' 5 | export { FormError, ServerError } from './FormError' 6 | -------------------------------------------------------------------------------- /packages/bling/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "DOM.Iterable", "ES6"], 4 | "target": "ESNext", 5 | "module": "ES2020", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "outDir": "./dist", 9 | "skipLibCheck": true, 10 | "typeRoots": ["./node_modules/@types", "./src/@types"] 11 | }, 12 | "include": ["src"] 13 | } 14 | -------------------------------------------------------------------------------- /examples/astro-react-router/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import react from '@astrojs/react' 3 | import node from '@astrojs/node' 4 | import { astroBling } from '@tanstack/bling/astro' 5 | 6 | export default defineConfig({ 7 | output: 'server', 8 | adapter: node({ 9 | mode: 'standalone', 10 | }), 11 | integrations: [astroBling(), react()], 12 | }) 13 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import react from '@astrojs/react' 3 | import node from '@astrojs/node' 4 | import { astroBling } from '@tanstack/bling/astro' 5 | 6 | export default defineConfig({ 7 | output: 'server', 8 | adapter: node({ 9 | mode: 'standalone', 10 | }), 11 | integrations: [astroBling(), react()], 12 | }) 13 | -------------------------------------------------------------------------------- /examples/astro-react/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import react from '@astrojs/react' 3 | import node from '@astrojs/node' 4 | import { astroBling } from '@tanstack/bling/astro' 5 | 6 | export default defineConfig({ 7 | output: 'server', 8 | adapter: node({ 9 | mode: 'standalone', 10 | }), 11 | 12 | integrations: [astroBling(), react()], 13 | }) 14 | -------------------------------------------------------------------------------- /examples/astro-solid/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import node from '@astrojs/node' 3 | import { astroBling } from '@tanstack/bling/astro' 4 | import solidJs from '@astrojs/solid-js' 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | output: 'server', 9 | adapter: node({ 10 | mode: 'standalone', 11 | }), 12 | integrations: [astroBling(), solidJs()], 13 | }) 14 | -------------------------------------------------------------------------------- /examples/astro-solid-router/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import node from '@astrojs/node' 3 | import { astroBling } from '@tanstack/bling/astro' 4 | import solidJs from '@astrojs/solid-js' 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | output: 'server', 9 | adapter: node({ 10 | mode: 'standalone', 11 | }), 12 | integrations: [astroBling(), solidJs()], 13 | }) 14 | -------------------------------------------------------------------------------- /scripts/builder.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import { packages } from './config' 3 | import { Package } from './types' 4 | 5 | export async function builder() { 6 | await Promise.all( 7 | packages.map(async (pkg) => { 8 | buildPackage(pkg) 9 | }) 10 | ) 11 | } 12 | 13 | function buildPackage(pkg: Package) { 14 | execSync(`cd packages/${pkg.packageDir} && yarn build`, { stdio: 'inherit' }) 15 | } 16 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import node from '@astrojs/node' 3 | import { astroBling } from '@tanstack/bling/astro' 4 | import solidJs from '@astrojs/solid-js' 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | output: 'server', 9 | adapter: node({ 10 | mode: 'standalone', 11 | }), 12 | integrations: [astroBling(), solidJs()], 13 | }) 14 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/types.ts: -------------------------------------------------------------------------------- 1 | export interface IComment { 2 | user: string; 3 | time_ago: string; 4 | content: string; 5 | comments: IComment[]; 6 | } 7 | 8 | export interface IStory { 9 | id: string; 10 | points: string; 11 | url: string; 12 | title: string; 13 | domain: string; 14 | type: string; 15 | time_ago: string; 16 | user: string; 17 | comments_count: number; 18 | comments: IComment[]; 19 | } 20 | -------------------------------------------------------------------------------- /examples/astro-react-router/src/app/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import { createBrowserRouter, RouterProvider } from 'react-router-dom' 3 | import { routes } from './root' 4 | import { addSerializer } from '@tanstack/bling/client' 5 | 6 | addSerializer({ 7 | apply: (req) => req instanceof Request, 8 | serialize: (value) => '$request', 9 | }) 10 | 11 | let router = createBrowserRouter(routes) 12 | 13 | ReactDOM.hydrateRoot(document, ) 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Feature Requests & Questions 4 | url: https://github.com/tanstack/router/discussions 5 | about: Please ask and answer questions here. 6 | - name: 💬 Community Chat 7 | url: https://discord.gg/mQd7egN 8 | about: A dedicated discord server hosted by Tanner Linsley 9 | - name: 💬 Tanstack Twitter 10 | url: https://twitter.com/tan_stack 11 | about: Stay up to date with new releases of our libraries 12 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "sqlite" 10 | url = "file:./dev.db" 11 | } 12 | 13 | model Todo { 14 | id String @id @default(cuid()) 15 | title String 16 | complete Boolean 17 | createdAt DateTime @default(now()) 18 | updatedAt DateTime @updatedAt 19 | } 20 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "skipLibCheck": true, 10 | "checkJs": true, 11 | "types": ["node"] 12 | }, 13 | "ts-node": { 14 | "transpileOnly": true, 15 | "files": true, 16 | "compilerOptions": { 17 | "sourceMap": true, 18 | "inlineSources": true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/app/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import { Router, useRoutes } from '@solidjs/router' 2 | import { hydrate } from 'solid-js/web' 3 | import { manifestContext } from './manifest' 4 | import { manifest } from 'astro:ssr-manifest' 5 | import { routes } from './root' 6 | 7 | hydrate(() => { 8 | const Routes = useRoutes(routes) 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | }, document) 17 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import { Router, useRoutes } from '@solidjs/router' 2 | import { hydrate } from 'solid-js/web' 3 | import { manifestContext } from './manifest' 4 | import { manifest } from 'astro:ssr-manifest' 5 | import { routes } from './root' 6 | 7 | hydrate(() => { 8 | const Routes = useRoutes(routes) 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | }, document) 17 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/components/toggle.tsx: -------------------------------------------------------------------------------- 1 | import { createSignal } from "solid-js"; 2 | 3 | export default function Toggle(props: { children: any }) { 4 | const [open, setOpen] = createSignal(true); 5 | 6 | return ( 7 | <> 8 | 11 |
    12 | {props.children} 13 |
14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /docs/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "docSearch": { 3 | "appId": "", 4 | "apiKey": "", 5 | "indexName": "" 6 | }, 7 | "menu": [ 8 | { 9 | "label": "Getting Started", 10 | "children": [ 11 | { 12 | "label": "Overview", 13 | "to": "overview" 14 | }, 15 | { 16 | "label": "Installation", 17 | "to": "installation" 18 | } 19 | ] 20 | }, 21 | { 22 | "label": "Guide", 23 | "children": [] 24 | }, 25 | { 26 | "label": "API", 27 | "children": [] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/src/app/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import { createBrowserRouter, RouterProvider } from 'react-router-dom' 3 | import { routes } from './root' 4 | import { addSerializer } from '@tanstack/bling/client' 5 | import { QueryClientProvider } from '@tanstack/react-query' 6 | 7 | addSerializer({ 8 | apply: (req) => req instanceof Request, 9 | serialize: (value) => '$request', 10 | }) 11 | 12 | let router = createBrowserRouter(routes) 13 | 14 | function Client() { 15 | return 16 | } 17 | 18 | ReactDOM.hydrateRoot(document, ) 19 | -------------------------------------------------------------------------------- /examples/astro-solid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-astro-solid", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "npm run dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/netlify": "^1.2.1", 15 | "@astrojs/node": "^5.0.3", 16 | "@astrojs/solid-js": "^1.2.3", 17 | "@babel/generator": "^7.21.1", 18 | "@tanstack/bling": "^0.1.3", 19 | "astro": "0.0.0-ssr-manifest-20230306183729", 20 | "concurrently": "^7.6.0", 21 | "solid-js": "^1.4.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/astro-solid-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-astro-solid-router", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "npm run dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/netlify": "^1.2.1", 15 | "@astrojs/node": "^5.0.3", 16 | "@astrojs/solid-js": "^1.2.3", 17 | "@babel/generator": "^7.21.1", 18 | "@solidjs/router": "^0.7.0", 19 | "@tanstack/bling": "^0.1.3", 20 | "astro": "0.0.0-ssr-manifest-20230306183729", 21 | "concurrently": "^7.6.0", 22 | "solid-js": "^1.4.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-astro-solid-hackernews", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "npm run dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/netlify": "^1.2.1", 15 | "@astrojs/node": "^5.0.3", 16 | "@astrojs/solid-js": "^1.2.3", 17 | "@babel/generator": "^7.21.1", 18 | "@solidjs/router": "^0.7.0", 19 | "@tanstack/bling": "^0.1.3", 20 | "astro": "0.0.0-ssr-manifest-20230306183729", 21 | "concurrently": "^7.6.0", 22 | "solid-js": "^1.4.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vite 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | types 9 | build 10 | */build 11 | dist 12 | lib 13 | es 14 | artifacts 15 | .rpt2_cache 16 | coverage 17 | *.tgz 18 | 19 | # misc 20 | .DS_Store 21 | .env 22 | .env.local 23 | .env.development.local 24 | .env.test.local 25 | .env.production.local 26 | .next 27 | 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .history 32 | size-plugin.json 33 | stats-hydration.json 34 | stats-react.json 35 | stats.html 36 | .vscode/settings.json 37 | 38 | *.log 39 | .DS_Store 40 | node_modules 41 | .cache 42 | dist 43 | ts-perf 44 | 45 | /examples/*/*/yarn.lock 46 | /examples/*/*/package-lock.json 47 | 48 | .netlify 49 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/components/nav.tsx: -------------------------------------------------------------------------------- 1 | import { A } from "@solidjs/router"; 2 | 3 | function Nav() { 4 | return ( 5 |
6 | 26 |
27 | ); 28 | } 29 | 30 | export default Nav; 31 | -------------------------------------------------------------------------------- /examples/astro-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-astro-react", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "npm run dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/netlify": "^1.2.1", 15 | "@astrojs/node": "^5.0.3", 16 | "@astrojs/react": "^1.2.2", 17 | "@babel/generator": "^7.21.1", 18 | "@tanstack/bling": "^0.1.3", 19 | "@vitejs/plugin-react": "^3.1.0", 20 | "astro": "0.0.0-ssr-manifest-20230306183729", 21 | "concurrently": "^7.6.0", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0" 24 | }, 25 | "devDependencies": { 26 | "@types/react": "^18.0.28", 27 | "@types/react-dom": "^18.0.11" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/astro-solid/src/app/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { hasHandler, handleFetch$ } from '@tanstack/bling/server' 2 | import type { APIContext } from 'astro' 3 | import { renderToStringAsync } from 'solid-js/web' 4 | import { App } from './root' 5 | import { manifest } from 'astro:ssr-manifest' 6 | import { manifestContext } from './manifest' 7 | 8 | export const requestHandler = async ({ request }: APIContext) => { 9 | if (hasHandler(new URL(request.url).pathname)) { 10 | return await handleFetch$({ 11 | request, 12 | }) 13 | } 14 | 15 | return new Response( 16 | await renderToStringAsync(() => ( 17 | 18 | 19 | 20 | )), 21 | { 22 | headers: { 23 | 'content-type': 'text/html', 24 | }, 25 | }, 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /examples/astro-react/src/app/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { hasHandler, handleFetch$ } from '@tanstack/bling/server' 2 | import type { APIContext } from 'astro' 3 | import * as ReactDOM from 'react-dom/server.browser' 4 | import { App } from './root' 5 | import { manifest } from 'astro:ssr-manifest' 6 | import { manifestContext } from './manifest' 7 | 8 | export const requestHandler = async ({ request }: APIContext) => { 9 | if (hasHandler(new URL(request.url).pathname)) { 10 | return await handleFetch$({ 11 | request, 12 | }) 13 | } 14 | 15 | return new Response( 16 | await ReactDOM.renderToReadableStream( 17 | 18 | 19 | , 20 | ), 21 | { 22 | headers: { 23 | 'content-type': 'text/html', 24 | }, 25 | }, 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/components/comment.tsx: -------------------------------------------------------------------------------- 1 | import { Component, For, Show } from 'solid-js' 2 | import { A } from '@solidjs/router' 3 | import type { IComment } from '../types' 4 | import Toggle from './toggle' 5 | 6 | const Comment: Component<{ comment: IComment }> = (props) => { 7 | return ( 8 |
  • 9 |
    10 | {props.comment.user}{' '} 11 | {props.comment.time_ago} ago 12 |
    13 |
    14 | 15 | 16 | 17 | {(comment) => } 18 | 19 | 20 | 21 |
  • 22 | ) 23 | } 24 | 25 | export default Comment 26 | -------------------------------------------------------------------------------- /scripts/config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { BranchConfig, Package } from './types' 3 | 4 | // TODO: List your npm packages here. 5 | export const packages: Package[] = [ 6 | { 7 | name: '@tanstack/bling', 8 | packageDir: 'bling', 9 | srcDir: 'src', 10 | }, 11 | ] 12 | 13 | export const latestBranch = 'main' 14 | 15 | export const branchConfigs: Record = { 16 | main: { 17 | prerelease: false, 18 | ghRelease: true, 19 | }, 20 | next: { 21 | prerelease: true, 22 | ghRelease: true, 23 | }, 24 | beta: { 25 | prerelease: true, 26 | ghRelease: true, 27 | }, 28 | alpha: { 29 | prerelease: true, 30 | ghRelease: true, 31 | }, 32 | } 33 | 34 | export const rootDir = path.resolve(__dirname, '..') 35 | export const examplesDirs = [ 36 | // 'examples/react', 37 | // 'examples/solid', 38 | // 'examples/svelte', 39 | // 'examples/vue', 40 | ] 41 | -------------------------------------------------------------------------------- /examples/astro-react-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-astro-react-router", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "npm run dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/netlify": "^1.2.1", 15 | "@astrojs/node": "^5.0.3", 16 | "@astrojs/react": "^1.2.2", 17 | "@babel/generator": "^7.21.1", 18 | "@remix-run/router": "^1.3.3", 19 | "@tanstack/bling": "^0.1.3", 20 | "@vitejs/plugin-react": "^3.1.0", 21 | "astro": "0.0.0-ssr-manifest-20230306183729", 22 | "concurrently": "^7.6.0", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0", 25 | "react-router-dom": "^6.8.2" 26 | }, 27 | "devDependencies": { 28 | "@types/react": "^18.0.28", 29 | "@types/react-dom": "^18.0.11" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/astro-react/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /examples/astro-solid/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /scripts/types.ts: -------------------------------------------------------------------------------- 1 | export type Commit = { 2 | commit: CommitOrTree 3 | tree: CommitOrTree 4 | author: AuthorOrCommitter 5 | committer: AuthorOrCommitter 6 | subject: string 7 | body: string 8 | parsed: Parsed 9 | } 10 | 11 | export type CommitOrTree = { 12 | long: string 13 | short: string 14 | } 15 | 16 | export type AuthorOrCommitter = { 17 | name: string 18 | email: string 19 | date: string 20 | } 21 | 22 | export type Parsed = { 23 | type: string 24 | scope?: string | null 25 | subject: string 26 | merge?: null 27 | header: string 28 | body?: null 29 | footer?: null 30 | notes?: null[] | null 31 | references?: null[] | null 32 | mentions?: null[] | null 33 | revert?: null 34 | raw: string 35 | } 36 | 37 | export type Package = { 38 | name: string 39 | packageDir: string 40 | srcDir: string 41 | } 42 | 43 | export type BranchConfig = { 44 | prerelease: boolean 45 | ghRelease: boolean 46 | } 47 | -------------------------------------------------------------------------------- /examples/astro-react-router/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /examples/astro-solid-router/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/app/data/FormError.tsx: -------------------------------------------------------------------------------- 1 | export class ServerError extends Error { 2 | status: number; 3 | constructor(message: string, { status, stack }: { status?: number; stack?: string } = {}) { 4 | super(message); 5 | this.name = "ServerError"; 6 | this.status = status || 400 7 | if (stack) { 8 | this.stack = stack; 9 | } 10 | } 11 | } 12 | 13 | export class FormError extends ServerError { 14 | formError?: string; 15 | fields?: {}; 16 | fieldErrors?: { [key: string]: string }; 17 | constructor( 18 | message: string, 19 | { 20 | fieldErrors = {}, 21 | form, 22 | fields, 23 | stack 24 | }: { fieldErrors?: {}; form?: FormData; fields?: {}; stack?: string } = {} 25 | ) { 26 | super(message, { stack }); 27 | this.formError = message; 28 | this.name = "FormError"; 29 | this.fields = 30 | fields || Object.fromEntries(typeof form !== "undefined" ? form.entries() : []) || {}; 31 | this.fieldErrors = fieldErrors; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: pr 2 | on: [pull_request] 3 | jobs: 4 | test: 5 | name: 'Test' 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | with: 10 | fetch-depth: '0' 11 | - uses: pnpm/action-setup@v2 12 | name: Install pnpm 13 | id: pnpm-install 14 | with: 15 | version: 7 16 | run_install: false 17 | 18 | - name: Get pnpm store directory 19 | id: pnpm-cache 20 | shell: bash 21 | run: | 22 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 23 | 24 | - uses: actions/cache@v3 25 | name: Setup pnpm cache 26 | with: 27 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 28 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 29 | restore-keys: | 30 | ${{ runner.os }}-pnpm-store- 31 | 32 | - name: Build & Test 33 | run: | 34 | pnpm install --no-frozen-lockfile 35 | pnpm build 36 | pnpm test 37 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-bling", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "npm run dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/netlify": "^1.2.1", 15 | "@astrojs/node": "^5.0.3", 16 | "@astrojs/react": "^1.2.2", 17 | "@babel/generator": "^7.21.1", 18 | "@prisma/client": "^4.11.0", 19 | "@remix-run/router": "^1.3.3", 20 | "@tanstack/bling": "^0.1.3", 21 | "@tanstack/react-query": "^4.26.0", 22 | "@vitejs/plugin-react": "^3.1.0", 23 | "astro": "0.0.0-ssr-manifest-20230306183729", 24 | "concurrently": "^7.6.0", 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0", 27 | "react-router-dom": "^6.8.2", 28 | "tiny-invariant": "^1.3.1" 29 | }, 30 | "devDependencies": { 31 | "@types/react": "^18.0.28", 32 | "@types/react-dom": "^18.0.11", 33 | "prisma": "^4.11.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/app/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { hasHandler, handleFetch$ } from '@tanstack/bling/server' 2 | import type { APIContext } from 'astro' 3 | import { renderToStringAsync } from 'solid-js/web' 4 | import { manifest } from 'astro:ssr-manifest' 5 | import { manifestContext } from './manifest' 6 | import { routes } from './root' 7 | import { Router, useRoutes } from '@solidjs/router' 8 | 9 | export const requestHandler = async ({ request }: APIContext) => { 10 | if (hasHandler(new URL(request.url).pathname)) { 11 | return await handleFetch$({ 12 | request, 13 | }) 14 | } 15 | 16 | return new Response( 17 | await renderToStringAsync(() => { 18 | const Routes = useRoutes(routes) 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | }), 27 | { 28 | headers: { 29 | 'content-type': 'text/html', 30 | }, 31 | }, 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { hasHandler, handleFetch$ } from '@tanstack/bling/server' 2 | import type { APIContext } from 'astro' 3 | import { renderToStringAsync } from 'solid-js/web' 4 | import { manifest } from 'astro:ssr-manifest' 5 | import { manifestContext } from './manifest' 6 | import { routes } from './root' 7 | import { Router, Routes, useRoutes } from '@solidjs/router' 8 | 9 | export const requestHandler = async ({ request }: APIContext) => { 10 | if (hasHandler(new URL(request.url).pathname)) { 11 | return await handleFetch$({ 12 | request, 13 | }) 14 | } 15 | 16 | return new Response( 17 | await renderToStringAsync(() => { 18 | const Routes = useRoutes(routes) 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | }), 27 | { 28 | headers: { 29 | 'content-type': 'text/html', 30 | }, 31 | }, 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/app/data/LICENSE.remix.md: -------------------------------------------------------------------------------- 1 | Copyright 2021 Remix Software Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tanner Linsley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "repository": "https://github.com/tanstack/bling.git", 4 | "scripts": { 5 | "build": "ts-node scripts/build.ts", 6 | "dev": "pnpm -rc --filter \"./packages/**\" --parallel exec 'pnpm dev'", 7 | "test": "exit 0", 8 | "test:dev": "exit 0", 9 | "test:ci": "exit 0", 10 | "clean-all": "pnpm -rc --parallel exec 'rm -rf build dist node_modules'", 11 | "prettier": "prettier \"packages/*/{src/**,examples/**/src/**}.{md,js,jsx,ts,tsx,json}\" --write", 12 | "pub": "pnpm build && pnpm publish -r --no-git-checks", 13 | "cipublish": "ts-node scripts/publish.ts", 14 | "cipublishforce": "CI=true pnpm cipublish" 15 | }, 16 | "pnpm": { 17 | "overrides": { 18 | "@tanstack/bling": "workspace:*" 19 | } 20 | }, 21 | "devDependencies": { 22 | "@commitlint/parse": "^17.4.4", 23 | "@types/node": "^18.14.0", 24 | "axios": "^1.3.4", 25 | "current-git-branch": "^1.1.0", 26 | "git-log-parser": "^1.2.0", 27 | "jsonfile": "^6.1.0", 28 | "luxon": "^3.2.1", 29 | "stream-to-array": "^2.3.0", 30 | "ts-node": "^10.9.1", 31 | "type-fest": "^3.6.0", 32 | "vite": "^4.1.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/bling/src/astro.ts: -------------------------------------------------------------------------------- 1 | import { bling } from './vite' 2 | import { fileURLToPath } from 'url' 3 | import type { AstroIntegration, AstroConfig } from 'astro' 4 | // https://astro.build/config 5 | export function astroBling(): AstroIntegration { 6 | let astroConfig: AstroConfig 7 | return { 8 | name: '', 9 | 10 | hooks: { 11 | 'astro:config:setup': (config) => { 12 | config.updateConfig({ 13 | vite: { 14 | plugins: [bling()], 15 | }, 16 | }) 17 | }, 18 | 'astro:config:done': (config) => { 19 | astroConfig = config.config 20 | }, 21 | 'astro:build:ssr': (config) => { 22 | console.log(astroConfig) 23 | let entryClient = fileURLToPath( 24 | new URL('./src/app/entry-client.tsx', astroConfig.root), 25 | ) 26 | 27 | ;(config.manifest as any)['entry-client'] = 28 | config.manifest.entryModules[entryClient] 29 | }, 30 | 'astro:build:done': (config) => {}, 31 | 'astro:build:setup': (config) => { 32 | if (config.target === 'client') { 33 | if (Array.isArray(config.vite.build?.rollupOptions?.input)) { 34 | config.vite.build?.rollupOptions?.input.push( 35 | 'src/app/entry-client.tsx', 36 | ) 37 | } 38 | 39 | if (config.vite.build) { 40 | config.vite.build.ssrManifest = true 41 | config.vite.build.manifest = true 42 | } 43 | } 44 | }, 45 | }, 46 | } satisfies AstroIntegration 47 | } 48 | -------------------------------------------------------------------------------- /examples/astro-react-router/src/app/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | hasHandler, 3 | handleFetch$, 4 | addDeserializer, 5 | } from '@tanstack/bling/server' 6 | import type { APIContext } from 'astro' 7 | import * as ReactDOM from 'react-dom/server.browser' 8 | import { createStaticHandler } from '@remix-run/router' 9 | import { 10 | createStaticRouter, 11 | StaticRouterProvider, 12 | } from 'react-router-dom/server' 13 | import { routes } from './root' 14 | import { manifest } from 'astro:ssr-manifest' 15 | import { manifestContext } from './manifest' 16 | 17 | addDeserializer({ 18 | apply: (req) => req === '$request', 19 | deserialize: (value, ctx) => ctx.request, 20 | }) 21 | 22 | export const requestHandler = async ({ request }: APIContext) => { 23 | // manifest['entry-client'] = 1 24 | if (hasHandler(new URL(request.url).pathname)) { 25 | return await handleFetch$({ 26 | request, 27 | }) 28 | } 29 | 30 | let { query } = createStaticHandler(routes) 31 | let context = await query(request) 32 | 33 | if (context instanceof Response) { 34 | throw context 35 | } 36 | 37 | let router = createStaticRouter(routes, context) 38 | 39 | return new Response( 40 | await ReactDOM.renderToReadableStream( 41 | 42 | 47 | , 48 | ), 49 | { 50 | headers: { 51 | 'content-type': 'text/html', 52 | }, 53 | }, 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/src/app/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | hasHandler, 3 | handleFetch$, 4 | addDeserializer, 5 | } from '@tanstack/bling/server' 6 | import type { APIContext } from 'astro' 7 | import * as ReactDOM from 'react-dom/server.browser' 8 | import { createStaticHandler } from '@remix-run/router' 9 | import { 10 | createStaticRouter, 11 | StaticRouterProvider, 12 | } from 'react-router-dom/server' 13 | import { routes } from './root' 14 | import { manifestContext } from './manifest' 15 | import { manifest } from 'astro:ssr-manifest' 16 | 17 | addDeserializer({ 18 | apply: (req) => req === '$request', 19 | deserialize: (value, ctx) => ctx.request, 20 | }) 21 | 22 | export const requestHandler = async ({ request }: APIContext) => { 23 | // manifest['entry-client'] = 1 24 | if (hasHandler(new URL(request.url).pathname)) { 25 | return await handleFetch$({ 26 | request, 27 | }) 28 | } 29 | 30 | let { query } = createStaticHandler(routes) 31 | let context = await query(request) 32 | 33 | if (context instanceof Response) { 34 | throw context 35 | } 36 | 37 | let router = createStaticRouter(routes, context) 38 | 39 | return new Response( 40 | await ReactDOM.renderToReadableStream( 41 | 42 | 47 | , 48 | ), 49 | { 50 | headers: { 51 | 'content-type': 'text/html', 52 | }, 53 | }, 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/components/story.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Show } from 'solid-js' 2 | import { A } from '@solidjs/router' 3 | 4 | import type { IStory } from '../types' 5 | 6 | const Story: Component<{ story: IStory }> = (props) => { 7 | return ( 8 |
  • 9 | {props.story.points} 10 | 11 | {props.story.title}} 14 | > 15 | 16 | {props.story.title} 17 | 18 | ({props.story.domain}) 19 | 20 | 21 |
    22 | 23 | {props.story.time_ago} 27 | } 28 | > 29 | by {props.story.user}{' '} 30 | {props.story.time_ago} |{' '} 31 | 32 | {props.story.comments_count 33 | ? `${props.story.comments_count} comments` 34 | : 'discuss'} 35 | 36 | 37 | 38 | 39 | {' '} 40 | {props.story.type} 41 | 42 |
  • 43 | ) 44 | } 45 | 46 | export default Story 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | concurrency: 3 | group: publish-${{ github.github.base_ref }} 4 | cancel-in-progress: true 5 | on: [push] 6 | jobs: 7 | test-and-publish: 8 | name: 'Test & Publish' 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | fetch-depth: '0' 14 | - uses: pnpm/action-setup@v2 15 | name: Install pnpm 16 | id: pnpm-install 17 | with: 18 | version: 7 19 | run_install: false 20 | env: 21 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | 23 | - name: Get pnpm store directory 24 | id: pnpm-cache 25 | shell: bash 26 | run: | 27 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 28 | env: 29 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | 31 | - uses: actions/cache@v3 32 | name: Setup pnpm cache 33 | with: 34 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 35 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 36 | restore-keys: | 37 | ${{ runner.os }}-pnpm-store- 38 | 39 | - name: Publish 40 | run: | 41 | pnpm install --no-frozen-lockfile 42 | git config --global user.name 'Tanner Linsley' 43 | git config --global user.email 'tannerlinsley@users.noreply.github.com' 44 | pnpm cipublish 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 47 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 48 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 49 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 50 | -------------------------------------------------------------------------------- /examples/astro-solid/src/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { server$, secret$, import$ } from '@tanstack/bling' 2 | import { createSignal, lazy, Suspense, useContext } from 'solid-js' 3 | import { HydrationScript, NoHydration } from 'solid-js/web' 4 | import { manifestContext } from './manifest' 5 | import { secret } from './server.secret$' 6 | 7 | const sayHello = server$(() => console.log('Hello world')) 8 | 9 | const LazyHello3 = lazy(() => 10 | import$({ 11 | default: () => { 12 | return ( 13 | <> 14 | 15 | 16 | ) 17 | }, 18 | }), 19 | ) 20 | 21 | const inlineSecret = secret$('I am an inline server secret!') 22 | 23 | export function App() { 24 | console.log( 25 | 'Do you know the inline server secret?', 26 | inlineSecret ?? 'Not even.', 27 | ) 28 | 29 | console.log( 30 | 'Do you know the server secret in server.secret.ts?', 31 | secret ?? 'Nope', 32 | ) 33 | 34 | const [state, setState] = createSignal(0) 35 | 36 | return ( 37 | 38 | 39 | Hello World 40 | 41 | 42 |
    Hello world
    43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | 53 | function Scripts() { 54 | const manifest = useContext(manifestContext) 55 | return ( 56 | 57 | 58 | {import.meta.env.DEV ? ( 59 | <> 60 | 61 | 66 | 67 | ) : ( 68 | <> 69 | 74 | 75 | )} 76 | 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /examples/astro-react/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Astro](https://astro.build) 2 | 3 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 4 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/basics) 5 | 6 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 7 | 8 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 9 | 10 | 11 | ## 🚀 Project Structure 12 | 13 | Inside of your Astro project, you'll see the following folders and files: 14 | 15 | ``` 16 | / 17 | ├── public/ 18 | │ └── favicon.svg 19 | ├── src/ 20 | │ ├── components/ 21 | │ │ └── Card.astro 22 | │ ├── layouts/ 23 | │ │ └── Layout.astro 24 | │ └── pages/ 25 | │ └── index.astro 26 | └── package.json 27 | ``` 28 | 29 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 30 | 31 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 32 | 33 | Any static assets, like images, can be placed in the `public/` directory. 34 | 35 | ## 🧞 Commands 36 | 37 | All commands are run from the root of the project, from a terminal: 38 | 39 | | Command | Action | 40 | | :--------------------- | :------------------------------------------------- | 41 | | `npm install` | Installs dependencies | 42 | | `npm run dev` | Starts local dev server at `localhost:3000` | 43 | | `npm run build` | Build your production site to `./dist/` | 44 | | `npm run preview` | Preview your build locally, before deploying | 45 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` | 46 | | `npm run astro --help` | Get help using the Astro CLI | 47 | 48 | ## 👀 Want to learn more? 49 | 50 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 51 | -------------------------------------------------------------------------------- /examples/astro-solid/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Astro](https://astro.build) 2 | 3 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 4 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/basics) 5 | 6 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 7 | 8 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 9 | 10 | 11 | ## 🚀 Project Structure 12 | 13 | Inside of your Astro project, you'll see the following folders and files: 14 | 15 | ``` 16 | / 17 | ├── public/ 18 | │ └── favicon.svg 19 | ├── src/ 20 | │ ├── components/ 21 | │ │ └── Card.astro 22 | │ ├── layouts/ 23 | │ │ └── Layout.astro 24 | │ └── pages/ 25 | │ └── index.astro 26 | └── package.json 27 | ``` 28 | 29 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 30 | 31 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 32 | 33 | Any static assets, like images, can be placed in the `public/` directory. 34 | 35 | ## 🧞 Commands 36 | 37 | All commands are run from the root of the project, from a terminal: 38 | 39 | | Command | Action | 40 | | :--------------------- | :------------------------------------------------- | 41 | | `npm install` | Installs dependencies | 42 | | `npm run dev` | Starts local dev server at `localhost:3000` | 43 | | `npm run build` | Build your production site to `./dist/` | 44 | | `npm run preview` | Preview your build locally, before deploying | 45 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` | 46 | | `npm run astro --help` | Get help using the Astro CLI | 47 | 48 | ## 👀 Want to learn more? 49 | 50 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 51 | -------------------------------------------------------------------------------- /examples/astro-react-router/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Astro](https://astro.build) 2 | 3 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 4 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/basics) 5 | 6 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 7 | 8 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 9 | 10 | 11 | ## 🚀 Project Structure 12 | 13 | Inside of your Astro project, you'll see the following folders and files: 14 | 15 | ``` 16 | / 17 | ├── public/ 18 | │ └── favicon.svg 19 | ├── src/ 20 | │ ├── components/ 21 | │ │ └── Card.astro 22 | │ ├── layouts/ 23 | │ │ └── Layout.astro 24 | │ └── pages/ 25 | │ └── index.astro 26 | └── package.json 27 | ``` 28 | 29 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 30 | 31 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 32 | 33 | Any static assets, like images, can be placed in the `public/` directory. 34 | 35 | ## 🧞 Commands 36 | 37 | All commands are run from the root of the project, from a terminal: 38 | 39 | | Command | Action | 40 | | :--------------------- | :------------------------------------------------- | 41 | | `npm install` | Installs dependencies | 42 | | `npm run dev` | Starts local dev server at `localhost:3000` | 43 | | `npm run build` | Build your production site to `./dist/` | 44 | | `npm run preview` | Preview your build locally, before deploying | 45 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` | 46 | | `npm run astro --help` | Get help using the Astro CLI | 47 | 48 | ## 👀 Want to learn more? 49 | 50 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 51 | -------------------------------------------------------------------------------- /examples/astro-solid-router/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Astro](https://astro.build) 2 | 3 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 4 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/basics) 5 | 6 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 7 | 8 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 9 | 10 | 11 | ## 🚀 Project Structure 12 | 13 | Inside of your Astro project, you'll see the following folders and files: 14 | 15 | ``` 16 | / 17 | ├── public/ 18 | │ └── favicon.svg 19 | ├── src/ 20 | │ ├── components/ 21 | │ │ └── Card.astro 22 | │ ├── layouts/ 23 | │ │ └── Layout.astro 24 | │ └── pages/ 25 | │ └── index.astro 26 | └── package.json 27 | ``` 28 | 29 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 30 | 31 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 32 | 33 | Any static assets, like images, can be placed in the `public/` directory. 34 | 35 | ## 🧞 Commands 36 | 37 | All commands are run from the root of the project, from a terminal: 38 | 39 | | Command | Action | 40 | | :--------------------- | :------------------------------------------------- | 41 | | `npm install` | Installs dependencies | 42 | | `npm run dev` | Starts local dev server at `localhost:3000` | 43 | | `npm run build` | Build your production site to `./dist/` | 44 | | `npm run preview` | Preview your build locally, before deploying | 45 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` | 46 | | `npm run astro --help` | Get help using the Astro CLI | 47 | 48 | ## 👀 Want to learn more? 49 | 50 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 51 | -------------------------------------------------------------------------------- /examples/astro-react-todomvc/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Astro](https://astro.build) 2 | 3 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 4 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/basics) 5 | 6 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 7 | 8 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 9 | 10 | 11 | ## 🚀 Project Structure 12 | 13 | Inside of your Astro project, you'll see the following folders and files: 14 | 15 | ``` 16 | / 17 | ├── public/ 18 | │ └── favicon.svg 19 | ├── src/ 20 | │ ├── components/ 21 | │ │ └── Card.astro 22 | │ ├── layouts/ 23 | │ │ └── Layout.astro 24 | │ └── pages/ 25 | │ └── index.astro 26 | └── package.json 27 | ``` 28 | 29 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 30 | 31 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 32 | 33 | Any static assets, like images, can be placed in the `public/` directory. 34 | 35 | ## 🧞 Commands 36 | 37 | All commands are run from the root of the project, from a terminal: 38 | 39 | | Command | Action | 40 | | :--------------------- | :------------------------------------------------- | 41 | | `npm install` | Installs dependencies | 42 | | `npm run dev` | Starts local dev server at `localhost:3000` | 43 | | `npm run build` | Build your production site to `./dist/` | 44 | | `npm run preview` | Preview your build locally, before deploying | 45 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` | 46 | | `npm run astro --help` | Get help using the Astro CLI | 47 | 48 | ## 👀 Want to learn more? 49 | 50 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 51 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Astro](https://astro.build) 2 | 3 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 4 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/basics) 5 | 6 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 7 | 8 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 9 | 10 | 11 | ## 🚀 Project Structure 12 | 13 | Inside of your Astro project, you'll see the following folders and files: 14 | 15 | ``` 16 | / 17 | ├── public/ 18 | │ └── favicon.svg 19 | ├── src/ 20 | │ ├── components/ 21 | │ │ └── Card.astro 22 | │ ├── layouts/ 23 | │ │ └── Layout.astro 24 | │ └── pages/ 25 | │ └── index.astro 26 | └── package.json 27 | ``` 28 | 29 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 30 | 31 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 32 | 33 | Any static assets, like images, can be placed in the `public/` directory. 34 | 35 | ## 🧞 Commands 36 | 37 | All commands are run from the root of the project, from a terminal: 38 | 39 | | Command | Action | 40 | | :--------------------- | :------------------------------------------------- | 41 | | `npm install` | Installs dependencies | 42 | | `npm run dev` | Starts local dev server at `localhost:3000` | 43 | | `npm run build` | Build your production site to `./dist/` | 44 | | `npm run preview` | Preview your build locally, before deploying | 45 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` | 46 | | `npm run astro --help` | Get help using the Astro CLI | 47 | 48 | ## 👀 Want to learn more? 49 | 50 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 51 | -------------------------------------------------------------------------------- /examples/astro-react/src/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { server$, lazy$, import$, secret$ } from '@tanstack/bling' 2 | import React, { Fragment, lazy, Suspense, useContext } from 'react' 3 | import { manifestContext } from './manifest' 4 | 5 | const sayHello = server$(() => console.log('Hello world')) 6 | 7 | function ServerHello() { 8 | return 9 | } 10 | 11 | const LazyHello = lazy$((props) => { 12 | return 13 | }) 14 | 15 | const LazyHello2 = lazy$(function SplitHelloas() { 16 | return ( 17 | 18 | 19 | sayHello()} /> 20 | 21 | ) 22 | }) 23 | 24 | const LazyHello3 = lazy(() => 25 | import$({ 26 | default: () => { 27 | return ( 28 | 29 | 30 | sayHello()} /> 31 | 32 | ) 33 | }, 34 | }), 35 | ) 36 | 37 | const inlineSecret = secret$('I am an inline server secret!') 38 | 39 | export function App() { 40 | console.log( 41 | 'Do you know the inline server secret?', 42 | inlineSecret ?? 'Not even.', 43 | ) 44 | 45 | const [state, setState] = React.useState(0) 46 | 47 | return ( 48 | 49 | 50 | Hello World 51 | 52 | 53 |
    Hello world
    54 | 55 | 56 | {state > 0 && ( 57 | 58 | 59 | 60 | 61 | 62 | )} 63 | 64 | 65 | 66 | ) 67 | } 68 | 69 | function Scripts() { 70 | const manifest = useContext(manifestContext) 71 | return import.meta.env.DEV ? ( 72 | <> 73 | 74 | 75 | 76 | ) : ( 77 | <> 78 | 79 | 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /packages/bling/src/types.ts: -------------------------------------------------------------------------------- 1 | export const FormError = Error 2 | export const ServerError = Error 3 | 4 | export type Serializer = { 5 | apply: (value: any) => boolean 6 | serialize: (value: any) => any 7 | } 8 | 9 | export type Deserializer = { 10 | apply: (value: any) => any 11 | deserialize: (value: any, ctx: FetchFnCtx) => any 12 | } 13 | 14 | export type AnyFetchFn = (payload: any, ctx: FetchFnCtxWithRequest) => any 15 | 16 | export type FetchFnReturn = Awaited< 17 | ReturnType 18 | > extends JsonResponse 19 | ? R 20 | : ReturnType 21 | 22 | export type CreateFetcherFn = ( 23 | fn: T, 24 | opts?: FetchFnCtxWithRequest, 25 | ) => Fetcher 26 | 27 | export type FetcherFn = ( 28 | payload: Parameters['0'] extends undefined 29 | ? void | undefined 30 | : Parameters['0'], 31 | opts?: FetchFnCtx, 32 | ) => Promise>> 33 | 34 | export type FetcherMethods = { 35 | url: string 36 | fetch: ( 37 | init: RequestInit, 38 | opts?: FetchFnCtxOptions, 39 | ) => Promise>> 40 | } 41 | 42 | export type Fetcher = FetcherFn & FetcherMethods 43 | 44 | export interface JsonResponse extends Response {} 45 | 46 | export type FetchFnCtxBase = { 47 | method?: 'GET' | 'POST' 48 | } 49 | 50 | export type FetchFnCtxOptions = FetchFnCtxBase & { 51 | request?: RequestInit 52 | __hasRequest?: never 53 | } 54 | 55 | export type FetchFnCtxWithRequest = FetchFnCtxBase & { 56 | request: Request 57 | __hasRequest: true 58 | } 59 | 60 | export type FetchFnCtx = FetchFnCtxOptions | FetchFnCtxWithRequest 61 | 62 | export type NonFnProps = { 63 | [TKey in keyof T]: TKey extends (...args: any[]) => any ? never : T[TKey] 64 | } 65 | 66 | export type AnySplitFn = (...args: any[]) => any 67 | export type ModuleObj = any 68 | 69 | export type CreateSplitFn = (fn: T) => SplitFn 70 | 71 | export type CreateLazyFn = (fn: T) => T 72 | 73 | export type CreateImportFn = (fn: T) => Promise 74 | 75 | export type SplitFn = ( 76 | ...args: Parameters 77 | ) => Promise>> 78 | 79 | export type CreateSecretFn = (value: T) => T 80 | -------------------------------------------------------------------------------- /packages/bling/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tanstack/bling", 3 | "version": "0.5.0", 4 | "description": "", 5 | "author": "Nikhil Saraf", 6 | "license": "MIT", 7 | "type": "module", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "files": [ 12 | "*" 13 | ], 14 | "scripts": { 15 | "dev": "concurrently --kill-others \"pnpm build:types --watch\" \"pnpm build:server --watch\" \"pnpm build:vite --watch\" \"pnpm build:compilers --watch\" \"pnpm build:client --watch\" \"pnpm build:astro --watch\"", 16 | "build": "pnpm build:types && pnpm build:server && pnpm build:vite && pnpm build:astro && pnpm build:compilers && pnpm build:client", 17 | "build:types": "tsc --emitDeclarationOnly --declaration --skipLibCheck", 18 | "build:server": "esbuild src/server.ts --bundle --platform=node --format=esm --sourcemap --packages=external --outfile=dist/server.js", 19 | "build:vite": "esbuild src/vite.ts --bundle --platform=node --format=esm --sourcemap --packages=external --outfile=dist/vite.js", 20 | "build:astro": "esbuild src/astro.ts --bundle --platform=node --format=esm --sourcemap --packages=external --outfile=dist/astro.js", 21 | "build:compilers": "esbuild src/compilers.ts --bundle --platform=node --format=esm --sourcemap --packages=external --outfile=dist/compilers.js", 22 | "build:client": "esbuild src/client.ts --bundle --format=esm --minify --sourcemap --outfile=dist/client.js", 23 | "prettier": "prettier \"packages/*/{src/**,examples/**/src/**}.{md,js,jsx,ts,tsx,json}\" --write" 24 | }, 25 | "exports": { 26 | ".": "./dist/client.js", 27 | "./server": "./dist/server.js", 28 | "./client": "./dist/client.js", 29 | "./vite": "./dist/vite.js", 30 | "./astro": "./dist/astro.js", 31 | "./compilers": "./dist/compilers.js", 32 | "./package.json": "./package.json" 33 | }, 34 | "types": "./dist/server.d.ts", 35 | "typesVersions": { 36 | "*": { 37 | ".": [ 38 | "./dist/client.d.ts" 39 | ], 40 | "client": [ 41 | "./dist/client.d.ts" 42 | ], 43 | "server": [ 44 | "./dist/server.d.ts" 45 | ], 46 | "vite": [ 47 | "./dist/vite.d.ts" 48 | ], 49 | "astro": [ 50 | "./dist/astro.d.ts" 51 | ], 52 | "compilers": [ 53 | "./dist/compilers.d.ts" 54 | ] 55 | } 56 | }, 57 | "dependencies": { 58 | "@babel/generator": "^7.21.1", 59 | "@babel/template": "^7.20.7", 60 | "@babel/traverse": "^7.21.2", 61 | "@babel/types": "^7.21.2", 62 | "@vitejs/plugin-react": "^3.1.0", 63 | "esbuild": "^0.16.17", 64 | "esbuild-plugin-replace": "^1.3.0" 65 | }, 66 | "devDependencies": { 67 | "@types/babel__core": "^7.20.0", 68 | "@types/babel__generator": "^7.6.4", 69 | "@types/babel__template": "^7.4.1", 70 | "@types/babel__traverse": "^7.18.3", 71 | "astro": "0.0.0-ssr-manifest-20230306183729", 72 | "concurrently": "^7.6.0", 73 | "typescript": "4.9.4", 74 | "vitest": "^0.26.2" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/bling/src/vite.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | import viteReact, { Options } from '@vitejs/plugin-react' 3 | import { fileURLToPath, pathToFileURL } from 'url' 4 | import { compileSecretFile, compileFile, splitFile } from './compilers' 5 | 6 | export const virtualModuleSplitPrefix = 'virtual:bling-split$-' 7 | export const virtualPrefix = '\0' 8 | 9 | export function bling(opts?: { babel?: Options['babel'] }): Plugin { 10 | const options = opts ?? {} 11 | 12 | return { 13 | name: 'vite-plugin-bling', 14 | enforce: 'pre', 15 | transform: async (code, id, transformOptions) => { 16 | const isSsr = 17 | transformOptions === null || transformOptions === void 0 18 | ? void 0 19 | : transformOptions.ssr 20 | 21 | let ssr = process.env.TEST_ENV === 'client' ? false : !!isSsr 22 | 23 | let [fileId, queryParam] = id.split('?') 24 | 25 | let param = new URLSearchParams(queryParam) 26 | 27 | const url = pathToFileURL(id) 28 | url.searchParams.delete('v') 29 | id = fileURLToPath(url).replace(/\\/g, '/') 30 | 31 | const babelOptions = 32 | (fn?: (source: any, id: any) => { plugins: any[] }) => 33 | (source: any, id: any) => { 34 | const b: any = 35 | typeof options.babel === 'function' 36 | ? // @ts-ignore 37 | options.babel(...args) 38 | : options.babel ?? { plugins: [] } 39 | const d = fn?.(source, id) 40 | return { 41 | plugins: [...b.plugins, ...(d?.plugins ?? [])], 42 | } 43 | } 44 | 45 | let viteCompile = ( 46 | code: string, 47 | id: string, 48 | fn?: (source: any, id: any) => { plugins: any[] }, 49 | ) => { 50 | let plugin = viteReact({ 51 | ...(options ?? {}), 52 | fastRefresh: false, 53 | babel: babelOptions(fn), 54 | }) 55 | 56 | // @ts-ignore 57 | return plugin[0].transform(code, id, transformOptions) 58 | } 59 | 60 | if (param.has('split')) { 61 | const compiled = await splitFile({ 62 | code, 63 | viteCompile, 64 | ssr, 65 | id: fileId.replace(/\.ts$/, '.tsx').replace(/\.js$/, '.jsx'), 66 | splitIndex: Number(param.get('split')), 67 | ref: param.get('ref') ?? 'fn', 68 | }) 69 | 70 | return compiled.code 71 | } 72 | 73 | if (url.pathname.includes('.secret$.') && !ssr) { 74 | const compiled = compileSecretFile({ 75 | code, 76 | }) 77 | 78 | return compiled.code 79 | } 80 | 81 | if ( 82 | code.includes('fetch$(' || code.includes('split$(')) || 83 | code.includes('server$(') 84 | ) { 85 | const compiled = await compileFile({ 86 | code, 87 | viteCompile, 88 | ssr, 89 | id: id.replace(/\.ts$/, '.tsx').replace(/\.js$/, '.jsx'), 90 | }) 91 | 92 | return compiled.code 93 | } 94 | }, 95 | } 96 | } 97 | 98 | export default bling 99 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { server$, import$, secret$, redirect } from '@tanstack/bling' 2 | import { createSignal, lazy, Show, Suspense, useContext } from 'solid-js' 3 | import { HydrationScript, NoHydration } from 'solid-js/web' 4 | import { manifestContext } from './manifest' 5 | import { Link, Outlet, RouteDefinition, useRouteData } from '@solidjs/router' 6 | import { useAction, useLoader } from './data' 7 | 8 | const sayHello = server$(() => console.log('Hello world')) 9 | 10 | const LazyHello3 = lazy(() => 11 | import$({ 12 | default: () => { 13 | return ( 14 | <> 15 | 16 | 17 | ) 18 | }, 19 | }), 20 | ) 21 | 22 | const inlineSecret = secret$('I am an inline server secret!') 23 | 24 | export function App() { 25 | console.log( 26 | 'Do you know the inline server secret?', 27 | inlineSecret ?? 'Not even.', 28 | ) 29 | 30 | const [state, setState] = createSignal(0) 31 | 32 | return ( 33 | 34 | 35 | Hello World 36 | 37 | 38 |
    Hello world
    39 | Home 40 | About 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | } 50 | 51 | function Scripts() { 52 | const manifest = useContext(manifestContext) 53 | return ( 54 | 55 | 56 | {import.meta.env.DEV ? ( 57 | <> 58 | 59 | 64 | 65 | ) : ( 66 | <> 67 | 72 | 73 | )} 74 | 75 | ) 76 | } 77 | 78 | let count = 0 79 | 80 | const increment = server$(async () => { 81 | count = count + 1 82 | return redirect('/about') 83 | }) 84 | 85 | export const routes = [ 86 | { 87 | path: '/', 88 | component: App, 89 | children: [ 90 | { 91 | path: '', 92 | component: lazy(() => import$({ default: () =>
    Home
    })), 93 | }, 94 | { 95 | path: 'about', 96 | data: () => { 97 | return useLoader(server$(() => ({ count }))) 98 | }, 99 | component: lazy(() => 100 | import$({ 101 | default: () => { 102 | const routeData = useRouteData() 103 | const [action, submit] = useAction(increment) 104 | return ( 105 |
    106 | About {routeData().count} 107 | 108 | 109 | 110 | 111 |
    112 | ) 113 | }, 114 | }), 115 | ), 116 | }, 117 | ], 118 | }, 119 | ] satisfies RouteDefinition[] 120 | -------------------------------------------------------------------------------- /examples/astro-react-router/src/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { server$, lazy$, secret$, import$ } from '@tanstack/bling' 2 | import React, { Fragment, lazy, Suspense, useContext } from 'react' 3 | import { Link, Outlet, RouteObject, useLoaderData } from 'react-router-dom' 4 | import { manifestContext } from './manifest' 5 | 6 | const sayHello = server$(() => console.log('Hello world')) 7 | 8 | function ServerHello() { 9 | return 10 | } 11 | 12 | const LazyHello = lazy$((props) => { 13 | return 14 | }) 15 | 16 | const LazyHello2 = lazy$(function SplitHelloas() { 17 | return ( 18 | 19 | 20 | sayHello()} /> 21 | 22 | ) 23 | }) 24 | 25 | const LazyHello3 = lazy(() => 26 | import$({ 27 | default: () => { 28 | return ( 29 | 30 | 31 | sayHello()} /> 32 | 33 | ) 34 | }, 35 | }), 36 | ) 37 | 38 | const inlineSecret = secret$('I am an inline server secret!') 39 | 40 | const App = lazy$(() => { 41 | console.log( 42 | 'Do you know the inline server secret?', 43 | inlineSecret ?? 'Not even.', 44 | ) 45 | 46 | return ( 47 | 48 | 49 | Hello World 50 | 51 | 52 |
    Hello world
    53 | hello 54 | home 55 | 56 | 57 | 58 | 59 | 60 | 61 | ) 62 | }) 63 | 64 | const SomeRoute = lazy$(() => { 65 | const [state, setState] = React.useState(0) 66 | 67 | return ( 68 | <> 69 | 70 | 71 | {state > 0 && ( 72 | 73 | 74 | 75 | 76 | 77 | )} 78 | 79 | ) 80 | }) 81 | 82 | const SomeRoute2 = lazy(() => 83 | import$({ 84 | default: () => { 85 | const [state, setState] = React.useState(0) 86 | const data = useLoaderData() 87 | console.log(data) 88 | 89 | return ( 90 | <> 91 | 92 | 93 | ) 94 | }, 95 | }), 96 | ) 97 | 98 | function Scripts() { 99 | const manifest = useContext(manifestContext) 100 | return import.meta.env.DEV ? ( 101 | <> 102 | 103 | 104 | 105 | ) : ( 106 | 107 | ) 108 | } 109 | 110 | export let routes = [ 111 | { 112 | path: '/', 113 | element: , 114 | children: [ 115 | { 116 | index: true, 117 | element: , 118 | }, 119 | { 120 | path: 'hello', 121 | loader: server$((args, { request }) => { 122 | return { 123 | 'got data': inlineSecret, 124 | req: [...request.headers.entries()], 125 | } 126 | }), 127 | 128 | element: , 129 | }, 130 | ], 131 | }, 132 | ] satisfies RouteObject[] 133 | -------------------------------------------------------------------------------- /packages/bling/src/client.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mergeRequestInits, 3 | mergeFetchOpts, 4 | parseResponse, 5 | payloadRequestInit, 6 | resolveRequestHref, 7 | XBlingOrigin, 8 | XBlingResponseTypeHeader, 9 | } from './utils/utils' 10 | 11 | import type { 12 | AnyFetchFn, 13 | Serializer, 14 | FetcherFn, 15 | FetcherMethods, 16 | FetchFnReturn, 17 | FetchFnCtxOptions, 18 | FetchFnCtx, 19 | CreateSplitFn, 20 | CreateSecretFn, 21 | CreateImportFn, 22 | FetchFnCtxWithRequest, 23 | } from './types' 24 | 25 | export * from './utils/utils' 26 | 27 | // 28 | 29 | let serializers: Serializer[] = [] 30 | 31 | export function addSerializer({ apply, serialize }: Serializer) { 32 | serializers.push({ apply, serialize }) 33 | } 34 | 35 | export type CreateClientFetcherFn = ( 36 | fn: T, 37 | opts?: FetchFnCtxWithRequest, 38 | ) => ClientFetcher 39 | 40 | export type CreateClientFetcherMethods = { 41 | createFetcher( 42 | route: string, 43 | defualtOpts: FetchFnCtxOptions, 44 | ): ClientFetcher 45 | } 46 | 47 | export type ClientFetcher = FetcherFn & 48 | FetcherMethods 49 | 50 | export type ClientFetcherMethods = FetcherMethods & { 51 | fetch: ( 52 | init: RequestInit, 53 | opts?: FetchFnCtxOptions, 54 | ) => Promise>> 55 | } 56 | 57 | export type ClientFetchFn = CreateClientFetcherFn & CreateClientFetcherMethods 58 | 59 | const fetchImpl = (() => { 60 | throw new Error('Should be compiled away') 61 | }) as any 62 | 63 | const fetchMethods: CreateClientFetcherMethods = { 64 | createFetcher: (pathname: string, defaultOpts?: FetchFnCtxOptions) => { 65 | const fetcherImpl = async (payload: any = {}, opts?: FetchFnCtxOptions) => { 66 | const method = opts?.method || defaultOpts?.method || 'POST' 67 | 68 | const baseInit: RequestInit = { 69 | method, 70 | headers: { 71 | [XBlingOrigin]: 'client', 72 | }, 73 | } 74 | 75 | let payloadInit = payloadRequestInit(payload, serializers) 76 | 77 | const resolvedHref = resolveRequestHref(pathname, method, payloadInit) 78 | 79 | const requestInit = mergeRequestInits( 80 | baseInit, 81 | payloadInit, 82 | defaultOpts?.request, 83 | opts?.request, 84 | ) 85 | const request = new Request(resolvedHref, requestInit) 86 | 87 | const response = await fetch(request) 88 | 89 | // // throws response, error, form error, json object, string 90 | if (response.headers.get(XBlingResponseTypeHeader) === 'throw') { 91 | throw await parseResponse(response) 92 | } else { 93 | return await parseResponse(response) 94 | } 95 | } 96 | 97 | const fetcherMethods: ClientFetcherMethods = { 98 | url: pathname, 99 | fetch: (request: RequestInit, opts?: FetchFnCtxOptions) => { 100 | return fetcherImpl({}, mergeFetchOpts({ request }, opts) as any) 101 | }, 102 | } 103 | 104 | return Object.assign(fetcherImpl, fetcherMethods) as ClientFetcher 105 | }, 106 | } 107 | 108 | export const fetch$: ClientFetchFn = Object.assign(fetchImpl, fetchMethods) 109 | export const server$: ClientFetchFn = fetch$ 110 | 111 | export const split$: CreateSplitFn = (_fn) => { 112 | throw new Error('Should be compiled away') 113 | } 114 | 115 | export const import$: CreateImportFn = (_fn) => { 116 | throw new Error('Should be compiled away') 117 | } 118 | 119 | export const lazy$: CreateSplitFn = (_fn) => { 120 | throw new Error('Should be compiled away') 121 | } 122 | 123 | export const secret$: CreateSecretFn = (_value) => { 124 | throw new Error('Should be compiled away') 125 | } 126 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: '🐛 Bug report' 2 | description: Create a report to help us improve 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for reporting an issue :pray:. 8 | 9 | This issue tracker is for reporting bugs found in `router` (https://github.com/tanstack/router). 10 | If you have a question about how to achieve something and are struggling, please post a question 11 | inside of `router` Discussions tab: https://github.com/tanstack/router/discussions 12 | 13 | Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: 14 | - `router` Issues tab: https://github.com/tanstack/router/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc 15 | - `router` closed issues tab: https://github.com/tanstack/router/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed 16 | - `router` Discussions tab: https://github.com/tanstack/router/discussions 17 | 18 | The more information you fill in, the better the community can help you. 19 | - type: textarea 20 | id: description 21 | attributes: 22 | label: Describe the bug 23 | description: Provide a clear and concise description of the challenge you are running into. 24 | validations: 25 | required: true 26 | - type: input 27 | id: link 28 | attributes: 29 | label: Your Example Website or App 30 | description: | 31 | Which website or app were you using when the bug happened? 32 | Note: 33 | - Please provide a link via our pre-configured [Stackblitz project](https://stackblitz.com/github/tanstack/router/tree/beta/examples/react/quickstart?file=src%2Fmain.tsx) or a link to a repo that can reproduce the issue. 34 | - Your bug will may get fixed much faster if we can run your code and it doesn't have dependencies other than the `router` npm package / dependency. 35 | - To create a shareable code example you can use Stackblitz. Please no localhost URLs. 36 | - Please read these tips for providing a minimal example: https://stackoverflow.com/help/mcve. 37 | placeholder: reproduction URL 38 | validations: 39 | required: true 40 | - type: textarea 41 | id: steps 42 | attributes: 43 | label: Steps to Reproduce the Bug or Issue 44 | description: Describe the steps we have to take to reproduce the behavior. 45 | placeholder: | 46 | 1. Go to '...' 47 | 2. Click on '....' 48 | 3. Scroll down to '....' 49 | 4. See error 50 | validations: 51 | required: true 52 | - type: textarea 53 | id: expected 54 | attributes: 55 | label: Expected behavior 56 | description: Provide a clear and concise description of what you expected to happen. 57 | placeholder: | 58 | As a user, I expected ___ behavior but i am seeing ___ 59 | validations: 60 | required: true 61 | - type: textarea 62 | id: screenshots_or_videos 63 | attributes: 64 | label: Screenshots or Videos 65 | description: | 66 | If applicable, add screenshots or a video to help explain your problem. 67 | For more information on the supported file image/file types and the file size limits, please refer 68 | to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files 69 | placeholder: | 70 | You can drag your video or image files inside of this editor ↓ 71 | - type: textarea 72 | id: platform 73 | attributes: 74 | label: Platform 75 | value: | 76 | - OS: [e.g. macOS, Windows, Linux] 77 | - Browser: [e.g. Chrome, Safari, Firefox] 78 | - Version: [e.g. 91.1] 79 | validations: 80 | required: true 81 | - type: textarea 82 | id: additional 83 | attributes: 84 | label: Additional context 85 | description: Add any other context about the problem here. 86 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/root.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 3 | font-size: 15px; 4 | background-color: #f2f3f5; 5 | margin: 0; 6 | padding-top: 55px; 7 | color: #34495e; 8 | overflow-y: scroll; 9 | } 10 | 11 | a { 12 | color: #34495e; 13 | text-decoration: none; 14 | } 15 | 16 | .header { 17 | background-color: #335d92; 18 | position: fixed; 19 | z-index: 999; 20 | height: 55px; 21 | top: 0; 22 | left: 0; 23 | right: 0; 24 | } 25 | 26 | .header .inner { 27 | max-width: 800px; 28 | box-sizing: border-box; 29 | margin: 0 auto; 30 | padding: 15px 5px; 31 | } 32 | 33 | .header a { 34 | color: rgba(255, 255, 255, 0.8); 35 | line-height: 24px; 36 | transition: color 0.15s ease; 37 | display: inline-block; 38 | vertical-align: middle; 39 | font-weight: 300; 40 | letter-spacing: 0.075em; 41 | margin-right: 1.8em; 42 | } 43 | 44 | .header a:hover { 45 | color: #fff; 46 | } 47 | 48 | .header a.active { 49 | color: #fff; 50 | font-weight: 400; 51 | } 52 | 53 | .header a:nth-child(6) { 54 | margin-right: 0; 55 | } 56 | 57 | .header .github { 58 | color: #fff; 59 | font-size: 0.9em; 60 | margin: 0; 61 | float: right; 62 | } 63 | 64 | .logo { 65 | width: 24px; 66 | margin-right: 10px; 67 | display: inline-block; 68 | vertical-align: middle; 69 | } 70 | 71 | .view { 72 | max-width: 800px; 73 | margin: 0 auto; 74 | position: relative; 75 | } 76 | 77 | @media (max-width: 860px) { 78 | .header .inner { 79 | padding: 15px 30px; 80 | } 81 | } 82 | 83 | @media (max-width: 600px) { 84 | .header .inner { 85 | padding: 15px; 86 | } 87 | 88 | .header a { 89 | margin-right: 1em; 90 | } 91 | 92 | .header .github { 93 | display: none; 94 | } 95 | } 96 | 97 | .news-view { 98 | padding-top: 45px; 99 | } 100 | 101 | .news-list, .news-list-nav { 102 | background-color: #fff; 103 | border-radius: 2px; 104 | } 105 | 106 | .news-list-nav { 107 | padding: 15px 30px; 108 | position: fixed; 109 | text-align: center; 110 | top: 55px; 111 | left: 0; 112 | right: 0; 113 | z-index: 998; 114 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 115 | } 116 | 117 | .news-list-nav .page-link { 118 | margin: 0 1em; 119 | } 120 | 121 | .news-list-nav .disabled { 122 | color: #aaa; 123 | } 124 | 125 | .news-list { 126 | position: absolute; 127 | margin: 30px 0; 128 | width: 100%; 129 | } 130 | 131 | .news-list ul { 132 | list-style-type: none; 133 | padding: 0; 134 | margin: 0; 135 | } 136 | 137 | @media (max-width: 600px) { 138 | .news-list { 139 | margin: 10px 0; 140 | } 141 | } 142 | 143 | .news-item { 144 | background-color: #fff; 145 | padding: 20px 30px 20px 80px; 146 | border-bottom: 1px solid #eee; 147 | position: relative; 148 | line-height: 20px; 149 | } 150 | 151 | .news-item .score { 152 | color: #335d92; 153 | font-size: 1.1em; 154 | font-weight: 700; 155 | position: absolute; 156 | top: 50%; 157 | left: 0; 158 | width: 80px; 159 | text-align: center; 160 | margin-top: -10px; 161 | } 162 | 163 | .news-item .host, .news-item .meta { 164 | font-size: 0.85em; 165 | color: #626262; 166 | } 167 | 168 | .news-item .host a, .news-item .meta a { 169 | color: #626262; 170 | text-decoration: underline; 171 | } 172 | 173 | .news-item .host a:hover, .news-item .meta a:hover { 174 | color: #335d92; 175 | } 176 | 177 | .item-view-header { 178 | background-color: #fff; 179 | padding: 1.8em 2em 1em; 180 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); 181 | } 182 | 183 | .item-view-header h1 { 184 | display: inline; 185 | font-size: 1.5em; 186 | margin: 0; 187 | margin-right: 0.5em; 188 | } 189 | 190 | .item-view-header .host, .item-view-header .meta, .item-view-header .meta a { 191 | color: #626262; 192 | } 193 | 194 | .item-view-header .meta a { 195 | text-decoration: underline; 196 | } 197 | 198 | .item-view-comments { 199 | background-color: #fff; 200 | margin-top: 10px; 201 | padding: 0 2em 0.5em; 202 | } 203 | 204 | .item-view-comments-header { 205 | margin: 0; 206 | font-size: 1.1em; 207 | padding: 1em 0; 208 | position: relative; 209 | } 210 | 211 | .item-view-comments-header .spinner { 212 | display: inline-block; 213 | margin: -15px 0; 214 | } 215 | 216 | .comment-children { 217 | list-style-type: none; 218 | padding: 0; 219 | margin: 0; 220 | } 221 | 222 | @media (max-width: 600px) { 223 | .item-view-header h1 { 224 | font-size: 1.25em; 225 | } 226 | } 227 | 228 | .comment-children .comment-children { 229 | margin-left: 1.5em; 230 | } 231 | 232 | .comment { 233 | border-top: 1px solid #eee; 234 | position: relative; 235 | } 236 | 237 | .comment .by, .comment .text, .comment .toggle { 238 | font-size: 0.9em; 239 | margin: 1em 0; 240 | } 241 | 242 | .comment .by { 243 | color: #626262; 244 | } 245 | 246 | .comment .by a { 247 | color: #626262; 248 | text-decoration: underline; 249 | } 250 | 251 | .comment .text { 252 | overflow-wrap: break-word; 253 | } 254 | 255 | .comment .text a:hover { 256 | color: #335d92; 257 | } 258 | 259 | .comment .text pre { 260 | white-space: pre-wrap; 261 | } 262 | 263 | .comment .toggle { 264 | background-color: #fffbf2; 265 | padding: 0.3em 0.5em; 266 | border-radius: 4px; 267 | } 268 | 269 | .comment .toggle a { 270 | color: #626262; 271 | cursor: pointer; 272 | } 273 | 274 | .comment .toggle.open { 275 | padding: 0; 276 | background-color: transparent; 277 | margin-bottom: -0.5em; 278 | } 279 | 280 | .user-view { 281 | background-color: #fff; 282 | box-sizing: border-box; 283 | padding: 2em 3em; 284 | } 285 | 286 | .user-view h1 { 287 | margin: 0; 288 | font-size: 1.5em; 289 | } 290 | 291 | .user-view .meta { 292 | list-style-type: none; 293 | padding: 0; 294 | } 295 | 296 | .user-view .label { 297 | display: inline-block; 298 | min-width: 4em; 299 | } 300 | 301 | .user-view .about { 302 | margin: 1em 0; 303 | } 304 | 305 | .user-view .links a { 306 | text-decoration: underline; 307 | } 308 | -------------------------------------------------------------------------------- /examples/astro-solid-router/src/app/data/useLoader.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Resource, 3 | ResourceFetcher, 4 | ResourceFetcherInfo, 5 | ResourceOptions, 6 | Signal, 7 | } from 'solid-js' 8 | import { 9 | createResource, 10 | onCleanup, 11 | startTransition, 12 | untrack, 13 | useContext, 14 | } from 'solid-js' 15 | import type { ReconcileOptions } from 'solid-js/store' 16 | import { createStore, reconcile, unwrap } from 'solid-js/store' 17 | import { isServer } from 'solid-js/web' 18 | import { useNavigate } from '@solidjs/router' 19 | import { isRedirectResponse, LocationHeader } from '@tanstack/bling' 20 | // import { ServerContext } from '../server/ServerContext' 21 | // import { FETCH_EVENT, ServerFunctionEvent } from '../server/types' 22 | 23 | interface RouteDataEvent {} 24 | 25 | type RouteDataSource = 26 | | S 27 | | false 28 | | null 29 | | undefined 30 | | (() => S | false | null | undefined) 31 | 32 | type RouteDataFetcher = ( 33 | source: S, 34 | event: RouteDataEvent, 35 | ) => T | Promise 36 | 37 | type RouteDataOptions = ResourceOptions & { 38 | key?: RouteDataSource 39 | reconcileOptions?: ReconcileOptions 40 | } 41 | 42 | const resources = new Set<(k: any) => void>() 43 | const promises = new Map>() 44 | 45 | export function useLoader( 46 | fetcher: RouteDataFetcher, 47 | options?: RouteDataOptions, 48 | ): Resource 49 | export function useLoader( 50 | fetcher: RouteDataFetcher, 51 | options: RouteDataOptions, 52 | ): Resource 53 | export function useLoader( 54 | fetcher?: RouteDataFetcher, 55 | options: RouteDataOptions | RouteDataOptions = {}, 56 | ): Resource | Resource { 57 | const navigate = useNavigate() 58 | // const pageEvent = useContext(ServerContext) 59 | 60 | function handleResponse(response: Response) { 61 | if (isRedirectResponse(response)) { 62 | startTransition(() => { 63 | let url = response.headers.get(LocationHeader) 64 | if (url && url.startsWith('/')) { 65 | navigate(url, { 66 | replace: true, 67 | }) 68 | } else { 69 | if (!isServer && url) { 70 | window.location.href = url 71 | } 72 | } 73 | }) 74 | // if (isServer && pageEvent) { 75 | // pageEvent.setStatusCode(response.status) 76 | // response.headers.forEach((head, value) => { 77 | // pageEvent.responseHeaders.set(value, head) 78 | // }) 79 | // } 80 | } 81 | } 82 | 83 | const resourceFetcher: ResourceFetcher = async (key: S) => { 84 | try { 85 | // let event = pageEvent as RouteDataEvent 86 | // if (isServer && pageEvent) { 87 | // event = Object.freeze({ 88 | // request: pageEvent.request, 89 | // env: pageEvent.env, 90 | // clientAddress: pageEvent.clientAddress, 91 | // locals: pageEvent.locals, 92 | // $type: FETCH_EVENT, 93 | // fetch: pageEvent.fetch, 94 | // }) 95 | // } 96 | 97 | let response = await (fetcher as any).call({}, key) 98 | if (response instanceof Response) { 99 | if (isServer) { 100 | handleResponse(response) 101 | } else { 102 | setTimeout(() => handleResponse(response), 0) 103 | } 104 | } 105 | return response 106 | } catch (e: any | Error) { 107 | if (e instanceof Response) { 108 | if (isServer) { 109 | handleResponse(e) 110 | } else { 111 | setTimeout(() => handleResponse(e), 0) 112 | } 113 | return e 114 | } 115 | throw e 116 | } 117 | } 118 | 119 | function dedupe(fetcher: ResourceFetcher): ResourceFetcher { 120 | return (key: S, info: ResourceFetcherInfo) => { 121 | if ( 122 | info.refetching && 123 | info.refetching !== true && 124 | !partialMatch(key, info.refetching) && 125 | info.value 126 | ) { 127 | return info.value 128 | } 129 | 130 | if (key == true) return fetcher(key, info) 131 | 132 | let promise = promises.get(key) 133 | if (promise) return promise 134 | promise = fetcher(key, info) as Promise 135 | promises.set(key, promise) 136 | return promise.finally(() => promises.delete(key)) 137 | } 138 | } 139 | 140 | const [resource, { refetch }] = createResource( 141 | (options.key || true) as RouteDataSource, 142 | dedupe(resourceFetcher), 143 | { 144 | storage: (init: T | undefined) => 145 | createDeepSignal(init, options.reconcileOptions), 146 | ...options, 147 | } as any, 148 | ) 149 | 150 | if (!isServer) { 151 | resources.add(refetch) 152 | onCleanup(() => resources.delete(refetch)) 153 | } 154 | 155 | return resource 156 | } 157 | 158 | export function refetchLoaders(key?: string | any[] | void) { 159 | if (isServer) throw new Error('Cannot refetch route data on the server.') 160 | return startTransition(() => { 161 | for (let refetch of resources) refetch(key) 162 | }) 163 | } 164 | 165 | function createDeepSignal(value: T, options?: ReconcileOptions) { 166 | const [store, setStore] = createStore({ 167 | value, 168 | }) 169 | return [ 170 | () => store.value, 171 | (v: T) => { 172 | const unwrapped = untrack(() => unwrap(store.value)) 173 | typeof v === 'function' && (v = v(unwrapped)) 174 | setStore('value', reconcile(v, options)) 175 | return store.value 176 | }, 177 | ] as Signal 178 | } 179 | 180 | /* React Query key matching https://github.com/tannerlinsley/react-query */ 181 | function partialMatch(a: any, b: any) { 182 | return partialDeepEqual(ensureQueryKeyArray(a), ensureQueryKeyArray(b)) 183 | } 184 | 185 | function ensureQueryKeyArray( 186 | value: V, 187 | ): R { 188 | return (Array.isArray(value) ? value : [value]) as R 189 | } 190 | 191 | /** 192 | * Checks if `b` partially matches with `a`. 193 | */ 194 | function partialDeepEqual(a: any, b: any): boolean { 195 | if (a === b) { 196 | return true 197 | } 198 | 199 | if (typeof a !== typeof b) { 200 | return false 201 | } 202 | 203 | if (a.length && !b.length) return false 204 | 205 | if (a && b && typeof a === 'object' && typeof b === 'object') { 206 | return !Object.keys(b).some((key) => !partialDeepEqual(a[key], b[key])) 207 | } 208 | 209 | return false 210 | } 211 | -------------------------------------------------------------------------------- /packages/bling/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AnyFetchFn, 3 | JsonResponse, 4 | Serializer, 5 | Fetcher, 6 | FetcherFn, 7 | FetcherMethods, 8 | FetchFnCtx, 9 | FetchFnCtxOptions, 10 | FetchFnCtxWithRequest, 11 | } from '../types' 12 | 13 | export const XBlingStatusCodeHeader = 'x-bling-status-code' 14 | export const XBlingLocationHeader = 'x-bling-location' 15 | export const LocationHeader = 'Location' 16 | export const ContentTypeHeader = 'content-type' 17 | export const XBlingResponseTypeHeader = 'x-bling-response-type' 18 | export const XBlingContentTypeHeader = 'x-bling-content-type' 19 | export const XBlingOrigin = 'x-bling-origin' 20 | export const JSONResponseType = 'application/json' 21 | 22 | /** 23 | * A JSON response. Converts `data` to JSON and sets the `Content-Type` header. 24 | */ 25 | export function json( 26 | data: TData, 27 | init: number | ResponseInit = {}, 28 | ): JsonResponse { 29 | let responseInit: any = init 30 | if (typeof init === 'number') { 31 | responseInit = { status: init } 32 | } 33 | 34 | let headers = new Headers(responseInit.headers) 35 | 36 | if (!headers.has(ContentTypeHeader)) { 37 | headers.set(ContentTypeHeader, 'application/json; charset=utf-8') 38 | } 39 | 40 | headers.set(XBlingContentTypeHeader, 'json') 41 | 42 | const response = new Response(JSON.stringify(data), { 43 | ...responseInit, 44 | headers, 45 | }) 46 | 47 | return response 48 | } 49 | 50 | /** 51 | * A redirect response. Sets the status code and the `Location` header. 52 | * Defaults to "302 Found". 53 | */ 54 | export function redirect( 55 | url: string, 56 | init: number | ResponseInit = 302, 57 | ): Response { 58 | let responseInit = init 59 | if (typeof responseInit === 'number') { 60 | responseInit = { status: responseInit } 61 | } else if (typeof responseInit.status === 'undefined') { 62 | responseInit.status = 302 63 | } 64 | 65 | if (url === '') { 66 | url = '/' 67 | } 68 | 69 | if (process.env.NODE_ENV === 'development') { 70 | if (url.startsWith('.')) { 71 | throw new Error('Relative URLs are not allowed in redirect') 72 | } 73 | } 74 | 75 | let headers = new Headers(responseInit.headers) 76 | headers.set(LocationHeader, url) 77 | 78 | const response = new Response(null, { 79 | ...responseInit, 80 | headers: headers, 81 | }) 82 | 83 | return response 84 | } 85 | 86 | export function eventStream( 87 | request: Request, 88 | init: (send: (event: string, data: any) => void) => () => void, 89 | ) { 90 | let stream = new ReadableStream({ 91 | start(controller) { 92 | let encoder = new TextEncoder() 93 | let send = (event: string, data: any) => { 94 | controller.enqueue(encoder.encode('event: ' + event + '\n')) 95 | controller.enqueue(encoder.encode('data: ' + data + '\n' + '\n')) 96 | } 97 | let cleanup = init(send) 98 | let closed = false 99 | let close = () => { 100 | if (closed) return 101 | cleanup() 102 | closed = true 103 | request.signal.removeEventListener('abort', close) 104 | controller.close() 105 | } 106 | request.signal.addEventListener('abort', close) 107 | if (request.signal.aborted) { 108 | close() 109 | return 110 | } 111 | }, 112 | }) 113 | return new Response(stream, { 114 | headers: { 'Content-Type': 'text/event-stream' }, 115 | }) 116 | } 117 | 118 | export function isResponse(value: any): value is Response { 119 | return ( 120 | value != null && 121 | typeof value.status === 'number' && 122 | typeof value.statusText === 'string' && 123 | typeof value.headers === 'object' && 124 | typeof value.body !== 'undefined' 125 | ) 126 | } 127 | 128 | const redirectStatusCodes = new Set([204, 301, 302, 303, 307, 308]) 129 | 130 | export function isRedirectResponse( 131 | response: Response | any, 132 | ): response is Response { 133 | return ( 134 | response && 135 | response instanceof Response && 136 | redirectStatusCodes.has(response.status) 137 | ) 138 | } 139 | 140 | export function mergeHeaders(...objs: (Headers | HeadersInit | undefined)[]) { 141 | const allHeaders: any = {} 142 | 143 | for (const header of objs) { 144 | if (!header) continue 145 | const headers: Headers = new Headers(header) 146 | 147 | for (const [key, value] of (headers as any).entries()) { 148 | if (value === undefined || value === 'undefined') { 149 | delete allHeaders[key] 150 | } else { 151 | allHeaders[key] = value 152 | } 153 | } 154 | } 155 | 156 | return new Headers(allHeaders) 157 | } 158 | 159 | export function mergeRequestInits(...objs: (RequestInit | undefined)[]) { 160 | return Object.assign({}, ...objs, { 161 | headers: mergeHeaders(...objs.map((o) => o && o.headers)), 162 | }) 163 | } 164 | 165 | export async function parseResponse(response: Response) { 166 | if (response instanceof Response) { 167 | const contentType = 168 | response.headers.get(XBlingContentTypeHeader) || 169 | response.headers.get(ContentTypeHeader) || 170 | '' 171 | 172 | if (contentType.includes('json')) { 173 | return await response.json() 174 | } else if (contentType.includes('text')) { 175 | return await response.text() 176 | } else if (contentType.includes('error')) { 177 | const data = await response.json() 178 | const error = new Error(data.error.message) 179 | if (data.error.stack) { 180 | error.stack = data.error.stack 181 | } 182 | return error 183 | } else if (contentType.includes('response')) { 184 | if (response.status === 204 && response.headers.get(LocationHeader)) { 185 | return redirect(response.headers.get(LocationHeader)!) 186 | } 187 | return response 188 | } else { 189 | if (response.status === 200) { 190 | const text = await response.text() 191 | try { 192 | return JSON.parse(text) 193 | } catch {} 194 | } 195 | if (response.status === 204 && response.headers.get(LocationHeader)) { 196 | return redirect(response.headers.get(LocationHeader)!) 197 | } 198 | return response 199 | } 200 | } 201 | 202 | return response 203 | } 204 | 205 | export function mergeFetchOpts( 206 | ...objs: (FetchFnCtxOptions | undefined)[] 207 | ): FetchFnCtxWithRequest { 208 | return Object.assign({}, [ 209 | ...objs, 210 | { 211 | request: mergeRequestInits(...objs.map((o) => o && o.request)), 212 | }, 213 | ]) as any 214 | } 215 | 216 | export function payloadRequestInit( 217 | payload: any, 218 | serializers: false | Serializer[], 219 | ) { 220 | let req: RequestInit = {} 221 | 222 | if (payload instanceof FormData) { 223 | req.body = payload 224 | } else { 225 | req.body = JSON.stringify( 226 | payload, 227 | serializers 228 | ? (key, value) => { 229 | let serializer = serializers.find(({ apply }) => apply(value)) 230 | if (serializer) { 231 | return serializer.serialize(value) 232 | } 233 | return value 234 | } 235 | : undefined, 236 | ) 237 | 238 | req.headers = { 239 | [ContentTypeHeader]: JSONResponseType, 240 | } 241 | } 242 | 243 | return req 244 | } 245 | 246 | export function createFetcher( 247 | route: string, 248 | fetcherImpl: FetcherFn, 249 | ): Fetcher { 250 | const fetcherMethods: FetcherMethods = { 251 | url: route, 252 | fetch: (request: RequestInit, ctx?: FetchFnCtxOptions) => { 253 | return fetcherImpl({} as any, mergeFetchOpts({ request }, ctx)) 254 | }, 255 | } 256 | 257 | return Object.assign(fetcherImpl, fetcherMethods) as Fetcher 258 | } 259 | 260 | export function resolveRequestHref( 261 | pathname: string, 262 | method: 'GET' | 'POST', 263 | payloadInit: RequestInit, 264 | ) { 265 | const resolved = 266 | method.toLowerCase() === 'get' 267 | ? `${pathname}?payload=${encodeURIComponent(payloadInit.body as string)}` 268 | : pathname 269 | 270 | return new URL( 271 | resolved, 272 | typeof document !== 'undefined' ? window.location.href : `http://localhost`, 273 | ).href 274 | } 275 | -------------------------------------------------------------------------------- /examples/astro-solid-hackernews/src/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { server$, import$, secret$, redirect } from '@tanstack/bling' 2 | import { 3 | createSignal, 4 | ErrorBoundary, 5 | lazy, 6 | Show, 7 | Suspense, 8 | useContext, 9 | } from 'solid-js' 10 | import { HydrationScript, NoHydration } from 'solid-js/web' 11 | import { manifestContext } from './manifest' 12 | import { 13 | A, 14 | Link, 15 | Outlet, 16 | RouteDataFuncArgs, 17 | RouteDefinition, 18 | useRouteData, 19 | } from '@solidjs/router' 20 | import Nav from './components/nav' 21 | import './root.css' 22 | import { Component, createResource, For } from 'solid-js' 23 | import type { IStory } from './types' 24 | import fetchAPI from './lib/api' 25 | import Story from './components/story' 26 | import Comment from './components/comment' 27 | const sayHello = server$(() => console.log('Hello world')) 28 | 29 | const LazyHello3 = lazy(() => 30 | import$({ 31 | default: () => { 32 | return ( 33 | <> 34 | 35 | 36 | ) 37 | }, 38 | }), 39 | ) 40 | 41 | const inlineSecret = secret$('I am an inline server secret!') 42 | 43 | function App() { 44 | console.log( 45 | 'Do you know the inline server secret?', 46 | inlineSecret ?? 'Not even.', 47 | ) 48 | 49 | return ( 50 | 51 | 52 | SolidStart - Hacker News 53 | 54 | 55 | 56 | 57 | 58 |