create-sprinkles 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +295 -0
- package/dist/index.d.mts +46 -0
- package/dist/index.mjs +180 -0
- package/package.json +45 -0
- package/templates/react-router-convex/.env.local +2 -0
- package/templates/react-router-convex/convex/schema.ts +9 -0
- package/templates/react-router-rsc/app/home.tsx.hbs +31 -0
- package/templates/react-router-rsc/app/root.tsx.hbs +55 -0
- package/templates/react-router-rsc/tsconfig.json.hbs +31 -0
- package/templates/react-router-rsc/workers/entry.rsc.tsx +44 -0
- package/templates/react-router-rsc/workers/entry.ssr.tsx +41 -0
- package/templates/react-router-rsc/wrangler.rsc.jsonc.hbs +25 -0
- package/templates/react-router-rsc/wrangler.ssr.jsonc.hbs +14 -0
- package/templates/react-router-rsc-content-layer/app/content.config.ts.hbs +26 -0
- package/templates/react-router-rsc-content-layer/content-layer/api.ts +350 -0
- package/templates/react-router-rsc-content-layer/content-layer/codegen.ts +89 -0
- package/templates/react-router-rsc-content-layer/content-layer/config.ts +20 -0
- package/templates/react-router-rsc-content-layer/content-layer/digest.ts +6 -0
- package/templates/react-router-rsc-content-layer/content-layer/frontmatter.ts +19 -0
- package/templates/react-router-rsc-content-layer/content-layer/loaders/file.ts +55 -0
- package/templates/react-router-rsc-content-layer/content-layer/loaders/glob.ts +82 -0
- package/templates/react-router-rsc-content-layer/content-layer/loaders/index.ts +2 -0
- package/templates/react-router-rsc-content-layer/content-layer/plugin.ts +419 -0
- package/templates/react-router-rsc-content-layer/content-layer/resolve-hook.js +12 -0
- package/templates/react-router-rsc-content-layer/content-layer/runtime.ts +73 -0
- package/templates/react-router-rsc-content-layer/content-layer/store.ts +59 -0
- package/templates/react-router-spa/app/home.tsx.hbs +7 -0
- package/templates/react-router-spa/app/root.tsx.hbs +60 -0
- package/templates/react-router-spa/tsconfig.json.hbs +26 -0
- package/templates/react-router-spa/wrangler.jsonc.hbs +9 -0
- package/templates/react-router-ssr/app/home.tsx.hbs +21 -0
- package/templates/react-router-ssr/app/root.tsx.hbs +105 -0
- package/templates/react-router-ssr/convex/schema.ts +7 -0
- package/templates/react-router-ssr/tsconfig.json.hbs +28 -0
- package/templates/react-router-ssr/wrangler.jsonc.hbs +13 -0
- package/templates/react-router-ssr-convex/app/lib/client.ts +19 -0
- package/templates/react-router-ssr-convex/app/tanstack-query-integration/middleware.ts +18 -0
- package/templates/react-router-ssr-convex/app/tanstack-query-integration/query-preloader.ts +125 -0
- package/templates/react-shared/app/routes.ts.hbs +3 -0
- package/templates/react-shared/app/styles/tailwind.css +1 -0
- package/templates/react-shared/react-compiler.plugin.ts.hbs +10 -0
- package/templates/react-shared/react-router.config.ts.hbs +9 -0
- package/templates/shared/.gitignore.hbs +23 -0
- package/templates/shared/.node-version +1 -0
- package/templates/shared/.vscode/extensions.json.hbs +8 -0
- package/templates/shared/.vscode/settings.json.hbs +72 -0
- package/templates/shared/AGENTS.md.hbs +599 -0
- package/templates/shared/README.md.hbs +24 -0
- package/templates/shared/package.json.hbs +41 -0
- package/templates/shared/vite.config.ts.hbs +384 -0
- package/templates/ts-package/src/index.ts +3 -0
- package/templates/ts-package/tests/index.test.ts +9 -0
- package/templates/ts-package/tsconfig.json +18 -0
- package/templates/ts-package-cli/bin/index.ts.hbs +1 -0
- package/templates/ts-package-cli/src/cli.ts.hbs +37 -0
- package/templates/ts-package-generator/bin/create.ts.hbs +2 -0
- package/templates/ts-package-generator/src/template.ts.hbs +22 -0
- package/templates/ts-package-sea/sea-config.json.hbs +2 -0
- package/templates/ts-package-sea/src/sea-entry.ts.hbs +4 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { DataEntry, DataStore, MetaStore } from "./api.ts";
|
|
2
|
+
|
|
3
|
+
export function createDataStore<
|
|
4
|
+
D extends Record<string, unknown> = Record<string, unknown>,
|
|
5
|
+
>(): DataStore<D> {
|
|
6
|
+
const map = new Map<string, DataEntry>();
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
clear() {
|
|
10
|
+
map.clear();
|
|
11
|
+
},
|
|
12
|
+
delete(key) {
|
|
13
|
+
map.delete(key);
|
|
14
|
+
},
|
|
15
|
+
entries() {
|
|
16
|
+
return [...map.entries()] as [string, DataEntry<D>][];
|
|
17
|
+
},
|
|
18
|
+
get(key) {
|
|
19
|
+
return map.get(key) as DataEntry<D> | undefined;
|
|
20
|
+
},
|
|
21
|
+
has(key) {
|
|
22
|
+
return map.has(key);
|
|
23
|
+
},
|
|
24
|
+
keys() {
|
|
25
|
+
return [...map.keys()];
|
|
26
|
+
},
|
|
27
|
+
set(entry) {
|
|
28
|
+
let existing = map.get(entry.id);
|
|
29
|
+
// Skip update if both entries have a digest and they match
|
|
30
|
+
if (existing?.digest && entry.digest && existing.digest === entry.digest) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
map.set(entry.id, entry);
|
|
34
|
+
return true;
|
|
35
|
+
},
|
|
36
|
+
values() {
|
|
37
|
+
return [...map.values()] as DataEntry<D>[];
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createMetaStore(): MetaStore {
|
|
43
|
+
const map = new Map<string, string>();
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
delete(key) {
|
|
47
|
+
map.delete(key);
|
|
48
|
+
},
|
|
49
|
+
get(key) {
|
|
50
|
+
return map.get(key);
|
|
51
|
+
},
|
|
52
|
+
has(key) {
|
|
53
|
+
return map.has(key);
|
|
54
|
+
},
|
|
55
|
+
set(key, value) {
|
|
56
|
+
map.set(key, value);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { PropsWithChildren } from "react";
|
|
2
|
+
|
|
3
|
+
{{#if hasConvex}}
|
|
4
|
+
import { ConvexQueryClient } from "@convex-dev/react-query";
|
|
5
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
6
|
+
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
|
7
|
+
{{/if}}
|
|
8
|
+
import { Outlet, Scripts } from "react-router";
|
|
9
|
+
|
|
10
|
+
{{#if hasConvex}}
|
|
11
|
+
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
|
|
12
|
+
const convexQueryClient = new ConvexQueryClient(convex);
|
|
13
|
+
|
|
14
|
+
const queryClient = new QueryClient({
|
|
15
|
+
defaultOptions: {
|
|
16
|
+
queries: {
|
|
17
|
+
queryKeyHashFn: convexQueryClient.hashFn(),
|
|
18
|
+
queryFn: convexQueryClient.queryFn(),
|
|
19
|
+
experimental_prefetchInRender: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
convexQueryClient.connect(queryClient);
|
|
25
|
+
|
|
26
|
+
{{/if}}
|
|
27
|
+
export function Layout({ children }: PropsWithChildren) {
|
|
28
|
+
return (
|
|
29
|
+
<html lang="en">
|
|
30
|
+
<head>
|
|
31
|
+
<meta charSet="utf-8" />
|
|
32
|
+
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
|
33
|
+
<meta content="#000000" name="theme-color" />
|
|
34
|
+
<title>{{repository}}</title>
|
|
35
|
+
</head>
|
|
36
|
+
<body>
|
|
37
|
+
{children}
|
|
38
|
+
<Scripts />
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function HydrateFallback() {
|
|
45
|
+
return <div>Loading...</div>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default function App() {
|
|
49
|
+
return (
|
|
50
|
+
{{#if hasConvex}}
|
|
51
|
+
<ConvexProvider client={convex}>
|
|
52
|
+
<QueryClientProvider client={queryClient}>
|
|
53
|
+
<Outlet />
|
|
54
|
+
</QueryClientProvider>
|
|
55
|
+
</ConvexProvider>
|
|
56
|
+
{{else}}
|
|
57
|
+
<Outlet />
|
|
58
|
+
{{/if}}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": [
|
|
3
|
+
"**/*.ts",
|
|
4
|
+
"**/*.tsx",
|
|
5
|
+
"./.react-router/types/**/*"
|
|
6
|
+
],
|
|
7
|
+
"compilerOptions": {
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noUnusedLocals": true,
|
|
11
|
+
"noUnusedParameters": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"moduleResolution": "Bundler",
|
|
16
|
+
"module": "ESNext",
|
|
17
|
+
"target": "ESNext",
|
|
18
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
19
|
+
"types": ["vite/client"],
|
|
20
|
+
"jsx": "react-jsx",
|
|
21
|
+
"rootDirs": [".", "./.react-router/types"],
|
|
22
|
+
"paths": {
|
|
23
|
+
"~/*": ["./app/*"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{{#if hasConvex}}
|
|
2
|
+
import { createPreloader } from "~/tanstack-query-integration/query-preloader.ts";
|
|
3
|
+
import type { QueryLoaderArgs } from "~/tanstack-query-integration/query-preloader.ts";
|
|
4
|
+
|
|
5
|
+
import type { Route } from "./+types/home.ts";
|
|
6
|
+
|
|
7
|
+
// TODO: Remove @ts-expect-error once preload is used
|
|
8
|
+
// @ts-expect-error TS6133 — preload is unused until you call it with your query options
|
|
9
|
+
export const loader = createPreloader(async ({ preload }: QueryLoaderArgs<Route.LoaderArgs>) => {
|
|
10
|
+
// Pre-populate query cache before render
|
|
11
|
+
// await preload(yourQueryOptions);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
{{/if}}
|
|
15
|
+
export default function Home() {
|
|
16
|
+
return (
|
|
17
|
+
<main>
|
|
18
|
+
<h1>Welcome to {{repository}}</h1>
|
|
19
|
+
</main>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{{#if hasConvex}}
|
|
2
|
+
import { useState, type PropsWithChildren } from "react";
|
|
3
|
+
import { ConvexQueryClient } from "@convex-dev/react-query";
|
|
4
|
+
import { HydrationBoundary, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
|
6
|
+
{{else}}
|
|
7
|
+
import type { PropsWithChildren } from "react";
|
|
8
|
+
{{/if}}
|
|
9
|
+
import {
|
|
10
|
+
isRouteErrorResponse,
|
|
11
|
+
Outlet,
|
|
12
|
+
Scripts,
|
|
13
|
+
ScrollRestoration,
|
|
14
|
+
} from "react-router";
|
|
15
|
+
|
|
16
|
+
{{#if hasConvex}}
|
|
17
|
+
import { useDehydratedState } from "./tanstack-query-integration/query-preloader.ts";
|
|
18
|
+
import { setQueryClient } from "~/tanstack-query-integration/middleware.ts";
|
|
19
|
+
import { createQueryClient } from "~/lib/client.ts";
|
|
20
|
+
|
|
21
|
+
import type { Route } from "./+types/root.ts";
|
|
22
|
+
|
|
23
|
+
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
|
|
24
|
+
const convexQueryClient = new ConvexQueryClient(convex);
|
|
25
|
+
|
|
26
|
+
function createConvexQueryClient() {
|
|
27
|
+
const queryClient = createQueryClient({
|
|
28
|
+
queryKeyHashFn: convexQueryClient.hashFn(),
|
|
29
|
+
queryFn: convexQueryClient.queryFn(),
|
|
30
|
+
});
|
|
31
|
+
convexQueryClient.connect(queryClient);
|
|
32
|
+
return queryClient;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const middleware = [setQueryClient(() => createConvexQueryClient())];
|
|
36
|
+
|
|
37
|
+
{{/if}}
|
|
38
|
+
export function Layout({ children }: PropsWithChildren) {
|
|
39
|
+
return (
|
|
40
|
+
<html lang="en">
|
|
41
|
+
<head>
|
|
42
|
+
<meta charSet="utf-8" />
|
|
43
|
+
<meta
|
|
44
|
+
content="width=device-width, initial-scale=1"
|
|
45
|
+
name="viewport"
|
|
46
|
+
/>
|
|
47
|
+
<meta content="#000000" name="theme-color" />
|
|
48
|
+
<title>{{repository}}</title>
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
{children}
|
|
52
|
+
<ScrollRestoration />
|
|
53
|
+
<Scripts />
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default function App() {
|
|
60
|
+
{{#if hasConvex}}
|
|
61
|
+
const [queryClient] = useState(() => createConvexQueryClient());
|
|
62
|
+
const state = useDehydratedState();
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ConvexProvider client={convex}>
|
|
66
|
+
<QueryClientProvider client={queryClient}>
|
|
67
|
+
<HydrationBoundary state={state}>
|
|
68
|
+
<Outlet />
|
|
69
|
+
</HydrationBoundary>
|
|
70
|
+
</QueryClientProvider>
|
|
71
|
+
</ConvexProvider>
|
|
72
|
+
);
|
|
73
|
+
{{else}}
|
|
74
|
+
return <Outlet />;
|
|
75
|
+
{{/if}}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function ErrorBoundary({ error }: {{#if hasConvex}}Route.ErrorBoundaryProps{{else}}{ error: unknown }{{/if}}) {
|
|
79
|
+
let message = "Oops!";
|
|
80
|
+
let details = "An unexpected error occurred.";
|
|
81
|
+
let stack: string | undefined;
|
|
82
|
+
|
|
83
|
+
if (isRouteErrorResponse(error)) {
|
|
84
|
+
message = error.status === 404 ? "404" : "Error";
|
|
85
|
+
details =
|
|
86
|
+
error.status === 404
|
|
87
|
+
? "The requested page could not be found."
|
|
88
|
+
: error.statusText || details;
|
|
89
|
+
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
|
90
|
+
details = error.message;
|
|
91
|
+
stack = error.stack;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<main className="pt-16 p-4 container mx-auto">
|
|
96
|
+
<h1>{message}</h1>
|
|
97
|
+
<p>{details}</p>
|
|
98
|
+
{stack && (
|
|
99
|
+
<pre className="w-full p-4 overflow-x-auto">
|
|
100
|
+
<code>{stack}</code>
|
|
101
|
+
</pre>
|
|
102
|
+
)}
|
|
103
|
+
</main>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": [
|
|
3
|
+
"**/*.ts",
|
|
4
|
+
"**/*.tsx",
|
|
5
|
+
"**/.server/**/*",
|
|
6
|
+
"**/.client/**/*",
|
|
7
|
+
".react-router/types/**/*"
|
|
8
|
+
],
|
|
9
|
+
"compilerOptions": {
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noUnusedLocals": true,
|
|
13
|
+
"noUnusedParameters": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"moduleResolution": "Bundler",
|
|
18
|
+
"module": "ESNext",
|
|
19
|
+
"target": "ESNext",
|
|
20
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
21
|
+
"types": ["vite/client"],
|
|
22
|
+
"jsx": "react-jsx",
|
|
23
|
+
"rootDirs": [".", "./.react-router/types"],
|
|
24
|
+
"paths": {
|
|
25
|
+
"~/*": ["./app/*"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "{{repository}}",
|
|
4
|
+
"compatibility_date": "2025-04-01",
|
|
5
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
6
|
+
"assets": {
|
|
7
|
+
"directory": "./build/client",
|
|
8
|
+
"binding": "ASSETS"
|
|
9
|
+
},
|
|
10
|
+
"observability": {
|
|
11
|
+
"enabled": true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DefaultOptions } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
export function createQueryClient(overrides: DefaultOptions["queries"] = {}) {
|
|
6
|
+
return new QueryClient({
|
|
7
|
+
defaultOptions: {
|
|
8
|
+
queries: {
|
|
9
|
+
// With SSR, we usually want to set some default staleTime
|
|
10
|
+
// Above 0 to avoid refetching immediately on the client
|
|
11
|
+
staleTime: 60 * 1000,
|
|
12
|
+
// Allows us to use `const { promise } = useQuery(...)`
|
|
13
|
+
// Along with `React.use(promise)`
|
|
14
|
+
experimental_prefetchInRender: true,
|
|
15
|
+
...overrides,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
import type { MiddlewareFunction } from "react-router";
|
|
4
|
+
|
|
5
|
+
import assert from "node:assert";
|
|
6
|
+
import { createContext, RouterContextProvider } from "react-router";
|
|
7
|
+
|
|
8
|
+
const QUERY_CLIENT = createContext<QueryClient | null>(null);
|
|
9
|
+
|
|
10
|
+
export function setQueryClient(createClient: () => QueryClient): MiddlewareFunction {
|
|
11
|
+
return ({ context }) => context.set(QUERY_CLIENT, createClient());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getQueryClient(context: Readonly<RouterContextProvider>): QueryClient {
|
|
15
|
+
const client = context.get(QUERY_CLIENT);
|
|
16
|
+
assert(client, "must use `setQueryClient` to set the query client for this application");
|
|
17
|
+
return client;
|
|
18
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DefaultError,
|
|
3
|
+
DehydratedState,
|
|
4
|
+
EnsureQueryDataOptions,
|
|
5
|
+
QueryKey,
|
|
6
|
+
} from "@tanstack/react-query";
|
|
7
|
+
import type { LoaderFunctionArgs } from "react-router";
|
|
8
|
+
|
|
9
|
+
import { dehydrate } from "@tanstack/react-query";
|
|
10
|
+
import { useMatches } from "react-router";
|
|
11
|
+
|
|
12
|
+
import { getQueryClient } from "./middleware.ts";
|
|
13
|
+
|
|
14
|
+
const DEHYDRATED_STATE_KEY = "@tanstack/react-query:dehydrated-state";
|
|
15
|
+
|
|
16
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
17
|
+
|
|
18
|
+
function mergeDehydratedStates(states: DehydratedState[]): DehydratedState {
|
|
19
|
+
const allMutations = states.flatMap(s => s.mutations ?? []);
|
|
20
|
+
const allQueries = states.flatMap(s => s.queries ?? []);
|
|
21
|
+
|
|
22
|
+
// Last-write-wins de-dupe by queryHash
|
|
23
|
+
const byHash = new Map<string, (typeof allQueries)[number]>();
|
|
24
|
+
|
|
25
|
+
for (const query of allQueries) {
|
|
26
|
+
if (!query) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
byHash.set(query.queryHash, query);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
mutations: allMutations,
|
|
34
|
+
queries: Array.from(byHash.values()),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useDehydratedState() {
|
|
39
|
+
const matches = useMatches();
|
|
40
|
+
const states = matches
|
|
41
|
+
.map(m => (m.loaderData as any)?.[DEHYDRATED_STATE_KEY] as DehydratedState | undefined)
|
|
42
|
+
.filter((s): s is DehydratedState => Boolean(s));
|
|
43
|
+
|
|
44
|
+
return states.length ? mergeDehydratedStates(states) : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createPreloader<
|
|
48
|
+
TLoaderArgs extends LoaderFunctionArgs,
|
|
49
|
+
TData extends Record<string, unknown> | void,
|
|
50
|
+
>(
|
|
51
|
+
fn: (args: QueryLoaderArgs<TLoaderArgs>) => MaybePromise<TData | Response>,
|
|
52
|
+
): QueryLoader<TLoaderArgs, TData> {
|
|
53
|
+
return (async args => {
|
|
54
|
+
const prefetches: Promise<any>[] = [];
|
|
55
|
+
const query = getQueryClient(args.context);
|
|
56
|
+
|
|
57
|
+
// If the navigation is aborted, cancel any in-flight queries
|
|
58
|
+
if (args.request.signal) {
|
|
59
|
+
if (args.request.signal.aborted) {
|
|
60
|
+
query.cancelQueries();
|
|
61
|
+
} else {
|
|
62
|
+
args.request.signal.addEventListener("abort", () => query.cancelQueries(), {
|
|
63
|
+
once: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function preload<
|
|
69
|
+
TQueryFnData,
|
|
70
|
+
TError = DefaultError,
|
|
71
|
+
TData = TQueryFnData,
|
|
72
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
73
|
+
>(options: EnsureQueryDataOptions<TQueryFnData, TError, TData, TQueryKey>): Promise<TData> {
|
|
74
|
+
const result = query.ensureQueryData(options);
|
|
75
|
+
prefetches.push(result);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = await fn({ ...args, preload });
|
|
80
|
+
|
|
81
|
+
if (result instanceof Response) {
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (args.request.signal.aborted) {
|
|
86
|
+
throw args.request.signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await Promise.all(prefetches);
|
|
90
|
+
|
|
91
|
+
if (args.request.signal.aborted) {
|
|
92
|
+
throw args.request.signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const dehydratedState = { [DEHYDRATED_STATE_KEY]: dehydrate(query) };
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
...result,
|
|
99
|
+
...dehydratedState,
|
|
100
|
+
};
|
|
101
|
+
}) as QueryLoader<TLoaderArgs, TData>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type WithDehydratedState<TData extends object | void> = (TData extends void ? {} : TData) & {
|
|
105
|
+
[DEHYDRATED_STATE_KEY]: DehydratedState;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type QueryLoaderReturn<TData extends object | void> = Promise<
|
|
109
|
+
WithDehydratedState<TData> | Response
|
|
110
|
+
>;
|
|
111
|
+
|
|
112
|
+
export type QueryLoader<LoaderArgs, TData extends object | void> = (
|
|
113
|
+
ctx: LoaderArgs,
|
|
114
|
+
) => QueryLoaderReturn<TData>;
|
|
115
|
+
|
|
116
|
+
export type QueryLoaderArgs<Args extends LoaderFunctionArgs = LoaderFunctionArgs> = Args & {
|
|
117
|
+
preload<
|
|
118
|
+
TQueryFnData,
|
|
119
|
+
TError = DefaultError,
|
|
120
|
+
TData = TQueryFnData,
|
|
121
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
122
|
+
>(
|
|
123
|
+
options: EnsureQueryDataOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
124
|
+
): Promise<TData>;
|
|
125
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import babel from "@rolldown/plugin-babel";
|
|
2
|
+
import { reactCompilerPreset } from "@vitejs/plugin-react";
|
|
3
|
+
|
|
4
|
+
export type ReactCompilerConfig = Partial<Parameters<typeof reactCompilerPreset>[0]>;
|
|
5
|
+
|
|
6
|
+
export function reactCompiler(config: ReactCompilerConfig = {}) {
|
|
7
|
+
return babel({
|
|
8
|
+
presets: [reactCompilerPreset(config as Parameters<typeof reactCompilerPreset>[0])],
|
|
9
|
+
} as Parameters<typeof babel>[0]);
|
|
10
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
.env
|
|
3
|
+
.env.local
|
|
4
|
+
/node_modules/
|
|
5
|
+
{{#if isReactRouter}}
|
|
6
|
+
|
|
7
|
+
# React Router
|
|
8
|
+
/.react-router/
|
|
9
|
+
/build/
|
|
10
|
+
{{/if}}
|
|
11
|
+
{{#if isRSC}}
|
|
12
|
+
|
|
13
|
+
# Content Layer
|
|
14
|
+
/.sprinkles/
|
|
15
|
+
|
|
16
|
+
# Cloudflare
|
|
17
|
+
.wrangler
|
|
18
|
+
{{/if}}
|
|
19
|
+
{{#if isPackage}}
|
|
20
|
+
|
|
21
|
+
# Build
|
|
22
|
+
/dist/
|
|
23
|
+
{{/if}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"js/ts.preferences.importModuleSpecifierEnding": "js",
|
|
3
|
+
"js/ts.experimental.useTsgo": true,
|
|
4
|
+
|
|
5
|
+
"oxc.enable.oxlint": true,
|
|
6
|
+
"oxc.enable.oxfmt": true,
|
|
7
|
+
|
|
8
|
+
"editor.codeActionsOnSave": {
|
|
9
|
+
"source.fixAll.oxc": "always",
|
|
10
|
+
"source.addMissingImports.ts": "explicit",
|
|
11
|
+
"source.removeUnused.ts": "never"
|
|
12
|
+
},
|
|
13
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
14
|
+
"editor.formatOnSave": true,
|
|
15
|
+
"editor.formatOnSaveMode": "file",
|
|
16
|
+
|
|
17
|
+
"[typescript]": {
|
|
18
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
19
|
+
},
|
|
20
|
+
"[tsx]": {
|
|
21
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
22
|
+
},
|
|
23
|
+
"[javascript]": {
|
|
24
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
25
|
+
},
|
|
26
|
+
"[jsx]": {
|
|
27
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
28
|
+
},
|
|
29
|
+
"[css]": {
|
|
30
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
31
|
+
},
|
|
32
|
+
"[json]": {
|
|
33
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
34
|
+
},
|
|
35
|
+
"[jsonc]": {
|
|
36
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
37
|
+
},
|
|
38
|
+
"[markdown]": {
|
|
39
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
40
|
+
},
|
|
41
|
+
{{#if hasContentLayer}}
|
|
42
|
+
"[mdx]": {
|
|
43
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
44
|
+
},
|
|
45
|
+
"mdx.server.enable": true,
|
|
46
|
+
|
|
47
|
+
{{/if}}
|
|
48
|
+
"explorer.fileNesting.enabled": true,
|
|
49
|
+
"explorer.fileNesting.expand": false,
|
|
50
|
+
"explorer.fileNesting.patterns": {
|
|
51
|
+
"package.json": ".github*, .npmrc, .prettierignore, .vscode*, biome.json*, bun.lock*, components.json, eslint.config.*, .node-version,.oxfmtrc.jsonc, .oxlintrc.jsonc, pnpm-*.yaml, prettier.config.*, tsconfig.*, wrangler.toml, wrangler.*.toml, wrangler.json*, wrangler.*.json, wrangler.*.jsonc, workspace.json",
|
|
52
|
+
"readme*": "agent*, authors, backers*, changelog*, citation*, claude*, code_of_conduct*, codeowners, contributing*, contributors, copying, credits, governance.md, history.md, license*, maintainers, readme*, security.md, sponsors*"{{#if isReactRouter}},
|
|
53
|
+
"vite.config.*": "react-router.config.*, vitest.config.*, *.plugin.ts"{{/if}}
|
|
54
|
+
},
|
|
55
|
+
{{#if isReactRouter}}
|
|
56
|
+
"files.exclude": {
|
|
57
|
+
"**/.react-router": true,
|
|
58
|
+
"**/.wrangler": true{{#if hasContentLayer}},
|
|
59
|
+
"**/.sprinkles": true{{/if}}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
"tailwindCSS.classFunctions": ["cva", "cx"],
|
|
63
|
+
|
|
64
|
+
{{/if}}
|
|
65
|
+
"biome.enabled": false,
|
|
66
|
+
"prettier.enable": false,
|
|
67
|
+
"eslint.enable": false,
|
|
68
|
+
"dprint.experimentalLsp": false,
|
|
69
|
+
"deno.enable": false,
|
|
70
|
+
"unocss.disable": true,
|
|
71
|
+
"vitest.ignoreWorkspace": true
|
|
72
|
+
}
|