feat: initial commit

gratitude
Katja Lutz 2 years ago
commit f31b1a72fb

@ -0,0 +1 @@
SESSION_SECRET=CHANGEME

26
.gitignore vendored

@ -0,0 +1,26 @@
dist
.solid
.output
.vercel
.netlify
netlify
/keepForLater
/postgres-backup.sql.gz
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/
# Temp
gitignore
# System Files
.DS_Store
Thumbs.db

@ -0,0 +1,39 @@
# SolidStart
## Setting up the project for devs
```bash
npm install
mkdir -p ../solid-directus-app/database/;
mkdir ../solid-directus-app/uploads/;
podman unshare chown -R 1000:1000 ../solid-directus-app/database/
podman unshare chown -R 1000:1000 ../solid-directus-app/uploads/
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
docker-compose up -d
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
docker-compose ps -q database
## Backing up the database
```bash
docker exec $(docker-compose ps -q database) /bin/bash \
-c "/usr/bin/pg_dump -U \$POSTGRES_USER \$POSTGRES_DB" \
| gzip -9 > postgres-backup.sql.gz
```
## Building
Solid apps are built with _adapters_, which optimise your project for deployment to different environments.
By default, `npm run build` will generate a Node app that you can run with `node build`. To use a different adapter, add it to the `devDependencies` in `package.json` and specify in your `vite.config.js`.

@ -0,0 +1,64 @@
version: "3"
services:
database:
container_name: database
image: postgis/postgis:13-master
volumes:
- ./initdb:/docker-entrypoint-initdb.d:Z
- ../solid-directus-app/database:/var/lib/postgresql/data:Z
networks:
- directus
environment:
POSTGRES_USER: "directus"
POSTGRES_PASSWORD: "directus"
POSTGRES_DB: "directus"
cache:
container_name: cache
image: redis:6
networks:
- directus
directus:
container_name: directus
image: directus/directus:latest
ports:
- 8055:8055
volumes:
# By default, uploads are stored in /directus/uploads
# Always make sure your volumes matches the storage root when using
# local driver
- ../solid-directus-app/uploads:/directus/uploads:Z
# Make sure to also mount the volume when using SQLite
# - ./database:/directus/database
# If you want to load extensions from the host
# - ./extensions:/directus/extensions
networks:
- directus
depends_on:
- cache
- database
environment:
KEY: "255d861b-5ea1-5996-9aa3-922530ec40b1"
SECRET: "6116487b-cda1-52c2-b5b5-c8022c45e263"
DB_CLIENT: "pg"
DB_HOST: "database"
DB_PORT: "5432"
DB_DATABASE: "directus"
DB_USER: "directus"
DB_PASSWORD: "directus"
CACHE_ENABLED: "true"
CACHE_STORE: "redis"
CACHE_REDIS: "redis://cache:6379"
ADMIN_EMAIL: "admin@example.com"
ADMIN_PASSWORD: "d1r3ctu5"
# Make sure to set this in production
# (see https://docs.directus.io/configuration/config-options/#general)
# PUBLIC_URL: 'https://directus.example.com'
networks:
directus:

Binary file not shown.

5271
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,34 @@
{
"name": "my-app",
"scripts": {
"dev": "solid-start dev",
"build": "solid-start build",
"start": "solid-start start",
"reformat": "prettier --write ."
},
"type": "module",
"devDependencies": {
"autoprefixer": "^10.4.2",
"cookie": "^0.5.0",
"daisyui": "^2.14.3",
"postcss": "^8.4.6",
"prettier": "^2.6.2",
"solid-app-router": "^0.3.2",
"solid-js": "^1.3.17",
"solid-meta": "^0.27.3",
"solid-start": "v0.1.0-alpha.80",
"solid-start-node": "next",
"tailwindcss": "^3.0.24",
"typescript": "^4.6.4",
"vite": "^2.9.9"
},
"engines": {
"node": ">=14"
},
"dependencies": {
"@urql/exchange-auth": "^0.1.7",
"graphql": "^16.4.0",
"turbo-solid": "^1.0.0",
"urql": "^2.2.0"
}
}

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

@ -0,0 +1,72 @@
import { useLocation } from "solid-app-router";
import {
createComputed,
createContext,
createEffect,
createResource,
useContext,
} from "solid-js";
import { createStore } from "solid-js/store";
import server, { redirect } from "solid-start/server";
import { useRefreshy } from "~/util/refreshy";
import { awaitJson, createRC } from "~/util";
import { getSessionData } from "./server";
export const authData = (
options_: { redirectNoAuth?: string; redirectWithAuth?: string } = {}
) =>
function () {
const [refreshy] = useRefreshy();
const fetcher = refreshy(
server(async (options: typeof options_) => {
const data = await getSessionData(createRC(server.request));
if (options.redirectNoAuth && !data?.username) {
throw redirect(options.redirectNoAuth);
}
if (options.redirectWithAuth && data?.username) {
throw redirect(options.redirectWithAuth);
}
return { username: data.username };
})
);
return createResource(() => fetcher(options_), { deferStream: true });
};
/*
const AuthContext = createContext();
export const AuthProvider = (props) => {
const [session, { refetch }] = createResource(server(() => getSessionData()))
const [state, setState] = createStore([{
session: {}
}])
const location = useLocation();
createEffect(function() {
const url = location.pathname;
refetch()
})
createEffect(function() {
setState(0,'session',session())
})
//createComputed(session)
return <AuthContext.Provider value={state}>
{props.children}
</AuthContext.Provider>
}
export const useAuth = () => {
const authContext = useContext(AuthContext)
console.log(authContext)
return authContext[0];
}
*/

@ -0,0 +1,194 @@
import { createCookieSessionStorage } from "solid-start/session";
import server, { json, redirect } from "solid-start/server";
import { gql, OperationResult } from "urql";
import { Session } from "solid-start/session/sessions";
import { awaitJson, createRC, RC } from "~/util";
import { createDirectusSystemClient } from "~/server/directus";
const storage = createCookieSessionStorage({
cookie: {
name: "RJ_session",
// secure doesn't work on localhost for Safari
// https://web.dev/when-to-use-local-https/
secure: true,
secrets: ["hello"],
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 30,
httpOnly: true,
},
});
export const login = async (
username,
password,
redirectTo = "/" as string | false
) => {
const client = createDirectusSystemClient();
let result: OperationResult;
try {
result = await client
.mutation(
gql`
mutation {
auth_login(email: "${username}", password: "${password}") {
access_token
refresh_token
expires
}
}
`
)
.toPromise();
if (result.error) {
console.dir(result.error);
throw new Error(result.error.response);
}
} catch (err) {
console.error(err);
}
if (!result) return;
const session = await storage.getSession();
session.set("username", username);
setAuthInSession(session, result.data.auth_login);
const response = {
headers: {
"Set-Cookie": await storage.commitSession(session),
},
};
throw redirectTo ? redirect(redirectTo, response) : json({}, response);
};
export const isLoggedIn = (session: Session) => {
return session.has("accessToken");
};
const setAuthInSession = (
session: Session,
{ access_token, refresh_token, expires }
) => {
session.set("accessToken", access_token);
session.set("refreshToken", refresh_token);
session.set("tokenTime", new Date().getTime());
if (expires != null) {
session.set("tokenExpires", expires);
}
console.log("new session data", session.data);
};
export const refreshAuthToken = async (rc: RC) => {
const session = await getSession(rc);
if (!isLoggedIn(session)) {
console.log("no refresh token");
return;
}
const refreshToken = session.get("refreshToken");
const tokenTime = session.get("tokenTime");
const tokenExpires = session.get("tokenExpires");
// console.log(tokenTime, Number.isInteger(tokenTime))
// console.log(tokenExpires, Number.isInteger(tokenExpires))
if (new Date().getTime() < tokenTime + tokenExpires / 2) {
console.log("not expired");
return;
}
console.log("token gets refreshed", refreshToken);
let result: OperationResult;
const client = createDirectusSystemClient();
try {
result = await client
.mutation(
gql`
mutation {
auth_refresh(refresh_token: "${refreshToken}") {
access_token
refresh_token
expires
}
}
`
)
.toPromise();
if (result.error) {
console.log("code", result.error.graphQLErrors[0].extensions.code);
console.log(result.error.graphQLErrors[0].extensions.graphqlErrors);
console.dir(result.error);
throw new Error(result.error.response);
}
} catch (err) {
console.error(err);
}
if (!result?.data?.auth_refresh) return;
setAuthInSession(session, result.data.auth_refresh);
const newCookie = await storage.commitSession(session);
if (rc.responseHeaders) {
rc.responseHeaders.set("Set-Cookie", newCookie);
}
return json(
{},
{
headers: {
"Set-Cookie": newCookie,
},
}
);
};
export const serveRefreshAuthToken = server(() =>
refreshAuthToken(createRC(server.request, server.responseHeaders))
);
export const getSession = async (rc: RC) => {
if (rc.context.session) {
return rc.context.session;
}
const session = await storage.getSession(rc.request.headers.get("Cookie"));
rc.context.session = session;
return session;
};
export const getSessionData = async (rc: RC) => {
const session = await getSession(rc);
return session.data;
};
export const logout = async function (
rc: RC,
redirectTo = "/" as string | false
) {
const session = await getSession(rc);
const response = {
headers: {
"Set-Cookie": await storage.destroySession(session),
},
};
throw redirectTo ? redirect(redirectTo, response) : json({}, response);
};
export const serveLogout = server(() =>
logout(createRC(server.request), false)
);

@ -0,0 +1,13 @@
import { createSignal } from "solid-js";
export default function Counter() {
const [count, setCount] = createSignal(0);
return (
<button
class="w-[200px] rounded-full bg-gray-100 border-2 border-gray-300 focus:border-gray-400 active:border-gray-400 px-[2rem] py-[1rem]"
onClick={() => setCount(count() + 1)}
>
Clicks: {count}
</button>
);
}

@ -0,0 +1,4 @@
import { hydrate } from "solid-js/web";
import { StartClient } from "solid-start/entry-client";
hydrate(() => <StartClient />, document);

@ -0,0 +1,18 @@
import {
StartServer,
createHandler,
renderAsync,
renderStream,
} from "solid-start/entry-server";
import { inlineServerModules } from "solid-start/server";
import { refreshAuthToken } from "./auth/server";
import { createRefreshMiddleware } from "./util/refreshy";
import { createRC } from "./util";
export default createHandler(
inlineServerModules,
createRefreshMiddleware((req, resHeaders) =>
refreshAuthToken(createRC(req, resHeaders))
),
renderAsync((context) => <StartServer context={context} />)
);

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@ -0,0 +1,41 @@
// @refresh reload
import { Suspense } from "solid-js";
import { Links, Meta, Routes, Scripts } from "solid-start/root";
import { ErrorBoundary } from "solid-start/error-boundary";
import "./index.css";
import { RefreshFunction, RefreshyProvider } from "./util/refreshy";
import { serveRefreshAuthToken } from "./auth/server";
const refresh: RefreshFunction = async () => {
console.log("refresh jwt");
await serveRefreshAuthToken();
return Math.round(Math.random() * 1000);
};
export default function Root() {
return (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body class="antialiased">
<ErrorBoundary>
<Suspense>
<RefreshyProvider ttl={5} refresh={refresh}>
<Suspense
fallback={<div class="w-96 mx-auto my-5">fetching</div>}
>
<Routes />
</Suspense>
</RefreshyProvider>
</Suspense>
</ErrorBoundary>
<Scripts />
</body>
</html>
);
}

@ -0,0 +1,31 @@
import { Link } from "solid-app-router";
export default function NotFound() {
return (
<main class="text-center mx-auto text-gray-700 p-4">
<h1 class="max-6-xs text-6xl text-sky-700 font-thin uppercase my-16">
Not Found
</h1>
<p class="mt-8">
Visit{" "}
<Link
href="https://solidjs.com"
target="_blank"
class="text-sky-600 hover:underline"
>
solidjs.com
</Link>{" "}
to learn how to build Solid apps.
</p>
<p class="my-4">
<Link href="/" class="text-sky-600 hover:underline">
Home
</Link>
{" - "}
<Link href="/about" class="text-sky-600 hover:underline">
About Page
</Link>
</p>
</main>
);
}

@ -0,0 +1,82 @@
import { Link, useRouteData } from "solid-app-router";
import { createEffect, createResource, For, Show } from "solid-js";
import server from "solid-start/server";
import { login, serveLogout } from "~/auth/server";
import { authData } from "~/auth/client";
import { composeRouteData, useServer } from "~/util";
import { serveTodos } from "~/server/todos";
import { useRefreshy } from "~/util/refreshy";
export const routeData = () => {
const [refreshy] = useRefreshy();
return composeRouteData({
auth: authData(),
todos: () => createResource(refreshy(() => serveTodos())),
})();
};
type Todo = { title: string };
export default function Home() {
const [data, refetch] = useRouteData();
const onLogin = useServer(
server(async () => {
await login("admin@example.com", "d1r3ctu5", false);
}),
refetch
);
const onLogout = useServer(serveLogout, refetch);
return (
<main class="text-center mx-auto container text-gray-700">
<div class="my-7 flex items-center gap-4 justify-end">
<Show when={data?.auth?.username}>
<div>Hey {data.auth.username}</div>
</Show>
<Show
when={data?.auth?.username}
fallback={
<>
<button class="btn btn-sm" onClick={onLogin}>
Login instant
</button>
<Link class="btn btn-sm" href="/login">
Login form
</Link>
</>
}
>
<button class="btn btn-sm" onClick={onLogout}>
Logout
</button>
</Show>
<Link class="btn btn-sm" href="/jwtTest">
JwtTest
</Link>
</div>
<Show
when={data?.auth?.username}
fallback={<div class="text-xl">Login first</div>}
>
<Show when={data?.todos}>
<div class="text-xl">Directus data:</div>
<div class="mx-auto flex flex-col gap-3">
<For each={data?.todos}>
{(item: Todo) => (
<div class="card bg-base-100 shadow-xl">
<div class="card-body">{item.title}</div>
</div>
)}
</For>
</div>
</Show>
</Show>
</main>
);
}

@ -0,0 +1,43 @@
import { useRouteData } from "solid-app-router";
import { createResource } from "solid-js";
import { authData } from "~/auth/client";
import { serveJwtData } from "~/server/jwtData";
import { composeRouteData } from "~/util";
import { useRefreshy } from "~/util/refreshy";
export const routeData = () => {
const [refreshy, refreshData] = useRefreshy();
const gety = refreshy((name) => serveJwtData(name));
return composeRouteData({
// TODO: Fix the redirect, it only works with ssr
auth: authData({ redirectNoAuth: "/login" }),
john: () => createResource(() => gety("john " + refreshData())),
klaus: () => createResource(() => gety("klaus " + refreshData())),
marry: () => createResource(() => gety("marry " + refreshData())),
})();
};
export default () => {
const [data, refetch, repos] = useRouteData() as any;
return (
<div class="mx-auto w-96 my-11">
<button
type="button"
class="btn mb-6"
onClick={() => {
repos.john[1].refetch();
repos.klaus[1].refetch();
repos.marry[1].refetch();
}}
>
Refetch
</button>
<div>{data.john}</div>
<div>{data.klaus}</div>
<div>{data.marry}</div>
</div>
);
};

@ -0,0 +1,43 @@
import { login } from "~/auth/server";
import server from "solid-start/server";
import { composeRouteData, useServer } from "~/util";
import { authData } from "~/auth/client";
import { useNavigate } from "solid-app-router";
export const routeData = composeRouteData({
auth: authData({ redirectWithAuth: "/" }),
});
export default function Login() {
const navigate = useNavigate();
// TODO: Use createAction when it works with redirects
const onClick = useServer(
server(() => login("admin@example.com", "d1r3ctu5")),
() => navigate("/")
);
return (
<main class="text-center mx-auto text-gray-700 p-4">
<h1 class="max-6-xs text-6xl text-sky-700 font-thin uppercase my-16">
Login Form
</h1>
<div>Here shall be a login form</div>
<button class="btn" onClick={onClick}>
Login and navigate
</button>
</main>
);
}
/*
const [item] = createUrqlResource(function() {
console.log('requested')
return gql`
query HelloWorld {
hello
}`
})
*/

@ -0,0 +1,25 @@
import { useRouteData } from "solid-app-router";
import { createResource } from "solid-js";
import server from "solid-start/server";
import { login } from "~/auth/server";
// This doesn't work yet!
// Open up: http://localhost:3000/loginWithGet?username=admin@example.com&password=d1r3ctu5
export function routeData() {
return createResource(
server(() => {
const url = new URL(server.request.url);
const username = url.searchParams.get("username");
const password = url.searchParams.get("password");
return login(username, password, "/");
})
);
}
export default () => {
const [data] = useRouteData();
data();
return <div></div>;
};

@ -0,0 +1,16 @@
import { useRouteData } from "solid-app-router";
import { createResource } from "solid-js";
import server from "solid-start/server";
import { logout } from "~/auth/server";
export function routeData() {
return createResource(server(() => logout(server.request)));
}
// http://localhost:3000/login?username=admin@example.com&password=d1r3ctu5
export default () => {
const [data] = useRouteData();
data();
return <div></div>;
};

@ -0,0 +1,79 @@
import { Session } from "solid-start/session/sessions";
import {
createClient,
fetchExchange,
errorExchange,
makeOperation,
} from "urql";
import { authExchange } from "@urql/exchange-auth";
import { GraphQLError } from "graphql";
export const createDirectusSystemClient = () => {
const client = createClient({
url: "http://localhost:8055/graphql/system",
});
return client;
};
export const createDirectusClient = (session: Session, onAuthError?) => {
const client = createClient({
url: "http://localhost:8055/graphql",
exchanges: [
authExchange({
getAuth: async ({ authState }) => {
if (!authState) {
const token = session.get("accessToken");
const refreshToken = session.get("refreshToken");
if (token && refreshToken) {
return { token, refreshToken };
}
return null;
}
return null;
},
addAuthToOperation: ({ authState, operation }: any) => {
if (!authState || !authState.token) {
return operation;
}
const fetchOptions =
typeof operation.context.fetchOptions === "function"
? operation.context.fetchOptions()
: operation.context.fetchOptions || {};
return makeOperation(operation.kind, operation, {
...operation.context,
fetchOptions: {
...fetchOptions,
headers: {
...fetchOptions.headers,
Authorization: `Bearer ${authState.token}`,
},
},
});
},
}),
errorExchange({
onError: async (error, o) => {
console.log("code", error.graphQLErrors[0].extensions.code);
console.log(error.graphQLErrors[0].extensions.graphqlErrors);
const isAuthError = error.graphQLErrors.some(
(e: GraphQLError) =>
["TOKEN_EXPIRED", "FORBIDDEN"].indexOf(
e.extensions?.code as string
) >= 0
);
console.log("isAuthError", isAuthError);
if (onAuthError && isAuthError) {
onAuthError();
}
},
}),
fetchExchange,
],
});
return client;
};

@ -0,0 +1,9 @@
import server from "solid-start/server";
export const getJwtData = async function (name: string) {
return `hello ${name}, random: ${Math.round(Math.random() * 100)}`;
};
export const serveJwtData = server((name) => {
return getJwtData(name);
});

@ -0,0 +1,54 @@
import { gql, OperationResult } from "urql";
import { getSession, isLoggedIn, logout } from "../auth/server";
import server from "solid-start/server";
import { awaitJson, createRC, RC } from "../util";
import { createDirectusClient } from "./directus";
export const getTodos = async function (rc: RC) {
const session = await getSession(rc);
if (!isLoggedIn(session)) {
return [];
}
let result: OperationResult;
let authError = false;
try {
const client = createDirectusClient(session, () => {
authError = true;
});
result = await client
.query(
gql`
query {
Todos {
title
}
}
`
)
.toPromise();
if (result.error) {
console.dir(result.error);
throw new Error(result.error.response);
}
} catch (err) {
console.error(err);
return [];
}
if (authError) {
console.log("logout");
return await logout(rc, "/login");
}
if (!result.data) return [];
return result.data.Todos;
};
export const serveTodos = server(async () =>
getTodos(createRC(server.request))
);

@ -0,0 +1,111 @@
import { useNavigate } from "solid-app-router";
import {
createEffect,
resetErrorBoundaries,
ResourceReturn,
startTransition,
} from "solid-js";
import { createStore, produce } from "solid-js/store";
import { isRedirectResponse, LocationHeader } from "solid-start/server";
// Should handle redirects, but it broke "quickly"
export const useServer = function (serverFunction, callback?) {
const navigate = useNavigate();
return async () => {
let res;
try {
res = await serverFunction();
} catch (err) {
if (err instanceof Response) {
res = err;
}
}
if (isRedirectResponse(res)) {
startTransition(() => {
navigate(res.headers.get(LocationHeader));
resetErrorBoundaries();
});
return null;
}
return callback ? callback(res) : res;
};
};
export const awaitJson = <T extends (...args) => any>(fn: T) => {
return async (...args: Parameters<T>) => {
const result = await fn(...args);
if (!result) {
return result;
}
if (!(result instanceof Response)) {
return result;
}
const contentType = result.headers.get("content-type");
if (!contentType) {
return result;
}
if (contentType.indexOf("application/json") === -1) {
return result;
}
const jsonContent = await result.json();
return jsonContent;
};
};
export const composeRouteData =
(functionMap: Record<any, (state) => ResourceReturn<any>>) => () => {
const [state, setState] = createStore({} as any);
const repos = {};
for (const dataKey of Object.keys(functionMap)) {
repos[dataKey] = functionMap[dataKey](state);
setState(dataKey, repos[dataKey][0]());
createEffect(function () {
const newValue = repos[dataKey][0]();
setState(
produce(function (s) {
s[dataKey] = newValue;
})
);
});
}
const refetch = function () {
for (const dataKey of Object.keys(functionMap)) {
repos[dataKey][1].refetch();
}
};
return [state, refetch, repos];
};
export type RC = {
request: Request;
context: Record<any, any>;
responseHeaders?: Headers;
url: URL;
};
export const createRC = function (request: Request, responseHeaders?: Headers) {
const rc: RC = {
request,
responseHeaders,
url: new URL(request.url),
context: {},
};
return rc;
};

@ -0,0 +1,104 @@
import {
createContext,
createSignal,
FlowComponent,
useContext,
} from "solid-js";
import { isServer } from "solid-js/web";
const refresh_ = async function (
ctx: ReturnType<typeof createRefreshyContext>
) {
const time = new Date().getTime();
if (ctx.lastRefresh + ctx.ttl * 1000 > time) {
return;
}
ctx.lastRefresh = time;
ctx.refreshPromise = (async () => {
const result = await ctx.refresh(ctx);
ctx.refreshResult[1](result);
ctx.refreshPromise = null;
})();
await ctx.refreshPromise;
};
export type RefreshyContext = ReturnType<typeof createRefreshyContext>;
const createRefreshyContext = function (
refresh: Function,
ttl = 60,
refreshSoon = false
) {
const refreshResult = createSignal();
const refreshy = <T extends (...args) => Promise<any>>(fetcher: T) => {
if (isServer) {
return fetcher;
}
return async (...args: Parameters<T>) => {
if (ctx.refreshPromise) {
await ctx.refreshPromise;
} else {
await refresh_(ctx);
}
return await fetcher(...args);
};
};
const ctx = {
ttl,
refresh,
refreshResult,
refreshy,
lastRefresh: isServer || refreshSoon ? 0 : new Date().getTime(),
refreshPromise: null as Promise<any> | null,
};
return ctx;
};
const FetcherContext = createContext(
{} as ReturnType<typeof createRefreshyContext>
);
export type RefreshFunction = (ctx: RefreshyContext) => any;
export const RefreshyProvider: FlowComponent<{
ttl?: number;
refresh: RefreshFunction;
refreshSoon?: boolean;
}> = (props) => {
const ctx = createRefreshyContext(
props.refresh,
props.ttl,
props.refreshSoon
);
return (
<FetcherContext.Provider value={ctx}>
{props.children}
</FetcherContext.Provider>
);
};
export const createRefreshMiddleware = (
refresh: (request: Request, responseHeaders: Headers) => Promise<any>
) => {
return function ({ forward }) {
return async (ctx) => {
await refresh(ctx.request, ctx.responseHeaders);
return await forward(ctx);
};
};
};
export const useRefreshy = function () {
const ctx = useContext(FetcherContext);
const refreshy = ctx.refreshy;
const signal = ctx.refreshResult[0];
return [refreshy, signal] as [typeof refreshy, typeof signal];
};

@ -0,0 +1,7 @@
module.exports = {
content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [require("daisyui")],
};

@ -0,0 +1,16 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"jsxImportSource": "solid-js",
"jsx": "preserve",
"types": ["vite/client"],
"baseUrl": "./",
"paths": {
"~/*": ["./src/*"]
}
}
}

@ -0,0 +1,6 @@
import { defineConfig } from "vite";
import solid from "solid-start";
export default defineConfig({
plugins: [solid()],
});
Loading…
Cancel
Save